Skip to content

Commit d7f346f

Browse files
committed
implement SearchOwnerReferenceResolver
1 parent dacc8bf commit d7f346f

File tree

4 files changed

+159
-100
lines changed

4 files changed

+159
-100
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ If you need to create DNS records for your machines, you'll therefore be require
9696

9797
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.
9898

99-
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.
99+
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.
100100

101101
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>`.
102102

internal/controllers/ipaddressclaim.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -278,26 +278,9 @@ func (h *InfobloxClaimHandler) getHostname(ctx context.Context) (string, error)
278278
}
279279

280280
func getHostnameResolver(cl client.Client, claim *ipamv1.IPAddressClaim) (hostname.Resolver, error) {
281-
switch claim.Kind {
282-
case "Metal3Data":
283-
return &hostname.OwnerChainResolver{
284-
Client: cl,
285-
Chain: []metav1.GroupKind{
286-
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Data"},
287-
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Machine"},
288-
{Group: "cluster.x-k8s.io", Kind: "Machine"},
289-
},
290-
}, nil
291-
case "VSphereVM":
292-
return &hostname.OwnerChainResolver{
293-
Client: cl,
294-
Chain: []metav1.GroupKind{
295-
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereVM"},
296-
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereMachine"},
297-
{Group: "cluster.x-k8s.io", Kind: "Machine"},
298-
},
299-
}, nil
300-
default:
301-
return nil, fmt.Errorf("failed to create resolver for kind %s", claim.Kind)
302-
}
281+
return &hostname.SearchOwnerReferenceResolver{
282+
Client: cl,
283+
SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"},
284+
MaxDepth: 5,
285+
}, nil
303286
}

internal/hostname/resolver.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package hostname
44
import (
55
"context"
66
"fmt"
7+
"slices"
78
"strings"
89

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

54+
// SearchOwnerReferenceResolver performs a depth search on the owner references until it finds the specified [metav1.GroupKind]
55+
// and uses it's name as the hostname.
56+
type SearchOwnerReferenceResolver struct {
57+
client.Client
58+
MaxDepth int
59+
SearchFor metav1.GroupKind
60+
}
61+
62+
// GetHostname returns the hostname for the specified claim.
63+
func (r *SearchOwnerReferenceResolver) GetHostname(ctx context.Context, claim *ipamv1.IPAddressClaim) (string, error) {
64+
if r.MaxDepth == 0 {
65+
r.MaxDepth = 5
66+
}
67+
obj := client.Object(claim)
68+
name, err := r.find(ctx, obj, 1)
69+
if err != nil {
70+
return "", err
71+
}
72+
if name != "" {
73+
return name, nil
74+
}
75+
return "", fmt.Errorf("failed to find owner reference to specified group and kind")
76+
}
77+
78+
func (r *SearchOwnerReferenceResolver) find(ctx context.Context, obj client.Object, currentDepth int) (string, error) {
79+
nextRefs := []metav1.OwnerReference{}
80+
for _, o := range obj.GetOwnerReferences() {
81+
if o.Kind == r.SearchFor.Kind && apiVersionToGroupVersion(o.APIVersion).Group == r.SearchFor.Group {
82+
return o.Name, nil
83+
}
84+
85+
nextRefs = append(nextRefs, o)
86+
}
87+
88+
// We'll try to iterate through promising things first to reduce the amount of api requests.
89+
// The simple heuristic is that anything in the infrastructure.capi.x-k8s.io group or anything that contains Machine in
90+
// it's name comes first. The name is more important than the group.
91+
// We don't care for equality since this is just optimization.
92+
slices.SortFunc(nextRefs, func(a, b metav1.OwnerReference) int {
93+
if strings.Contains(b.Kind, "Machine") {
94+
return 1
95+
}
96+
if strings.Contains(a.Kind, "Machine") || strings.HasPrefix(b.APIVersion, "infrastructure") {
97+
return -1
98+
}
99+
if strings.HasPrefix(b.APIVersion, "infrastructure") {
100+
return 1
101+
}
102+
return 0
103+
})
104+
105+
for _, o := range nextRefs {
106+
if currentDepth >= r.MaxDepth {
107+
continue
108+
}
109+
110+
obj2 := &unstructured.Unstructured{}
111+
obj2.SetAPIVersion(o.APIVersion)
112+
obj2.SetKind(o.Kind)
113+
if err := r.Client.Get(ctx, types.NamespacedName{Name: o.Name, Namespace: obj2.GetNamespace()}, obj2); err != nil {
114+
return "", err
115+
}
116+
if name, err := r.find(ctx, obj2, currentDepth+1); name != "" || err != nil {
117+
return name, err
118+
}
119+
}
120+
return "", nil
121+
}
122+
53123
// findOwnerReferenceWithGK searches the owner references of an object and returns the first with the specified [metav1.GroupVersion].
54124
func findOwnerReferenceWithGK(obj client.Object, gk metav1.GroupKind) (metav1.OwnerReference, error) {
55125
for _, o := range obj.GetOwnerReferences() {

internal/hostname/resolver_test.go

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,52 +27,42 @@ var _ = Describe("determining hostnames", func() {
2727
Expect(capv1.AddToScheme(testScheme)).To(Succeed())
2828

2929
Context("metal3", func() {
30-
When("the owner chain can be resolved", func() {
31-
var cl client.Client
32-
var claim ipamv1.IPAddressClaim
33-
BeforeEach(func() {
34-
cl = fake.NewClientBuilder().
35-
WithScheme(testScheme).
36-
WithObjects(
37-
&metal3v1.Metal3Data{
38-
ObjectMeta: metav1.ObjectMeta{
39-
Name: "data",
40-
OwnerReferences: []metav1.OwnerReference{
41-
{
42-
Name: "machine",
43-
Kind: "Metal3Machine",
44-
APIVersion: metal3v1.GroupVersion.String(),
45-
},
30+
var cl client.Client
31+
var claim ipamv1.IPAddressClaim
32+
BeforeEach(func() {
33+
cl = fake.NewClientBuilder().
34+
WithScheme(testScheme).
35+
WithObjects(
36+
&metal3v1.Metal3Data{
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: "data",
39+
OwnerReferences: []metav1.OwnerReference{
40+
{
41+
Name: "machine",
42+
Kind: "Metal3Machine",
43+
APIVersion: metal3v1.GroupVersion.String(),
4644
},
4745
},
4846
},
49-
&metal3v1.Metal3Machine{
50-
ObjectMeta: metav1.ObjectMeta{
51-
Name: "machine",
52-
OwnerReferences: []metav1.OwnerReference{
53-
{
54-
Name: "capimachine",
55-
Kind: "Machine",
56-
APIVersion: "cluster.x-k8s.io/v1beta1",
57-
},
47+
},
48+
&metal3v1.Metal3Machine{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: "machine",
51+
OwnerReferences: []metav1.OwnerReference{
52+
{
53+
Name: "capimachine",
54+
Kind: "Machine",
55+
APIVersion: "cluster.x-k8s.io/v1beta1",
5856
},
5957
},
6058
},
61-
).
62-
Build()
63-
claim = ipamv1.IPAddressClaim{
64-
ObjectMeta: metav1.ObjectMeta{
65-
OwnerReferences: []metav1.OwnerReference{
66-
{
67-
Name: "data",
68-
Kind: "Metal3Data",
69-
APIVersion: metal3v1.GroupVersion.String(),
70-
},
71-
},
7259
},
73-
}
74-
})
75-
It("the name of the capi Machine is used as the hostname", func() {
60+
).
61+
Build()
62+
claim = newClaim("data", "Metal3Data", metal3v1.GroupVersion.String())
63+
})
64+
Context("OwnerChainResolver", func() {
65+
It("finds the capi machine's name", func() {
7666
r := OwnerChainResolver{Client: cl, Chain: []metav1.GroupKind{
7767
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Data"},
7868
{Group: "infrastructure.cluster.x-k8s.io", Kind: "Metal3Machine"},
@@ -81,53 +71,49 @@ var _ = Describe("determining hostnames", func() {
8171
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
8272
})
8373
})
74+
Context("SearchOwnerReferenceResolver", func() {
75+
It("finds the capi machine's name", func() {
76+
r := SearchOwnerReferenceResolver{Client: cl, SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"}}
77+
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
78+
})
79+
})
8480
})
8581
Context("vsphere", func() {
86-
When("the owner chain can be resolved", func() {
87-
var cl client.Client
88-
var claim ipamv1.IPAddressClaim
89-
BeforeEach(func() {
90-
cl = fake.NewClientBuilder().
91-
WithScheme(testScheme).
92-
WithObjects(
93-
&capv1.VSphereVM{
94-
ObjectMeta: metav1.ObjectMeta{
95-
Name: "vm",
96-
OwnerReferences: []metav1.OwnerReference{
97-
{
98-
Name: "machine",
99-
Kind: "VSphereMachine",
100-
APIVersion: capv1.GroupVersion.String(),
101-
},
82+
var cl client.Client
83+
var claim ipamv1.IPAddressClaim
84+
BeforeEach(func() {
85+
cl = fake.NewClientBuilder().
86+
WithScheme(testScheme).
87+
WithObjects([]client.Object{
88+
&capv1.VSphereVM{
89+
ObjectMeta: metav1.ObjectMeta{
90+
Name: "vm",
91+
OwnerReferences: []metav1.OwnerReference{
92+
{
93+
Name: "machine",
94+
Kind: "VSphereMachine",
95+
APIVersion: capv1.GroupVersion.String(),
10296
},
10397
},
10498
},
105-
&capv1.VSphereMachine{
106-
ObjectMeta: metav1.ObjectMeta{
107-
Name: "machine",
108-
OwnerReferences: []metav1.OwnerReference{
109-
{
110-
Name: "capimachine",
111-
Kind: "Machine",
112-
APIVersion: "cluster.x-k8s.io/v1beta1",
113-
},
99+
},
100+
&capv1.VSphereMachine{
101+
ObjectMeta: metav1.ObjectMeta{
102+
Name: "machine",
103+
OwnerReferences: []metav1.OwnerReference{
104+
{
105+
Name: "capimachine",
106+
Kind: "Machine",
107+
APIVersion: "cluster.x-k8s.io/v1beta1",
114108
},
115109
},
116110
},
117-
).
118-
Build()
119-
claim = ipamv1.IPAddressClaim{
120-
ObjectMeta: metav1.ObjectMeta{
121-
OwnerReferences: []metav1.OwnerReference{
122-
{
123-
Name: "vm",
124-
Kind: "VSphereVM",
125-
APIVersion: capv1.GroupVersion.String(),
126-
},
127-
},
128111
},
129-
}
130-
})
112+
}...).
113+
Build()
114+
claim = newClaim("vm", "VSphereVM", capv1.GroupVersion.String())
115+
})
116+
Context("OwnerChainResolver", func() {
131117
It("the name of the capi Machine is used as the hostname", func() {
132118
r := OwnerChainResolver{Client: cl, Chain: []metav1.GroupKind{
133119
{Group: "infrastructure.cluster.x-k8s.io", Kind: "VSphereVM"},
@@ -137,5 +123,25 @@ var _ = Describe("determining hostnames", func() {
137123
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
138124
})
139125
})
126+
Context("SearchOwnerReferenceResolver", func() {
127+
It("finds the capi machine's name", func() {
128+
r := SearchOwnerReferenceResolver{Client: cl, SearchFor: metav1.GroupKind{Group: "cluster.x-k8s.io", Kind: "Machine"}}
129+
Expect(r.GetHostname(context.Background(), &claim)).To(Equal("capimachine"))
130+
})
131+
})
140132
})
141133
})
134+
135+
func newClaim(name, kind, apiVersion string) ipamv1.IPAddressClaim {
136+
return ipamv1.IPAddressClaim{
137+
ObjectMeta: metav1.ObjectMeta{
138+
OwnerReferences: []metav1.OwnerReference{
139+
{
140+
Name: name,
141+
Kind: kind,
142+
APIVersion: apiVersion,
143+
},
144+
},
145+
},
146+
}
147+
}

0 commit comments

Comments
 (0)