Skip to content

Commit

Permalink
Add support to reconcile labels from Machines to Nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
enxebre committed Sep 27, 2022
1 parent f784f1e commit 5cc2e73
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 1 deletion.
16 changes: 16 additions & 0 deletions api/v1beta1/machine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ const (
// This annotation can be set on BootstrapConfig or Machine objects. The value set on the Machine object takes precedence.
// This annotation can only be used on Control Plane Machines.
MachineCertificatesExpiryDateAnnotation = "machine.cluster.x-k8s.io/certificates-expiry"

// NodeRoleLabelPrefix is one of the CAPI managed prefix Node labels.
NodeRoleLabelPrefix = "node-role.kubernetes.io/"
// NodeRestrictionLabelPrefix is one of the CAPI managed prefix Node labels.
NodeRestrictionLabelPrefix = "node-restriction.kubernetes.io/"
// ManagedNodeLabelPrefix is one of the CAPI managed prefix Node labels.
ManagedNodeLabelPrefix = "node.cluster.x-k8s.io/"
)

var (
// ManagedNodeLabelPrefixes is a slice with all the CAPI managed prefix Node labels.
ManagedNodeLabelPrefixes = []string{
NodeRoleLabelPrefix,
NodeRestrictionLabelPrefix,
ManagedNodeLabelPrefix,
}
)

// ANCHOR: MachineSpec
Expand Down
51 changes: 50 additions & 1 deletion internal/controllers/machine/machine_controller_noderef.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package machine
import (
"context"
"fmt"
"strings"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -109,13 +110,21 @@ func (r *Reconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Clust
desired[clusterv1.OwnerKindAnnotation] = owner.Kind
desired[clusterv1.OwnerNameAnnotation] = owner.Name
}
if annotations.AddAnnotations(node, desired) {
annotationsHaveChanged := annotations.AddAnnotations(node, desired)
if annotationsHaveChanged {
if err := patchHelper.Patch(ctx, node); err != nil {
log.V(2).Info("Failed patch node to set annotations", "err", err, "node name", node.Name)
return ctrl.Result{}, err
}
}

if reconcileManagedLabels(machine, node) {
options := []client.PatchOption{
client.FieldOwner("MachineController"),
}
return ctrl.Result{}, remoteClient.Patch(ctx, node, client.Apply, options...)
}

// Do the remaining node health checks, then set the node health to true if all checks pass.
status, message := summarizeNodeConditions(node)
if status == corev1.ConditionFalse {
Expand All @@ -131,6 +140,46 @@ func (r *Reconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Clust
return ctrl.Result{}, nil
}

func reconcileManagedLabels(machine *clusterv1.Machine, node *corev1.Node) bool {
// Reconcile managed Labels.
var labelsHaveChanged bool
desiredLabels := getManagedLabels(machine.Labels)
currentNodeLabels := getManagedLabels(node.Labels)

// Reconcile desired labels.
for desiredKey, desiredValue := range desiredLabels {
if currentValue, ok := currentNodeLabels[desiredKey]; !ok || desiredValue != currentValue {
node.Labels[desiredKey] = desiredValue
labelsHaveChanged = true
}
}

// Delete current labels which are not desired anymore.
for currentKey := range currentNodeLabels {
if _, ok := desiredLabels[currentKey]; !ok {
labelsHaveChanged = true
delete(node.Labels, currentKey)
}
}

return labelsHaveChanged
}

// getManagedLabels gets a map[string]string and returns another map[string]string
// filtering out labels not managed by CAPI.
func getManagedLabels(labels map[string]string) map[string]string {
managedLabels := make(map[string]string)
for key, value := range labels {
for _, managedPrefix := range clusterv1.ManagedNodeLabelPrefixes {
if strings.Contains(key, managedPrefix) {
managedLabels[key] = value
}
}
}

return managedLabels
}

// summarizeNodeConditions summarizes a Node's conditions and returns the summary of condition statuses and concatenate failed condition messages:
// if there is at least 1 semantically-negative condition, summarized status = False;
// if there is at least 1 semantically-positive condition when there is 0 semantically negative condition, summarized status = True;
Expand Down
129 changes: 129 additions & 0 deletions internal/controllers/machine/machine_controller_noderef_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,132 @@ func TestSummarizeNodeConditions(t *testing.T) {
})
}
}

func TestGetManagedLabels(t *testing.T) {
// Create managedLabels map from known managed prefixes.
managedLabels := map[string]string{}
for _, prefix := range clusterv1.ManagedNodeLabelPrefixes {
managedLabels[prefix] = ""
}
managedLabels["custom-prefix."+clusterv1.NodeRestrictionLabelPrefix] = ""

// Append arbitrary labels.
allLabels := map[string]string{
"foo": "",
"bar": "",
}
for k, v := range managedLabels {
allLabels[k] = v
}

g := NewWithT(t)
got := getManagedLabels(allLabels)
g.Expect(got).To(BeEquivalentTo(managedLabels))
}

func TestReconcileManagedLabels(t *testing.T) {
allLabels := map[string]string{
"foo": "",
"bar": "",
}
for _, prefix := range clusterv1.ManagedNodeLabelPrefixes {
allLabels[prefix] = ""
}

testCases := []struct {
name string
node *corev1.Node
machine *clusterv1.Machine
expectedLabels map[string]string
expected bool
}{
{
name: "when Node is missing have wanted managed labels they should be added",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "A",
clusterv1.ManagedNodeLabelPrefix: "anything",
},
},
},
machine: &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
clusterv1.NodeRoleLabelPrefix: "",
clusterv1.NodeRestrictionLabelPrefix: "",
clusterv1.ManagedNodeLabelPrefix: "stamp",
"foo": "B",
},
},
},
expectedLabels: map[string]string{
clusterv1.NodeRoleLabelPrefix: "",
clusterv1.NodeRestrictionLabelPrefix: "",
clusterv1.ManagedNodeLabelPrefix: "stamp",
"foo": "A",
},
expected: true,
},
{
name: "when Node is have unwanted managed labels they should be removed",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
clusterv1.NodeRestrictionLabelPrefix: "anything",
clusterv1.ManagedNodeLabelPrefix: "anything",
"foo": "A",
},
},
},
machine: &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
clusterv1.NodeRoleLabelPrefix: "anything",
"foo": "B",
},
},
},
expectedLabels: map[string]string{
clusterv1.NodeRoleLabelPrefix: "anything",
"foo": "A",
},
expected: true,
},
{
name: "when all managed labels match nothing is needed",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
clusterv1.NodeRestrictionLabelPrefix: "anything",
clusterv1.ManagedNodeLabelPrefix: "anything",
"foo": "A",
},
},
},
machine: &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
clusterv1.NodeRestrictionLabelPrefix: "anything",
clusterv1.ManagedNodeLabelPrefix: "anything",
"foo": "B",
},
},
},
expectedLabels: map[string]string{
clusterv1.NodeRestrictionLabelPrefix: "anything",
clusterv1.ManagedNodeLabelPrefix: "anything",
"foo": "A",
},
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
needsUpdated := reconcileManagedLabels(tc.machine, tc.node)
g.Expect(tc.expected).To(Equal(needsUpdated))
g.Expect(tc.expectedLabels).To(BeEquivalentTo(tc.node.Labels))
})
}
}

0 comments on commit 5cc2e73

Please sign in to comment.