...

Source file src/edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/nodepatcher/nodepatcher.go

Documentation: edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/nodepatcher

     1  package nodepatcher
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"slices"
     7  	"strings"
     8  
     9  	"maps"
    10  
    11  	corev1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"sigs.k8s.io/controller-runtime/pkg/client"
    14  
    15  	"k8s.io/apimachinery/pkg/util/validation"
    16  
    17  	"edge-infra.dev/pkg/edge/api/graph/model"
    18  	"edge-infra.dev/pkg/edge/capabilities"
    19  	"edge-infra.dev/pkg/k8s/runtime/controller/reconcile"
    20  	"edge-infra.dev/pkg/sds/controlplaneguardian/identifier"
    21  	v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
    22  	"edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config"
    23  	nodemeta "edge-infra.dev/pkg/sds/ien/node"
    24  )
    25  
    26  type NodePatcher struct{}
    27  
    28  // Updates the host node with labels and ownerReferences generated from the IEN Spec
    29  func (nodePatcherPlugin NodePatcher) Reconcile(ctx context.Context, ienode *v1ien.IENode, conf config.Config) (reconcile.Result, error) {
    30  	node, err := conf.GetHostNode(ctx)
    31  	if err != nil {
    32  		return reconcile.ResultRequeue, err
    33  	}
    34  
    35  	idf, err := identifier.FromClient(ctx, conf.GetClient())
    36  	if client.IgnoreNotFound(err) != nil {
    37  		return reconcile.ResultRequeue, err
    38  	}
    39  
    40  	nodeUpdated := node.DeepCopy()
    41  	if err := setNodeLabels(nodeUpdated, ienode, idf); err != nil {
    42  		return reconcile.ResultRequeue, err
    43  	}
    44  	setNodeOwnerReferences(nodeUpdated, ienode, idf)
    45  
    46  	if err := conf.GetClient().Patch(ctx, nodeUpdated, client.StrategicMergeFrom(node)); err != nil {
    47  		return reconcile.ResultRequeue, err
    48  	}
    49  	return reconcile.ResultSuccess, nil
    50  }
    51  
    52  // Sets the labels on the hostsystem node from the IEN Spec
    53  func setNodeLabels(node *corev1.Node, ienode *v1ien.IENode, idf *identifier.ControlPlaneIdentifier) error {
    54  	labels := node.GetLabels()
    55  	if err := validateCustomNodeLabels(ienode); err != nil {
    56  		return err
    57  	}
    58  	// delete custom and edge capability labels so only the current custom labels on the ienode cr
    59  	// are applied to the node https://github.com/ncr-swt-retail/edge-roadmap/issues/9425
    60  	maps.DeleteFunc(labels, func(key string, _ string) bool {
    61  		edgeCap := slices.ContainsFunc(capabilities.EdgeAutomatedCapabilityLabels, func(edgeLabel *model.LabelInput) bool {
    62  			return edgeLabel != nil && edgeLabel.Key == key
    63  		})
    64  		return strings.HasPrefix(key, v1ien.CustomNodePrefix) || edgeCap
    65  	})
    66  	maps.Copy(labels, ienode.CustomLabels())
    67  	addNodeLabels(ienode, labels, idf)
    68  	node.SetLabels(labels)
    69  	return nil
    70  }
    71  
    72  // Validate the custom labels on the hostsystem node from the IEN Spec
    73  func validateCustomNodeLabels(ienode *v1ien.IENode) error {
    74  	customLabels := ienode.CustomLabels()
    75  	for key, value := range customLabels {
    76  		keyErrs := validation.IsQualifiedName(key)
    77  		if len(keyErrs) > 0 {
    78  			return fmt.Errorf("Invalid label key %s: %s", key, strings.Join(keyErrs, "; "))
    79  		}
    80  		valueErrs := validation.IsValidLabelValue(value)
    81  		if len(valueErrs) > 0 {
    82  			return fmt.Errorf("Invalid label value %s: %s", value, strings.Join(valueErrs, "; "))
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  // Creates the node labels from the IEN spec to apply to the host node
    89  func addNodeLabels(ienode *v1ien.IENode, labels map[string]string, idf *identifier.ControlPlaneIdentifier) {
    90  	if ienode.GetName() != "" {
    91  		labels[nodemeta.HostnameLabel] = ienode.GetName()
    92  	}
    93  	if string(ienode.Spec.Class) != "" {
    94  		labels[nodemeta.ClassLabel] = string(ienode.Spec.Class)
    95  	}
    96  	if ienode.Spec.Lane != "" {
    97  		labels[nodemeta.LaneLabel] = ienode.Spec.Lane
    98  	}
    99  
   100  	role := getRole(ienode, idf)
   101  
   102  	if role != "" {
   103  		labels[nodemeta.RoleLabel] = role
   104  	}
   105  	// ensures the control plane nodes are marked
   106  	if role == string(v1ien.ControlPlane) {
   107  		labels[nodemeta.ControlPlaneLabel] = ""
   108  		delete(labels, nodemeta.WorkerClassLabel)
   109  	}
   110  	if role == string(v1ien.Worker) {
   111  		labels[nodemeta.WorkerClassLabel] = string(ienode.Spec.Class)
   112  		delete(labels, nodemeta.ControlPlaneLabel)
   113  	}
   114  }
   115  
   116  // getRole returns the role of the IENode based on the identifier. If the
   117  // identifier is nil, the role is taken from the IENode spec.
   118  func getRole(ienode *v1ien.IENode, idf *identifier.ControlPlaneIdentifier) string {
   119  	if idf == nil || idf.ControlPlane == "" {
   120  		return string(ienode.Spec.Role)
   121  	}
   122  	if idf.ControlPlane == ienode.GetName() {
   123  		return string(v1ien.ControlPlane)
   124  	}
   125  	return string(v1ien.Worker)
   126  }
   127  
   128  // Sets the ownerRefernces on k8s node to be owned by the IENode document. This only applies to worker nodes.
   129  func setNodeOwnerReferences(node *corev1.Node, ienode *v1ien.IENode, idf *identifier.ControlPlaneIdentifier) {
   130  	if getRole(ienode, idf) == string(v1ien.Worker) {
   131  		node.SetOwnerReferences([]metav1.OwnerReference{createOwnerReferenceToIEN(ienode)})
   132  		return
   133  	}
   134  	// remove old owner references on existing control plane nodes
   135  	ownerReferences := []metav1.OwnerReference{}
   136  	for _, or := range node.ObjectMeta.OwnerReferences {
   137  		if or.Kind == v1ien.IENodeGVK.Kind && or.Name == ienode.Name {
   138  			continue
   139  		}
   140  		ownerReferences = append(ownerReferences, or)
   141  	}
   142  	node.OwnerReferences = ownerReferences
   143  }
   144  
   145  func createOwnerReferenceToIEN(ienode *v1ien.IENode) metav1.OwnerReference {
   146  	return metav1.OwnerReference{
   147  		APIVersion: ienode.APIVersion,
   148  		Kind:       ienode.Kind,
   149  		Name:       ienode.GetName(),
   150  		UID:        ienode.GetUID(),
   151  	}
   152  }
   153  

View as plain text