Skip to content

Commit 56aa8be

Browse files
authored
Merge pull request kubernetes-sigs#7173 from enxebre/node-labels
✨ Add Node managed labels support
2 parents 41f7dda + 4bc8972 commit 56aa8be

File tree

6 files changed

+112
-25
lines changed

6 files changed

+112
-25
lines changed

api/v1beta1/machine_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ const (
6464
// This annotation can be set on BootstrapConfig or Machine objects. The value set on the Machine object takes precedence.
6565
// This annotation can only be used on Control Plane Machines.
6666
MachineCertificatesExpiryDateAnnotation = "machine.cluster.x-k8s.io/certificates-expiry"
67+
68+
// NodeRoleLabelPrefix is one of the CAPI managed Node label prefixes.
69+
NodeRoleLabelPrefix = "node-role.kubernetes.io"
70+
// NodeRestrictionLabelDomain is one of the CAPI managed Node label domains.
71+
NodeRestrictionLabelDomain = "node-restriction.kubernetes.io"
72+
// ManagedNodeLabelDomain is one of the CAPI managed Node label domains.
73+
ManagedNodeLabelDomain = "node.cluster.x-k8s.io"
6774
)
6875

6976
// ANCHOR: MachineSpec

internal/controllers/machine/machine_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ type Reconciler struct {
9191
// nodeDeletionRetryTimeout determines how long the controller will retry deleting a node
9292
// during a single reconciliation.
9393
nodeDeletionRetryTimeout time.Duration
94+
disableNodeLabelSync bool
9495
}
9596

9697
func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {

internal/controllers/machine/machine_controller_noderef.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package machine
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223

2324
"github.com/pkg/errors"
2425
corev1 "k8s.io/api/core/v1"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+
"k8s.io/apimachinery/pkg/types"
2629
"k8s.io/klog/v2"
2730
ctrl "sigs.k8s.io/controller-runtime"
2831
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -116,6 +119,17 @@ func (r *Reconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Clust
116119
}
117120
}
118121

122+
if !r.disableNodeLabelSync {
123+
options := []client.PatchOption{
124+
client.FieldOwner("capi-machine"),
125+
client.ForceOwnership,
126+
}
127+
nodePatch := unstructuredNode(node.Name, node.UID, getManagedLabels(machine.Labels))
128+
if err := remoteClient.Patch(ctx, nodePatch, client.Apply, options...); err != nil {
129+
return ctrl.Result{}, errors.Wrap(err, "failed to apply patch label to the node")
130+
}
131+
}
132+
119133
// Do the remaining node health checks, then set the node health to true if all checks pass.
120134
status, message := summarizeNodeConditions(node)
121135
if status == corev1.ConditionFalse {
@@ -131,6 +145,37 @@ func (r *Reconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Clust
131145
return ctrl.Result{}, nil
132146
}
133147

148+
// unstructuredNode returns a raw unstructured from Node input.
149+
func unstructuredNode(name string, uid types.UID, labels map[string]string) *unstructured.Unstructured {
150+
obj := &unstructured.Unstructured{}
151+
obj.SetAPIVersion("v1")
152+
obj.SetKind("Node")
153+
obj.SetName(name)
154+
obj.SetUID(uid)
155+
obj.SetLabels(labels)
156+
return obj
157+
}
158+
159+
// getManagedLabels gets a map[string]string and returns another map[string]string
160+
// filtering out labels not managed by CAPI.
161+
func getManagedLabels(labels map[string]string) map[string]string {
162+
managedLabels := make(map[string]string)
163+
for key, value := range labels {
164+
dnsSubdomainOrName := strings.Split(key, "/")[0]
165+
if dnsSubdomainOrName == clusterv1.NodeRoleLabelPrefix {
166+
managedLabels[key] = value
167+
}
168+
if dnsSubdomainOrName == clusterv1.NodeRestrictionLabelDomain || strings.HasSuffix(dnsSubdomainOrName, "."+clusterv1.NodeRestrictionLabelDomain) {
169+
managedLabels[key] = value
170+
}
171+
if dnsSubdomainOrName == clusterv1.ManagedNodeLabelDomain || strings.HasSuffix(dnsSubdomainOrName, "."+clusterv1.ManagedNodeLabelDomain) {
172+
managedLabels[key] = value
173+
}
174+
}
175+
176+
return managedLabels
177+
}
178+
134179
// summarizeNodeConditions summarizes a Node's conditions and returns the summary of condition statuses and concatenate failed condition messages:
135180
// if there is at least 1 semantically-negative condition, summarized status = False;
136181
// if there is at least 1 semantically-positive condition when there is 0 semantically negative condition, summarized status = True;

internal/controllers/machine/machine_controller_noderef_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,29 @@ func TestSummarizeNodeConditions(t *testing.T) {
227227
})
228228
}
229229
}
230+
231+
func TestGetManagedLabels(t *testing.T) {
232+
// Create managedLabels map from known managed prefixes.
233+
managedLabels := map[string]string{}
234+
managedLabels[clusterv1.ManagedNodeLabelDomain] = ""
235+
managedLabels["custom-prefix."+clusterv1.NodeRestrictionLabelDomain] = ""
236+
managedLabels["custom-prefix."+clusterv1.NodeRestrictionLabelDomain+"/anything"] = ""
237+
managedLabels[clusterv1.NodeRoleLabelPrefix+"/anything"] = ""
238+
239+
// Append arbitrary labels.
240+
allLabels := map[string]string{
241+
"foo": "",
242+
"bar": "",
243+
"company.xyz/node.cluster.x-k8s.io": "not-managed",
244+
"gpu-node.cluster.x-k8s.io": "not-managed",
245+
"company.xyz/node-restriction.kubernetes.io": "not-managed",
246+
"gpu-node-restriction.kubernetes.io": "not-managed",
247+
}
248+
for k, v := range managedLabels {
249+
allLabels[k] = v
250+
}
251+
252+
g := NewWithT(t)
253+
got := getManagedLabels(allLabels)
254+
g.Expect(got).To(BeEquivalentTo(managedLabels))
255+
}

internal/controllers/machine/machine_controller_phases_test.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func TestReconcileMachinePhases(t *testing.T) {
118118
infraConfig := defaultInfra.DeepCopy()
119119

120120
r := &Reconciler{
121+
disableNodeLabelSync: true,
121122
Client: fake.NewClientBuilder().
122123
WithScheme(scheme.Scheme).
123124
WithObjects(defaultCluster,
@@ -156,6 +157,7 @@ func TestReconcileMachinePhases(t *testing.T) {
156157
infraConfig := defaultInfra.DeepCopy()
157158

158159
r := &Reconciler{
160+
disableNodeLabelSync: true,
159161
Client: fake.NewClientBuilder().
160162
WithScheme(scheme.Scheme).
161163
WithObjects(defaultCluster,
@@ -199,6 +201,7 @@ func TestReconcileMachinePhases(t *testing.T) {
199201
machine.Status.LastUpdated = &lastUpdated
200202

201203
r := &Reconciler{
204+
disableNodeLabelSync: true,
202205
Client: fake.NewClientBuilder().
203206
WithScheme(scheme.Scheme).
204207
WithObjects(defaultCluster,
@@ -269,8 +272,7 @@ func TestReconcileMachinePhases(t *testing.T) {
269272

270273
node := &corev1.Node{
271274
ObjectMeta: metav1.ObjectMeta{
272-
Name: "machine-test-node",
273-
Namespace: metav1.NamespaceDefault,
275+
Name: "machine-test-node",
274276
},
275277
Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
276278
}
@@ -288,8 +290,9 @@ func TestReconcileMachinePhases(t *testing.T) {
288290
WithIndex(&corev1.Node{}, index.NodeProviderIDField, index.NodeByProviderID).
289291
Build()
290292
r := &Reconciler{
291-
Client: cl,
292-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
293+
disableNodeLabelSync: true,
294+
Client: cl,
295+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
293296
}
294297

295298
res, err := r.reconcile(ctx, defaultCluster, machine)
@@ -337,8 +340,7 @@ func TestReconcileMachinePhases(t *testing.T) {
337340

338341
node := &corev1.Node{
339342
ObjectMeta: metav1.ObjectMeta{
340-
Name: "machine-test-node",
341-
Namespace: metav1.NamespaceDefault,
343+
Name: "machine-test-node",
342344
},
343345
Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
344346
}
@@ -356,8 +358,9 @@ func TestReconcileMachinePhases(t *testing.T) {
356358
WithIndex(&corev1.Node{}, index.NodeProviderIDField, index.NodeByProviderID).
357359
Build()
358360
r := &Reconciler{
359-
Client: cl,
360-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
361+
disableNodeLabelSync: true,
362+
Client: cl,
363+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
361364
}
362365

363366
res, err := r.reconcile(ctx, defaultCluster, machine)
@@ -415,8 +418,7 @@ func TestReconcileMachinePhases(t *testing.T) {
415418
machine.Status.LastUpdated = &lastUpdated
416419
node := &corev1.Node{
417420
ObjectMeta: metav1.ObjectMeta{
418-
Name: "machine-test-node",
419-
Namespace: metav1.NamespaceDefault,
421+
Name: "machine-test-node",
420422
},
421423
Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
422424
}
@@ -434,8 +436,9 @@ func TestReconcileMachinePhases(t *testing.T) {
434436
WithIndex(&corev1.Node{}, index.NodeProviderIDField, index.NodeByProviderID).
435437
Build()
436438
r := &Reconciler{
437-
Client: cl,
438-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
439+
disableNodeLabelSync: true,
440+
Client: cl,
441+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
439442
}
440443

441444
res, err := r.reconcile(ctx, defaultCluster, machine)
@@ -496,8 +499,9 @@ func TestReconcileMachinePhases(t *testing.T) {
496499
Build()
497500

498501
r := &Reconciler{
499-
Client: cl,
500-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
502+
disableNodeLabelSync: true,
503+
Client: cl,
504+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
501505
}
502506

503507
res, err := r.reconcile(ctx, defaultCluster, machine)
@@ -577,9 +581,10 @@ func TestReconcileMachinePhases(t *testing.T) {
577581
infraConfig,
578582
).Build()
579583
r := &Reconciler{
580-
Client: cl,
581-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
582-
recorder: record.NewFakeRecorder(32),
584+
disableNodeLabelSync: true,
585+
Client: cl,
586+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
587+
recorder: record.NewFakeRecorder(32),
583588
}
584589

585590
res, err := r.reconcileDelete(ctx, defaultCluster, machine)
@@ -876,6 +881,7 @@ func TestReconcileBootstrap(t *testing.T) {
876881

877882
bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig}
878883
r := &Reconciler{
884+
disableNodeLabelSync: true,
879885
Client: fake.NewClientBuilder().
880886
WithObjects(tc.machine,
881887
builder.GenericBootstrapConfigCRD.DeepCopy(),
@@ -1086,6 +1092,7 @@ func TestReconcileInfrastructure(t *testing.T) {
10861092

10871093
infraConfig := &unstructured.Unstructured{Object: tc.infraConfig}
10881094
r := &Reconciler{
1095+
disableNodeLabelSync: true,
10891096
Client: fake.NewClientBuilder().
10901097
WithObjects(tc.machine,
10911098
builder.GenericBootstrapConfigCRD.DeepCopy(),
@@ -1327,6 +1334,7 @@ func TestReconcileCertificateExpiry(t *testing.T) {
13271334
g := NewWithT(t)
13281335

13291336
r := &Reconciler{
1337+
disableNodeLabelSync: true,
13301338
Client: fake.NewClientBuilder().
13311339
WithObjects(
13321340
tc.machine,

internal/controllers/machine/machine_controller_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,7 @@ func TestReconcileRequest(t *testing.T) {
616616

617617
node := &corev1.Node{
618618
ObjectMeta: metav1.ObjectMeta{
619-
Name: "test",
620-
Namespace: metav1.NamespaceDefault,
619+
Name: "test",
621620
},
622621
Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
623622
}
@@ -727,8 +726,9 @@ func TestReconcileRequest(t *testing.T) {
727726
).WithIndex(&corev1.Node{}, index.NodeProviderIDField, index.NodeByProviderID).Build()
728727

729728
r := &Reconciler{
730-
Client: clientFake,
731-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
729+
disableNodeLabelSync: true,
730+
Client: clientFake,
731+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
732732
}
733733

734734
result, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: util.ObjectKey(&tc.machine)})
@@ -831,8 +831,7 @@ func TestMachineConditions(t *testing.T) {
831831

832832
node := &corev1.Node{
833833
ObjectMeta: metav1.ObjectMeta{
834-
Name: "test",
835-
Namespace: metav1.NamespaceDefault,
834+
Name: "test",
836835
},
837836
Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
838837
}
@@ -974,8 +973,9 @@ func TestMachineConditions(t *testing.T) {
974973
Build()
975974

976975
r := &Reconciler{
977-
Client: clientFake,
978-
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
976+
disableNodeLabelSync: true,
977+
Client: clientFake,
978+
Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
979979
}
980980

981981
_, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: util.ObjectKey(&machine)})

0 commit comments

Comments
 (0)