diff --git a/cmd/metalnetlet/main.go b/cmd/metalnetlet/main.go index c3c78aec..543aec99 100644 --- a/cmd/metalnetlet/main.go +++ b/cmd/metalnetlet/main.go @@ -57,6 +57,7 @@ func main() { var configOptions config.GetConfigOptions var metalnetKubeconfig string var metalnetNamespace string + var disableNetworkPeering bool flag.StringVar(&name, "name", "", "The name of the partition the metalnetlet represents (required).") flag.StringToStringVar(&nodeLabels, "node-label", nodeLabels, "Additional labels to add to the nodes.") @@ -69,6 +70,8 @@ func main() { configOptions.BindFlags(flag.CommandLine) flag.StringVar(&metalnetKubeconfig, "metalnet-kubeconfig", "", "Metalnet kubeconfig to use.") flag.StringVar(&metalnetNamespace, "metalnet-namespace", corev1.NamespaceDefault, "Metalnet namespace to use.") + flag.BoolVar(&disableNetworkPeering, "disable-network-peering", false, + "Disable the metalnet based network peering. If set to true the network peering is handled externally.") opts := zap.Options{ Development: true, @@ -153,10 +156,11 @@ func main() { } if err := (&controllers.NetworkReconciler{ - Client: mgr.GetClient(), - MetalnetClient: metalnetCluster.GetClient(), - PartitionName: name, - MetalnetNamespace: metalnetNamespace, + Client: mgr.GetClient(), + MetalnetClient: metalnetCluster.GetClient(), + PartitionName: name, + MetalnetNamespace: metalnetNamespace, + NetworkPeeringDisabled: disableNetworkPeering, }).SetupWithManager(mgr, metalnetCluster.GetCache()); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Network") os.Exit(1) diff --git a/metalnetlet/controllers/controllers_suite_test.go b/metalnetlet/controllers/controllers_suite_test.go index 7e6dd866..085acb76 100644 --- a/metalnetlet/controllers/controllers_suite_test.go +++ b/metalnetlet/controllers/controllers_suite_test.go @@ -186,6 +186,55 @@ func SetupTest(metalnetNs *corev1.Namespace) { }) } +func SetupTestWithNetworkPeeringDisabled(metalnetNs *corev1.Namespace) { + BeforeEach(func(ctx SpecContext) { + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + }) + Expect(err).ToNot(HaveOccurred()) + + // register reconciler here + Expect((&NetworkReconciler{ + Client: k8sManager.GetClient(), + MetalnetClient: k8sManager.GetClient(), + PartitionName: partitionName, + MetalnetNamespace: metalnetNs.Name, + NetworkPeeringDisabled: true, + }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + + Expect((&MetalnetNodeReconciler{ + Client: k8sManager.GetClient(), + MetalnetClient: k8sManager.GetClient(), + PartitionName: partitionName, + NodeLabels: nodeLabels, + }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + + Expect((&NetworkInterfaceReconciler{ + Client: k8sManager.GetClient(), + MetalnetClient: k8sManager.GetClient(), + PartitionName: partitionName, + MetalnetNamespace: metalnetNs.Name, + }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + + Expect((&InstanceReconciler{ + Client: k8sManager.GetClient(), + MetalnetClient: k8sManager.GetClient(), + PartitionName: partitionName, + MetalnetNamespace: metalnetNs.Name, + }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + + mgrCtx, cancel := context.WithCancel(context.Background()) + DeferCleanup(cancel) + go func() { + defer GinkgoRecover() + Expect(k8sManager.Start(mgrCtx)).To(Succeed(), "failed to start manager") + }() + }) +} + func SetupMetalnetNode() *corev1.Node { return SetupObjectStruct[*corev1.Node](&k8sClient, func(node *corev1.Node) { *node = corev1.Node{ diff --git a/metalnetlet/controllers/network_controller.go b/metalnetlet/controllers/network_controller.go index 43a4abba..145431d1 100644 --- a/metalnetlet/controllers/network_controller.go +++ b/metalnetlet/controllers/network_controller.go @@ -33,6 +33,8 @@ type NetworkReconciler struct { PartitionName string MetalnetNamespace string + + NetworkPeeringDisabled bool } //+kubebuilder:rbac:groups="",resources=events,verbs=create;patch @@ -114,18 +116,20 @@ func (r *NetworkReconciler) delete(ctx context.Context, log logr.Logger, network } func (r *NetworkReconciler) updateApinetNetworkStatus(ctx context.Context, log logr.Logger, network *apinetv1alpha1.Network, metalnetNetwork *metalnetv1alpha1.Network) error { - apinetStatusPeerings := metalnetNetworkPeeringsStatusToNetworkPeeringsStatus(metalnetNetwork.Status.Peerings) - if !equality.Semantic.DeepEqual(network.Status.Peerings[r.PartitionName], apinetStatusPeerings) { - log.V(1).Info("Patching apinet network status", "status", apinetStatusPeerings) - networkBase := network.DeepCopy() - if network.Status.Peerings == nil { - network.Status.Peerings = make(map[string][]apinetv1alpha1.NetworkPeeringStatus) - } - network.Status.Peerings[r.PartitionName] = apinetStatusPeerings - if err := r.Status().Patch(ctx, network, client.MergeFrom(networkBase)); err != nil { - return fmt.Errorf("unable to patch network: %w", err) + if !r.NetworkPeeringDisabled { + apinetStatusPeerings := metalnetNetworkPeeringsStatusToNetworkPeeringsStatus(metalnetNetwork.Status.Peerings) + if !equality.Semantic.DeepEqual(network.Status.Peerings[r.PartitionName], apinetStatusPeerings) { + log.V(1).Info("Patching apinet network status", "status", apinetStatusPeerings) + networkBase := network.DeepCopy() + if network.Status.Peerings == nil { + network.Status.Peerings = make(map[string][]apinetv1alpha1.NetworkPeeringStatus) + } + network.Status.Peerings[r.PartitionName] = apinetStatusPeerings + if err := r.Status().Patch(ctx, network, client.MergeFrom(networkBase)); err != nil { + return fmt.Errorf("unable to patch network: %w", err) + } + log.V(1).Info("Patched apinet network status") } - log.V(1).Info("Patched apinet network status") } return nil } @@ -178,22 +182,24 @@ func (r *NetworkReconciler) reconcile(ctx context.Context, log logr.Logger, netw } var peeredIDs []int32 var peeredPrefixes []metalnetv1alpha1.PeeredPrefix - for _, peering := range network.Spec.Peerings { - id, err := networkid.ParseVNI(peering.ID) - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to parse peered network ID: %w", err) - } + if !r.NetworkPeeringDisabled { + for _, peering := range network.Spec.Peerings { + id, err := networkid.ParseVNI(peering.ID) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to parse peered network ID: %w", err) + } - // metalnetNetwork.Spec.PeeredIDs = append(metalnetNetwork.Spec.PeeredIDs, id) - peeredIDs = append(peeredIDs, id) + // metalnetNetwork.Spec.PeeredIDs = append(metalnetNetwork.Spec.PeeredIDs, id) + peeredIDs = append(peeredIDs, id) - if len(peering.Prefixes) > 0 { - ipPrefixes := getIPPrefixes(peering.Prefixes) - peeredPrefix := metalnetv1alpha1.PeeredPrefix{ - ID: id, - Prefixes: ipPrefixesToMetalnetPrefixes(ipPrefixes), + if len(peering.Prefixes) > 0 { + ipPrefixes := getIPPrefixes(peering.Prefixes) + peeredPrefix := metalnetv1alpha1.PeeredPrefix{ + ID: id, + Prefixes: ipPrefixesToMetalnetPrefixes(ipPrefixes), + } + peeredPrefixes = append(peeredPrefixes, peeredPrefix) } - peeredPrefixes = append(peeredPrefixes, peeredPrefix) } } // metalnetNetwork.Spec.PeeredPrefixes = peeredPrefixes diff --git a/metalnetlet/controllers/network_controller_test.go b/metalnetlet/controllers/network_controller_test.go index bbac3c9b..94d9188f 100644 --- a/metalnetlet/controllers/network_controller_test.go +++ b/metalnetlet/controllers/network_controller_test.go @@ -140,4 +140,132 @@ var _ = Describe("NetworkController", func() { Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(metalnetNetwork2), metalnetNetwork2)).To(Satisfy(apierrors.IsNotFound)) }) + + var _ = Describe("NetworkPeeringController", func() { + ns := SetupNamespace(&k8sClient) + metalnetNs := SetupNamespace(&k8sClient) + SetupTestWithNetworkPeeringDisabled(metalnetNs) + + It("should create metalnet networks for apinet networks without peerings information if NetworkPeeringDisabled is set to true", func(ctx SpecContext) { + By("creating a apinet network-1") + network1 := &apinetv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "network-1", + }, + } + Expect(k8sClient.Create(ctx, network1)).To(Succeed()) + + By("creating a apinet network-2") + network2 := &apinetv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "network-2", + }, + } + Expect(k8sClient.Create(ctx, network2)).To(Succeed()) + + By("updating apinet networks spec with peerings") + baseNetwork1 := network1.DeepCopy() + network1.Spec.Peerings = []apinetv1alpha1.NetworkPeering{{ + Name: "peering-1", + Prefixes: []apinetv1alpha1.PeeringPrefix{{ + Name: "my-prefix", + Prefix: net.MustParseNewIPPrefix("10.0.0.0/24")}}, + ID: network2.Spec.ID}} + Expect(k8sClient.Patch(ctx, network1, client.MergeFrom(baseNetwork1))).To(Succeed()) + + baseNetwork2 := network2.DeepCopy() + network2.Spec.Peerings = []apinetv1alpha1.NetworkPeering{{ + Name: "peering-1", + ID: network1.Spec.ID}} + Expect(k8sClient.Patch(ctx, network2, client.MergeFrom(baseNetwork2))).To(Succeed()) + + By("parsing the VNI of network-1") + network1Vni, err := networkid.ParseVNI(network1.Spec.ID) + Expect(err).NotTo(HaveOccurred()) + + By("parsing the VNI of network-2") + network2Vni, err := networkid.ParseVNI(network2.Spec.ID) + Expect(err).NotTo(HaveOccurred()) + + By("waiting for the metalnet networks to be created") + metalnetNetwork1 := &metalnetv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metalnetNs.Name, + Name: string(network1.UID), + }, + } + Eventually(Object(metalnetNetwork1)).Should(SatisfyAll( + HaveField("Spec", metalnetv1alpha1.NetworkSpec{ + ID: network1Vni, + }), + )) + + By("validating metalnet network spec is not updated with peering information") + Consistently(Object(metalnetNetwork1)).Should(SatisfyAll( + HaveField("Spec", metalnetv1alpha1.NetworkSpec{ + ID: network1Vni, + PeeredIDs: nil, + PeeredPrefixes: nil, + }), + )) + + metalnetNetwork2 := &metalnetv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metalnetNs.Name, + Name: string(network2.UID), + }, + } + Eventually(Object(metalnetNetwork2)).Should(SatisfyAll( + HaveField("Spec", metalnetv1alpha1.NetworkSpec{ + ID: network2Vni, + }), + )) + + Consistently(Object(metalnetNetwork2)).Should(SatisfyAll( + HaveField("Spec", metalnetv1alpha1.NetworkSpec{ + ID: network2Vni, + PeeredIDs: nil, + PeeredPrefixes: nil, + }), + )) + + By("updating status of metalnet network peerings") + Eventually(UpdateStatus(metalnetNetwork1, func() { + metalnetNetwork1.Status.Peerings = []metalnetv1alpha1.NetworkPeeringStatus{{ + ID: network2Vni, + State: metalnetv1alpha1.NetworkPeeringStateReady, + }} + })).Should(Succeed()) + + Eventually(UpdateStatus(metalnetNetwork2, func() { + metalnetNetwork2.Status.Peerings = []metalnetv1alpha1.NetworkPeeringStatus{{ + ID: network1Vni, + State: metalnetv1alpha1.NetworkPeeringStateReady, + }} + })).Should(Succeed()) + + By("ensuring apinet network status peerings are not updated") + Consistently(Object(network1)).Should(SatisfyAll( + HaveField("Status.Peerings", BeEmpty()), + )) + + Consistently(Object(network2)).Should(SatisfyAll( + HaveField("Status.Peerings", BeEmpty()), + )) + + By("deleting the networks") + Expect(k8sClient.Delete(ctx, network1)).To(Succeed()) + Expect(k8sClient.Delete(ctx, network2)).To(Succeed()) + + By("waiting for networks to be gone") + Eventually(Get(network1)).Should(Satisfy(apierrors.IsNotFound)) + Eventually(Get(network2)).Should(Satisfy(apierrors.IsNotFound)) + + By("asserting the corresponding apinet network is gone as well") + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(metalnetNetwork1), metalnetNetwork1)).To(Satisfy(apierrors.IsNotFound)) + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(metalnetNetwork2), metalnetNetwork2)).To(Satisfy(apierrors.IsNotFound)) + }) + }) })