diff --git a/src/kubernetes-api/internal/controller/l2network_controller.go b/src/kubernetes-api/internal/controller/l2network_controller.go index db14b777c2e6f98b9a4d456da0db372e7efc5099..5915f860dc28019e5502e14ca86ba371eec2b8bb 100644 --- a/src/kubernetes-api/internal/controller/l2network_controller.go +++ b/src/kubernetes-api/internal/controller/l2network_controller.go @@ -72,17 +72,19 @@ func (r *L2NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // Check if the object is being deleted if network.GetDeletionTimestamp() != nil { - if utils.ContainsString(network.GetFinalizers(), "l2network.finalizers.l2sm.k8s.local") { + if utils.ContainsString(network.GetFinalizers(), l2smFinalizer) { // The object is being deleted if err := r.InternalClient.DeleteNetwork(network.Spec.Type, network.Name); err != nil { // If fail to delete the external dependency here, return with error // so that it can be retried + log.Error(err, "couldn't delete network in sdn controller") return ctrl.Result{}, err } // Remove our finalizer from the list and update it. - network.SetFinalizers(utils.RemoveString(network.GetFinalizers(), "l2network.finalizers.l2sm.k8s.local")) + network.SetFinalizers(utils.RemoveString(network.GetFinalizers(), l2smFinalizer)) if err := r.Update(ctx, network); err != nil { + log.Error(err, "couldn't remove finalizer to l2network") return ctrl.Result{}, err } } @@ -92,7 +94,7 @@ func (r *L2NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Add finalizer for this CR - if !utils.ContainsString(network.GetFinalizers(), "l2network.finalizers.l2sm.k8s.local") { + if !utils.ContainsString(network.GetFinalizers(), l2smFinalizer) { err := r.InternalClient.CreateNetwork(network.Spec.Type, sdnclient.VnetPayload{NetworkId: network.Name}) if err != nil { log.Error(err, "failed to create network") @@ -102,13 +104,14 @@ func (r *L2NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } log.Info("Network created in SDN controller", "NetworkID", network.Name) r.updateControllerStatus(ctx, network, l2smv1.OnlineStatus) - network.SetFinalizers(append(network.GetFinalizers(), "l2network.finalizers.l2sm.k8s.local")) + network.SetFinalizers(append(network.GetFinalizers(), l2smFinalizer)) if err := r.Update(ctx, network); err != nil { return ctrl.Result{}, err } } - if network.Spec.Type == l2smv1.NetworkTypeExtVnet { + // If network is inter domain + if network.Spec.Provider != nil { provStatus, err := interDomainReconcile(network, log) if err != nil { log.Error(err, "failed to connect to provider") @@ -149,6 +152,7 @@ func (r *L2NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // return ctrl.Result{}, statusUpdateErr // } + log.Info("something in the rain") return ctrl.Result{}, nil } diff --git a/src/kubernetes-api/internal/controller/networkedgedevice_controller.go b/src/kubernetes-api/internal/controller/networkedgedevice_controller.go index a04cc6d4c5e0f5951e0a5ba91ffa15feab229887..ad1a673001e8f82eed9818aa785d7a97dd64e86b 100644 --- a/src/kubernetes-api/internal/controller/networkedgedevice_controller.go +++ b/src/kubernetes-api/internal/controller/networkedgedevice_controller.go @@ -41,6 +41,8 @@ type NetworkEdgeDeviceReconciler struct { } var ( + // name of our custom finalizer + l2smFinalizer = "l2sm.operator.io/finalizer" replicaSetOwnerKey = ".metadata.controller" apiGVStr = l2smv1.GroupVersion.String() ) @@ -73,9 +75,6 @@ func (r *NetworkEdgeDeviceReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, client.IgnoreNotFound(err) } - // name of our custom finalizer - l2smFinalizer := "l2sm.operator.io/finalizer" - // examine DeletionTimestamp to determine if object is under deletion if netEdgeDevice.ObjectMeta.DeletionTimestamp.IsZero() { // The object is not being deleted, so if it does not have our finalizer, diff --git a/src/kubernetes-api/internal/controller/overlay_controller.go b/src/kubernetes-api/internal/controller/overlay_controller.go index da2ac7c405439f5d2e8aa6e64e5269a8f224847c..824ab627cf3d751c4db57971ed58323881245c91 100644 --- a/src/kubernetes-api/internal/controller/overlay_controller.go +++ b/src/kubernetes-api/internal/controller/overlay_controller.go @@ -71,7 +71,6 @@ func (r *OverlayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // name of our custom finalizer - l2smFinalizer := "l2sm.operator.io/finalizer" // examine DeletionTimestamp to determine if object is under deletion if overlay.ObjectMeta.DeletionTimestamp.IsZero() { diff --git a/src/kubernetes-api/internal/controller/pod_controller.go b/src/kubernetes-api/internal/controller/pod_controller.go index d7f6f2248b0f181e7a439b40ce7146c871c50f91..18897347f9f70e1611527a9b399add7bfd0506f5 100644 --- a/src/kubernetes-api/internal/controller/pod_controller.go +++ b/src/kubernetes-api/internal/controller/pod_controller.go @@ -18,17 +18,20 @@ package controller import ( "context" + "errors" "fmt" "os" "github.com/go-logr/logr" - nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" l2smv1 "l2sm.k8s.local/controllermanager/api/v1" + "l2sm.k8s.local/controllermanager/internal/nedinterface" "l2sm.k8s.local/controllermanager/internal/sdnclient" + "l2sm.k8s.local/controllermanager/internal/utils" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" ) // PodReconciler reconciles a Pod object @@ -55,7 +58,7 @@ type PodReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - // logger := log.FromContext(ctx) + logger := log.FromContext(ctx) pod := &corev1.Pod{} err := r.Get(ctx, req.NamespacedName, pod) @@ -73,100 +76,117 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R // Ensure the Multus annotation is correctly formatted and present // Check if the object is being deleted - // if pod.GetDeletionTimestamp() != nil { - // if utils.ContainsString(pod.GetFinalizers(), "pod.finalizers.l2sm.k8s.local") { - // // If the pod is being deleted, we should free the interface, both the net-attach-def crd and the openflow port. - // // This is done for each interface in the pod. - // multusAnnotations, ok := pod.Annotations[MULTUS_ANNOTATION_KEY] - - // if !ok { - // logger.Error(nil, "Error detaching the pod from the network attachment definitions") - // return ctrl.Result{}, nil - // } - - // multusNetAttachDefinitions, err := extractNetworks(pod.Annotations[multusAnnotations], r.SwitchesNamespace) - - // if err != nil { - // logger.Error(nil, "Error detaching the pod from the network attachment definitions") - // return ctrl.Result{}, nil - // } - - // for _, multusNetAttachDef := range multusNetAttachDefinitions { - - // // We liberate the specific attachment from the node, so it can be used again - // r.DetachNetAttachDef(ctx, multusNetAttachDef, r.SwitchesNamespace) - - // // We liberate the port in the onos app - // // r.InternalClient.DetachPodFromNetwork("vnet",multusNetAttachDef) - // } - - // // Remove our finalizer from the list and update it. - // pod.SetFinalizers(utils.RemoveString(pod.GetFinalizers(), "pod.finalizers.l2sm.k8s.local")) - // if err := r.Update(ctx, pod); err != nil { - // return ctrl.Result{}, err - // } - // // Stop reconciliation as the item is being deleted - // return ctrl.Result{}, nil - // } - - // // If it's not getting deleted, then let's check if it's been created or not - // // Add finalizer for this CR - // if !utils.ContainsString(pod.GetFinalizers(), "pod.finalizers.l2sm.k8s.local") { - - // networks, err := extractNetworks(pod.Annotations[L2SM_NETWORK_ANNOTATION], r.SwitchesNamespace) + if pod.GetDeletionTimestamp() != nil { + if utils.ContainsString(pod.GetFinalizers(), l2smFinalizer) { + logger.Info("L2S-M Pod deleted: detaching l2network") + + // If the pod is being deleted, we should free the interface, both the net-attach-def crd and the openflow port. + // This is done for each interface in the pod. + multusAnnotations, ok := pod.Annotations[MULTUS_ANNOTATION_KEY] + + if !ok { + logger.Error(nil, "Error detaching the pod from the network attachment definitions") + return ctrl.Result{}, nil + } + + multusNetAttachDefinitions, err := extractNetworks(pod.Annotations[multusAnnotations], r.SwitchesNamespace) + + if err != nil { + logger.Error(nil, "Error detaching the pod from the network attachment definitions") + return ctrl.Result{}, nil + } + + for _, multusNetAttachDef := range multusNetAttachDefinitions { + + fmt.Println(multusNetAttachDef) + // We liberate the specific attachment from the node, so it can be used again + //r.DetachNetAttachDef(ctx, multusNetAttachDef, r.SwitchesNamespace) + + // We liberate the port in the onos app + //r.InternalClient.DetachPodFromNetwork("vnet",multusNetAttachDef) + } + + // Remove our finalizer from the list and update it. + pod.SetFinalizers(utils.RemoveString(pod.GetFinalizers(), l2smFinalizer)) + if err := r.Update(ctx, pod); err != nil { + return ctrl.Result{}, err + } + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } - // if created, _ := r.verifyNetworksAreCreated(ctx, networks); !created { + // If it's not getting deleted, then let's check if it's been created or not + // Add finalizer for this CR + if !utils.ContainsString(pod.GetFinalizers(), l2smFinalizer) { + // We add the finalizers now that the pod has been added to the network and we want to keep track of it + pod.SetFinalizers(append(pod.GetFinalizers(), l2smFinalizer)) + if err := r.Update(ctx, pod); err != nil { + return ctrl.Result{}, err + } + logger.Info("L2S-M Pod created: attaching to l2network") - // logger.Error(nil, "Pod's network annotation incorrect. L2Network not attached.") - // // return admission.Allowed("Pod's network annotation incorrect. L2Network not attached.") - // return ctrl.Result{}, err + networkAnnotations, err := extractNetworks(pod.Annotations[L2SM_NETWORK_ANNOTATION], r.SwitchesNamespace) - // } - // // Add the pod interfaces to the sdn controller - // multusAnnotations, ok := pod.Annotations[MULTUS_ANNOTATION_KEY] + if err != nil { + logger.Error(err, "l2 networks could not be extracted from the pods annotations") + } + networks, err := GetL2Networks(ctx, r.Client, networkAnnotations) + if err != nil { - // if !ok { - // logger.Error(nil, "Error detaching the pod from the network attachment definitions") - // return ctrl.Result{}, nil - // } + logger.Error(nil, "Pod's network annotation incorrect. L2Network not attached.") + // return admission.Allowed("Pod's network annotation incorrect. L2Network not attached.") + return ctrl.Result{}, err - // multusNetAttachDefinitions, err := extractNetworks(pod.Annotations[multusAnnotations], r.SwitchesNamespace) + } + // Add the pod interfaces to the sdn controller + multusAnnotations, ok := pod.Annotations[MULTUS_ANNOTATION_KEY] - // if err != nil { - // logger.Error(nil, "Error detaching the pod from the network attachment definitions") - // return ctrl.Result{}, nil - // } + if !ok { + logger.Error(nil, "Error detaching the pod from the network attachment definitions") + return ctrl.Result{}, nil + } - // fmt.Println(multusNetAttachDefinitions) + multusNetAttachDefinitions, err := extractNetworks(multusAnnotations, r.SwitchesNamespace) - // // ofID := r.GetOpenflowId(ctx, pod.Spec.NodeName) + if err != nil || len(multusNetAttachDefinitions) != len(networkAnnotations) { + logger.Error(nil, "Error detaching the pod from the network attachment definitions") + return ctrl.Result{}, nil + } - // // for index, network := range networks { + ofID := fmt.Sprintf("of:%s", utils.GenerateDatapathID(pod.Spec.NodeName)) - // // portNumber, _ := utils.GetPortNumberFromNetAttachDef(multusNetAttachDefinitions[index].Name) - // // ofPort := fmt.Sprintf("%s/%s", ofID, portNumber) + for index, network := range networks { + portNumber, _ := utils.GetPortNumberFromNetAttachDef(multusNetAttachDefinitions[index].Name) + ofPort := fmt.Sprintf("%s/%s", ofID, portNumber) - // // r.InternalClient.AttachPodToNetwork("vnets", sdnclient.VnetPortPayload{NetworkId: network.Name, Port: []string{ofPort}}) - // // } + err = r.InternalClient.AttachPodToNetwork("vnets", sdnclient.VnetPortPayload{NetworkId: network.Name, Port: []string{ofPort}}) + if err != nil { + logger.Error(err, "Error attaching pod to the l2network") + return ctrl.Result{}, nil + } - // // We add the finalizers now that the pod has been added to the network and we want to keep track of it - // pod.SetFinalizers(append(pod.GetFinalizers(), "pod.finalizers.l2sm.k8s.local")) - // if err := r.Update(ctx, pod); err != nil { - // return ctrl.Result{}, err - // } - // } + // If the L2Network is of type inter-domain (has a provider), attach the associated NED + // and communicate with it + if network.Spec.Provider != nil { - // } + gatewayNodeName, err := r.CreateNewNEDConnection(network) + if err != nil { + return ctrl.Result{}, nil + } - // r.GetOpenflowId(ctx,pod.Spec.NodeName) + err = r.ConnectGatewaySwitchToNED(ctx, network.Name, gatewayNodeName) + if err != nil { + return ctrl.Result{}, nil + } - // for _, network := range networks { + } + } - // ofPort := fmt.Sprintf("%s/%s",ofID,portNumber) - // sdnclient.VnetPortPayload{NetworkId: network.Name, Port: ofPort} + } - // r.InternalClient.AttachPodToNetwork() - // } return ctrl.Result{}, nil } @@ -191,92 +211,63 @@ func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *PodReconciler) verifyNetworksAreCreated(ctx context.Context, networks []NetworkAnnotation) (bool, error) { - // List all L2Networks - l2Networks := &l2smv1.L2NetworkList{} - if err := r.List(ctx, l2Networks); err != nil { - return false, err +func (r *PodReconciler) CreateNewNEDConnection(network l2smv1.L2Network) (string, error) { + + clientConfig := sdnclient.ClientConfig{BaseURL: fmt.Sprintf("http://%s/onos/v1", network.Spec.Provider.Domain), Username: "karaf", Password: "karaf"} + + externalClient, err := sdnclient.NewClient(sdnclient.InternalType, clientConfig) + + if err != nil { + return "", err + // logger.Error(err, "no connection could be made with external sdn controller") } + gatewayNodeName, nedPortNumber := nedinterface.GetConnectionInfo() + + nedOFID := fmt.Sprintf("of:%s", utils.GenerateDatapathID(fmt.Sprintf("%s%s", gatewayNodeName, network.Spec.Provider.Name))) + nedOFPort := fmt.Sprintf("%s/%s", nedOFID, nedPortNumber) + + err = externalClient.AttachPodToNetwork("vnets", sdnclient.VnetPortPayload{NetworkId: network.Name, Port: []string{nedOFPort}}) + if err != nil { + return "", errors.Join(err, errors.New("could not update network attachment definition")) - // Create a map of existing L2Network names for quick lookup - existingNetworks := make(map[string]struct{}) - for _, network := range l2Networks.Items { - existingNetworks[network.Name] = struct{}{} } + return gatewayNodeName, nil +} - // Verify if each annotated network exists - for _, net := range networks { - if _, exists := existingNetworks[net.Name]; !exists { - return false, nil - } +func (r *PodReconciler) ConnectGatewaySwitchToNED(ctx context.Context, networkName, gatewayNodeName string) error { + + var err error + netAttachDefLabel := NET_ATTACH_LABEL_PREFIX + gatewayNodeName + netAttachDefs := GetFreeNetAttachDefs(ctx, r.Client, r.SwitchesNamespace, netAttachDefLabel) + if len(netAttachDefs.Items) == 0 { + err = errors.New("no interfaces available in control plane node") + //logger.Error(err, fmt.Sprintf("No interfaces available for node %s", gatewayNodeName)) + return err } - return true, nil -} + netAttachDef := &netAttachDefs.Items[0] + + portNumber, _ := utils.GetPortNumberFromNetAttachDef(netAttachDef.Name) + + gatewayOFID := fmt.Sprintf("of:%s", utils.GenerateDatapathID(gatewayNodeName)) + + gatewayOFPort := fmt.Sprintf("%s/%s", gatewayOFID, portNumber) -func (r *PodReconciler) DetachNetAttachDef(ctx context.Context, multusNetAttachDef NetworkAnnotation, namespace string) error { + err = r.InternalClient.AttachPodToNetwork("vnets", sdnclient.VnetPortPayload{NetworkId: networkName, Port: []string{gatewayOFPort}}) - netAttachDef := &nettypes.NetworkAttachmentDefinition{} - err := r.Get(ctx, client.ObjectKey{ - Name: multusNetAttachDef.Name, - Namespace: namespace, - }, netAttachDef) if err != nil { return err + //logger.Error(err, "could not make a connection between the gateway switch and the NED. Internal SDN controller error.") } - err = r.Delete(ctx, netAttachDef) - return err -} + netAttachDef.Labels[netAttachDefLabel] = "true" + err = r.Client.Update(ctx, netAttachDef) + if err != nil { + return err + //.Error(err, "Could not update network attachment definition") + + } -func (r *PodReconciler) GetOpenflowId(ctx context.Context, nodename string) (string, error) { - return "", nil - // // Define the list options with the namespace - // listOptions := &client.ListOptions{ - // Namespace: r.SwitchesNamespace, - // } - - // // List all pods in the namespace - // podList := &corev1.PodList{} - // if err := r.List(ctx, podList, listOptions); err != nil { - // return "", fmt.Errorf("failed to list pods: %w", err) - // } - - // // Iterate through the pods to find the one on the specified node - // var switchPod *corev1.Pod - // for _, pod := range podList.Items { - // if pod.Spec.NodeName == nodename { - // switchPod = &pod - // break - // } - // } - - // if switchPod == nil { - // return "", fmt.Errorf("pod not found on node: %s", nodename) - // } - - // // Check if the OpenFlow ID is already annotated - // if openflowID, exists := switchPod.Annotations["openflow-id"]; exists && openflowID != "" { - // return openflowID, nil - // } - - // // Retrieve the OpenFlow ID (replace this with your actual logic) - // openflowID, err := r.InternalClient.RetrieveOpenflowID(switchPod.) - // if err != nil { - // return "", fmt.Errorf("failed to retrieve openflow id: %w", err) - // } - - // // Annotate the pod with the OpenFlow ID - // if switchPod.Annotations == nil { - // switchPod.Annotations = make(map[string]string) - // } - // switchPod.Annotations["openflow-id"] = openflowID - - // // Update the pod with the new annotation - // if err := r.Update(ctx, switchPod); err != nil { - // return "", fmt.Errorf("failed to update pod with openflow id: %w", err) - // } - - // return openflowID, nil + return nil } diff --git a/src/kubernetes-api/internal/controller/pod_utils.go b/src/kubernetes-api/internal/controller/pod_utils.go new file mode 100644 index 0000000000000000000000000000000000000000..50796fb2d068667edf0820b58da4f39ee05d747f --- /dev/null +++ b/src/kubernetes-api/internal/controller/pod_utils.go @@ -0,0 +1,142 @@ +package controller + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "strings" + "time" + + nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + l2smv1 "l2sm.k8s.local/controllermanager/api/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + NET_ATTACH_LABEL_PREFIX = "used-" + L2SM_NETWORK_ANNOTATION = "l2sm/networks" + MULTUS_ANNOTATION_KEY = "k8s.v1.cni.cncf.io/networks" +) + +type NetworkAnnotation struct { + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` + IPAdresses []string `json:"ips,omitempty"` +} + +func extractNetworks(annotations, namespace string) ([]NetworkAnnotation, error) { + + var networks []NetworkAnnotation + err := json.Unmarshal([]byte(annotations), &networks) + if err != nil { + // If unmarshalling fails, treat as comma-separated list + names := strings.Split(annotations, ",") + + for _, name := range names { + name = strings.TrimSpace(name) + if name != "" { + networks = append(networks, NetworkAnnotation{Name: name}) + } + } + } + + // Iterate over the networks to check if any IPAddresses are missing + for i := range networks { + if len(networks[i].IPAdresses) == 0 { + // Call GenerateIPv6Address if IPAddresses are missing + networks[i].GenerateIPv6Address() + } + networks[i].Namespace = namespace + } + return networks, nil +} + +func GetL2Networks(ctx context.Context, c client.Client, networks []NetworkAnnotation) ([]l2smv1.L2Network, error) { + // List all L2Networks + l2Networks := &l2smv1.L2NetworkList{} + if err := c.List(ctx, l2Networks); err != nil { + return []l2smv1.L2Network{}, err + } + + // Create a map of existing L2Network names to L2Network objects for quick lookup + existingNetworks := make(map[string]l2smv1.L2Network) + for _, network := range l2Networks.Items { + existingNetworks[network.Name] = network + } + + // Collect the L2Networks that match the requested networks + var result l2smv1.L2NetworkList + for _, net := range networks { + if l2net, exists := existingNetworks[net.Name]; exists { + result.Items = append(result.Items, l2net) + } else { + return result.Items, fmt.Errorf("network %s doesn't exist", net.Name) + } + } + + return result.Items, nil +} + +func GetFreeNetAttachDefs(ctx context.Context, c client.Client, switchesNamespace, label string) nettypes.NetworkAttachmentDefinitionList { + + // We define the network attachment definition list that will be later filled. + freeNetAttachDef := &nettypes.NetworkAttachmentDefinitionList{} + + // We specify which net-attach-def we want. We want the ones that are specific to l2sm, in the overlay namespace and available in the desired node. + nodeSelector := labels.NewSelector() + + nodeRequirement, _ := labels.NewRequirement(label, selection.NotIn, []string{"true"}) + l2smRequirement, _ := labels.NewRequirement("app", selection.Equals, []string{"l2sm"}) + + nodeSelector.Add(*nodeRequirement) + nodeSelector.Add(*l2smRequirement) + + listOptions := client.ListOptions{LabelSelector: nodeSelector, Namespace: switchesNamespace} + + // We get the net-attach-def with the corresponding list options + c.List(ctx, freeNetAttachDef, &listOptions) + return *freeNetAttachDef + +} + +func (network *NetworkAnnotation) GenerateIPv6Address() { + rand.Seed(time.Now().UnixNano()) + + // Generating the interface ID (64 bits) + interfaceID := rand.Uint64() + + // Formatting to a 16 character hexadecimal string + interfaceIDHex := fmt.Sprintf("%016x", interfaceID) + + // Constructing the full IPv6 address in the fe80::/64 range + ipv6Address := fmt.Sprintf("fe80::%s:%s:%s:%s/64", + interfaceIDHex[:4], interfaceIDHex[4:8], interfaceIDHex[8:12], interfaceIDHex[12:]) + + network.IPAdresses = append(network.IPAdresses, ipv6Address) + +} +func multusAnnotationToString(multusAnnotations []NetworkAnnotation) string { + jsonData, err := json.Marshal(multusAnnotations) + if err != nil { + return "" + } + return string(jsonData) +} + +func (r *PodReconciler) DetachNetAttachDef(ctx context.Context, multusNetAttachDef NetworkAnnotation, namespace string) error { + + netAttachDef := &nettypes.NetworkAttachmentDefinition{} + err := r.Get(ctx, client.ObjectKey{ + Name: multusNetAttachDef.Name, + Namespace: namespace, + }, netAttachDef) + if err != nil { + return err + } + err = r.Delete(ctx, netAttachDef) + return err + +} diff --git a/src/kubernetes-api/internal/controller/pod_webhook.go b/src/kubernetes-api/internal/controller/pod_webhook.go index 0a7f3fca15d49baf2ef62ebcc0e876d0c20eaf2d..40261118faa40d72fa77d06cbfc1dbc147ab1566 100644 --- a/src/kubernetes-api/internal/controller/pod_webhook.go +++ b/src/kubernetes-api/internal/controller/pod_webhook.go @@ -4,28 +4,15 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "net/http" - "strings" - "time" - nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "sigs.k8s.io/controller-runtime/pkg/log" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - l2smv1 "l2sm.k8s.local/controllermanager/api/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -const ( - NET_ATTACH_LABEL_PREFIX = "used-" - L2SM_NETWORK_ANNOTATION = "l2sm/networks" - MULTUS_ANNOTATION_KEY = "k8s.v1.cni.cncf.io/networks" -) - // +kubebuilder:webhook:path=/mutate-v1-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io type PodAnnotator struct { Client client.Client @@ -33,12 +20,6 @@ type PodAnnotator struct { SwitchesNamespace string } -type NetworkAnnotation struct { - Name string `json:"name"` - Namespace string `json:"namespace,omitempty"` - IPAdresses []string `json:"ips,omitempty"` -} - func (a *PodAnnotator) Handle(ctx context.Context, req admission.Request) admission.Response { log := log.FromContext(ctx) log.Info("Registering pod") @@ -51,7 +32,7 @@ func (a *PodAnnotator) Handle(ctx context.Context, req admission.Request) admiss return admission.Errored(http.StatusBadRequest, err) } if pod.Spec.NodeName == "" { - return admission.Errored(http.StatusBadRequest, fmt.Errorf("Pod hasn't got a node assigned to it")) + return admission.Errored(http.StatusBadRequest, fmt.Errorf("pod hasn't got a node assigned to it")) } // Check if the pod has the annotation l2sm/networks. This webhook operation only will happen if so. Else, it will just @@ -69,7 +50,7 @@ func (a *PodAnnotator) Handle(ctx context.Context, req admission.Request) admiss return admission.Errored(http.StatusInternalServerError, err) } - if created, _ := a.verifyNetworksAreCreated(ctx, networks); !created { + if _, err := GetL2Networks(ctx, a.Client, networks); err != nil { log.Info("Pod's network annotation incorrect. L2Network not attached.") // return admission.Allowed("Pod's network annotation incorrect. L2Network not attached.") @@ -78,7 +59,7 @@ func (a *PodAnnotator) Handle(ctx context.Context, req admission.Request) admiss // We get the available network attachment definitions. These are interfaces attached to the switches, so // by using labelling, we can know which interfaces the switch has. var multusAnnotations []NetworkAnnotation - netAttachDefs := a.getFreeNetAttachDefs(ctx, netAttachDefLabel) + netAttachDefs := GetFreeNetAttachDefs(ctx, a.Client, a.SwitchesNamespace, netAttachDefLabel) // If there are no available network attachment definitions, we can't attach the pod to the desired networks // So, we launch an error. @@ -123,100 +104,3 @@ func (a *PodAnnotator) InjectDecoder(d *admission.Decoder) error { a.Decoder = d return nil } - -func extractNetworks(annotations, namespace string) ([]NetworkAnnotation, error) { - - var networks []NetworkAnnotation - err := json.Unmarshal([]byte(annotations), &networks) - if err != nil { - // If unmarshalling fails, treat as comma-separated list - names := strings.Split(annotations, ",") - for _, name := range names { - name = strings.TrimSpace(name) - if name != "" { - networks = append(networks, NetworkAnnotation{Name: name}) - } - } - } - - // Iterate over the networks to check if any IPAddresses are missing - for i := range networks { - if len(networks[i].IPAdresses) == 0 { - // Call GenerateIPv6Address if IPAddresses are missing - networks[i].GenerateIPv6Address() - } - networks[i].Namespace = namespace - } - - return networks, nil -} - -func (a *PodAnnotator) verifyNetworksAreCreated(ctx context.Context, networks []NetworkAnnotation) (bool, error) { - // List all L2Networks - l2Networks := &l2smv1.L2NetworkList{} - if err := a.Client.List(ctx, l2Networks); err != nil { - return false, err - } - - // Create a map of existing L2Network names for quick lookup - existingNetworks := make(map[string]struct{}) - for _, network := range l2Networks.Items { - existingNetworks[network.Name] = struct{}{} - } - - // Verify if each annotated network exists - for _, net := range networks { - if _, exists := existingNetworks[net.Name]; !exists { - return false, nil - } - - } - - return true, nil -} - -func (a *PodAnnotator) getFreeNetAttachDefs(ctx context.Context, nodeName string) nettypes.NetworkAttachmentDefinitionList { - - // We define the network attachment definition list that will be later filled. - freeNetAttachDef := &nettypes.NetworkAttachmentDefinitionList{} - - // We specify which net-attach-def we want. We want the ones that are specific to l2sm, in the overlay namespace and available in the desired node. - nodeSelector := labels.NewSelector() - - nodeRequirement, _ := labels.NewRequirement(fmt.Sprintf("%s%s", NET_ATTACH_LABEL_PREFIX, nodeName), selection.NotIn, []string{"true"}) - l2smRequirement, _ := labels.NewRequirement("app", selection.Equals, []string{"l2sm"}) - - nodeSelector.Add(*nodeRequirement) - nodeSelector.Add(*l2smRequirement) - - listOptions := client.ListOptions{LabelSelector: nodeSelector, Namespace: a.SwitchesNamespace} - - // We get the net-attach-def with the corresponding list options - a.Client.List(ctx, freeNetAttachDef, &listOptions) - return *freeNetAttachDef - -} - -func (network *NetworkAnnotation) GenerateIPv6Address() { - rand.Seed(time.Now().UnixNano()) - - // Generating the interface ID (64 bits) - interfaceID := rand.Uint64() - - // Formatting to a 16 character hexadecimal string - interfaceIDHex := fmt.Sprintf("%016x", interfaceID) - - // Constructing the full IPv6 address in the fe80::/64 range - ipv6Address := fmt.Sprintf("fe80::%s:%s:%s:%s/64", - interfaceIDHex[:4], interfaceIDHex[4:8], interfaceIDHex[8:12], interfaceIDHex[12:]) - - network.IPAdresses = append(network.IPAdresses, ipv6Address) - -} -func multusAnnotationToString(multusAnnotations []NetworkAnnotation) string { - jsonData, err := json.Marshal(multusAnnotations) - if err != nil { - return "" - } - return string(jsonData) -}