Skip to content

Commit

Permalink
implement SearchOwnerReferenceResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
schrej committed Jun 26, 2024
1 parent dacc8bf commit d7f346f
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 100 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ If you need to create DNS records for your machines, you'll therefore be require

We've currently only implemented one strategy for identifying the hostname of a machine, since it's the one we (Deutsche Telekom) are using. In case you have other requirements, we're open to accept contributions for new strategies. Please open an issue if you're interested.

Our strategy uses the name of the CAPI `Machine` as the hostname. To determine the Machine name the provider follows the owner chain from the `IPAddressClaim` via the infrastructure provider resources to the `Machine`. Our implementation is currently provider specific and only supports the VSphere and metal3 providers.
Our strategy uses the name of the CAPI `Machine` as the hostname. To determine the Machine name the provider follows the owner chain from the `IPAddressClaim` via the infrastructure provider resources to the `Machine`. This is used by searching through the owner references up to a depth of five.

To enable setting DNS entries, set the `spec.dnsZone` parameter on the `InfobloxIPPool` to your desired zone. The resulting DNS entries will then be `<machine name>.<dnsZone>`. The DNS view will be set to `default.<dnsZone>`.

Expand Down
27 changes: 5 additions & 22 deletions internal/controllers/ipaddressclaim.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,26 +278,9 @@ func (h *InfobloxClaimHandler) getHostname(ctx context.Context) (string, error)
}

func getHostnameResolver(cl client.Client, claim *ipamv1.IPAddressClaim) (hostname.Resolver, error) {
switch claim.Kind {
case "Metal3Data":
return &hostname.OwnerChainResolver{
Client: cl,
Chain: []metav1.GroupKind{
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Data"},
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Machine"},
{Group: "cluster.x-k8s.io", Kind: "Machine"},
},
}, nil
case "VSphereVM":
return &hostname.OwnerChainResolver{
Client: cl,
Chain: []metav1.GroupKind{
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereVM"},
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereMachine"},
{Group: "cluster.x-k8s.io", Kind: "Machine"},
},
}, nil
default:
return nil, fmt.Errorf("failed to create resolver for kind %s", claim.Kind)
}
return &hostname.SearchOwnerReferenceResolver{
Client: cl,
SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"},
MaxDepth: 5,
}, nil
}
70 changes: 70 additions & 0 deletions internal/hostname/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package hostname
import (
"context"
"fmt"
"slices"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -50,6 +51,75 @@ func (r *OwnerChainResolver) GetHostname(ctx context.Context, claim *ipamv1.IPAd
return "", fmt.Errorf("failed to follow owner chain")
}

// SearchOwnerReferenceResolver performs a depth search on the owner references until it finds the specified [metav1.GroupKind]
// and uses it's name as the hostname.
type SearchOwnerReferenceResolver struct {
client.Client
MaxDepth int
SearchFor metav1.GroupKind
}

// GetHostname returns the hostname for the specified claim.
func (r *SearchOwnerReferenceResolver) GetHostname(ctx context.Context, claim *ipamv1.IPAddressClaim) (string, error) {
if r.MaxDepth == 0 {
r.MaxDepth = 5
}
obj := client.Object(claim)
name, err := r.find(ctx, obj, 1)
if err != nil {
return "", err
}
if name != "" {
return name, nil
}
return "", fmt.Errorf("failed to find owner reference to specified group and kind")
}

func (r *SearchOwnerReferenceResolver) find(ctx context.Context, obj client.Object, currentDepth int) (string, error) {
nextRefs := []metav1.OwnerReference{}
for _, o := range obj.GetOwnerReferences() {
if o.Kind == r.SearchFor.Kind && apiVersionToGroupVersion(o.APIVersion).Group == r.SearchFor.Group {
return o.Name, nil
}

nextRefs = append(nextRefs, o)
}

// We'll try to iterate through promising things first to reduce the amount of api requests.
// The simple heuristic is that anything in the infrastructure.capi.x-k8s.io group or anything that contains Machine in
// it's name comes first. The name is more important than the group.
// We don't care for equality since this is just optimization.
slices.SortFunc(nextRefs, func(a, b metav1.OwnerReference) int {
if strings.Contains(b.Kind, "Machine") {
return 1
}
if strings.Contains(a.Kind, "Machine") || strings.HasPrefix(b.APIVersion, "infrastructure") {
return -1
}
if strings.HasPrefix(b.APIVersion, "infrastructure") {
return 1
}
return 0
})

for _, o := range nextRefs {
if currentDepth >= r.MaxDepth {
continue
}

obj2 := &unstructured.Unstructured{}
obj2.SetAPIVersion(o.APIVersion)
obj2.SetKind(o.Kind)
if err := r.Client.Get(ctx, types.NamespacedName{Name: o.Name, Namespace: obj2.GetNamespace()}, obj2); err != nil {
return "", err
}
if name, err := r.find(ctx, obj2, currentDepth+1); name != "" || err != nil {
return name, err
}
}
return "", nil
}

// findOwnerReferenceWithGK searches the owner references of an object and returns the first with the specified [metav1.GroupVersion].
func findOwnerReferenceWithGK(obj client.Object, gk metav1.GroupKind) (metav1.OwnerReference, error) {
for _, o := range obj.GetOwnerReferences() {
Expand Down
160 changes: 83 additions & 77 deletions internal/hostname/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,52 +27,42 @@ var _ = Describe("determining hostnames", func() {
Expect(capv1.AddToScheme(testScheme)).To(Succeed())

Context("metal3", func() {
When("the owner chain can be resolved", func() {
var cl client.Client
var claim ipamv1.IPAddressClaim
BeforeEach(func() {
cl = fake.NewClientBuilder().
WithScheme(testScheme).
WithObjects(
&metal3v1.Metal3Data{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
OwnerReferences: []metav1.OwnerReference{
{
Name: "machine",
Kind: "Metal3Machine",
APIVersion: metal3v1.GroupVersion.String(),
},
var cl client.Client
var claim ipamv1.IPAddressClaim
BeforeEach(func() {
cl = fake.NewClientBuilder().
WithScheme(testScheme).
WithObjects(
&metal3v1.Metal3Data{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
OwnerReferences: []metav1.OwnerReference{
{
Name: "machine",
Kind: "Metal3Machine",
APIVersion: metal3v1.GroupVersion.String(),
},
},
},
&metal3v1.Metal3Machine{
ObjectMeta: metav1.ObjectMeta{
Name: "machine",
OwnerReferences: []metav1.OwnerReference{
{
Name: "capimachine",
Kind: "Machine",
APIVersion: "cluster.x-k8s.io/v1beta1",
},
},
&metal3v1.Metal3Machine{
ObjectMeta: metav1.ObjectMeta{
Name: "machine",
OwnerReferences: []metav1.OwnerReference{
{
Name: "capimachine",
Kind: "Machine",
APIVersion: "cluster.x-k8s.io/v1beta1",
},
},
},
).
Build()
claim = ipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{
Name: "data",
Kind: "Metal3Data",
APIVersion: metal3v1.GroupVersion.String(),
},
},
},
}
})
It("the name of the capi Machine is used as the hostname", func() {
).
Build()
claim = newClaim("data", "Metal3Data", metal3v1.GroupVersion.String())
})
Context("OwnerChainResolver", func() {
It("finds the capi machine's name", func() {
r := OwnerChainResolver{Client: cl, Chain: []metav1.GroupKind{
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Data"},
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Machine"},
Expand All @@ -81,53 +71,49 @@ var _ = Describe("determining hostnames", func() {
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
})
})
Context("SearchOwnerReferenceResolver", func() {
It("finds the capi machine's name", func() {
r := SearchOwnerReferenceResolver{Client: cl, SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"}}
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
})
})
})
Context("vsphere", func() {
When("the owner chain can be resolved", func() {
var cl client.Client
var claim ipamv1.IPAddressClaim
BeforeEach(func() {
cl = fake.NewClientBuilder().
WithScheme(testScheme).
WithObjects(
&capv1.VSphereVM{
ObjectMeta: metav1.ObjectMeta{
Name: "vm",
OwnerReferences: []metav1.OwnerReference{
{
Name: "machine",
Kind: "VSphereMachine",
APIVersion: capv1.GroupVersion.String(),
},
var cl client.Client
var claim ipamv1.IPAddressClaim
BeforeEach(func() {
cl = fake.NewClientBuilder().
WithScheme(testScheme).
WithObjects([]client.Object{
&capv1.VSphereVM{
ObjectMeta: metav1.ObjectMeta{
Name: "vm",
OwnerReferences: []metav1.OwnerReference{
{
Name: "machine",
Kind: "VSphereMachine",
APIVersion: capv1.GroupVersion.String(),
},
},
},
&capv1.VSphereMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "machine",
OwnerReferences: []metav1.OwnerReference{
{
Name: "capimachine",
Kind: "Machine",
APIVersion: "cluster.x-k8s.io/v1beta1",
},
},
&capv1.VSphereMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "machine",
OwnerReferences: []metav1.OwnerReference{
{
Name: "capimachine",
Kind: "Machine",
APIVersion: "cluster.x-k8s.io/v1beta1",
},
},
},
).
Build()
claim = ipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{
Name: "vm",
Kind: "VSphereVM",
APIVersion: capv1.GroupVersion.String(),
},
},
},
}
})
}...).
Build()
claim = newClaim("vm", "VSphereVM", capv1.GroupVersion.String())
})
Context("OwnerChainResolver", func() {
It("the name of the capi Machine is used as the hostname", func() {
r := OwnerChainResolver{Client: cl, Chain: []metav1.GroupKind{
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereVM"},
Expand All @@ -137,5 +123,25 @@ var _ = Describe("determining hostnames", func() {
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
})
})
Context("SearchOwnerReferenceResolver", func() {
It("finds the capi machine's name", func() {
r := SearchOwnerReferenceResolver{Client: cl, SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"}}
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
})
})
})
})

func newClaim(name, kind, apiVersion string) ipamv1.IPAddressClaim {
return ipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{
Name: name,
Kind: kind,
APIVersion: apiVersion,
},
},
},
}
}

0 comments on commit d7f346f

Please sign in to comment.