From c85ce9d37ba146f97f838d9c820351e7a63436e1 Mon Sep 17 00:00:00 2001 From: Andreagit97 Date: Tue, 22 Jun 2021 09:54:44 +0200 Subject: [PATCH] Refactor of VirtualNode-controller. This commit allows to manage in a simpler way the NamespaceMap lifecycle. --- pkg/consts/namespace_mapping.go | 4 +- ...manage_virtualnode_controller_finalizer.go | 34 ++ .../namespaceMap_creation.go | 56 +++ .../namespaceMap_lifecycle.go | 153 ------- .../virtualNode-controller/node_controller.go | 148 ------- .../node_controller_test.go | 407 ------------------ .../virtualNode-controller/suite_test.go | 60 +-- .../virtualnode_controller.go | 114 +++++ .../virtualnode_controller_test.go | 214 +++++++++ .../virtualnode_deletion_logic.go | 42 ++ 10 files changed, 496 insertions(+), 736 deletions(-) create mode 100644 pkg/liqo-controller-manager/virtualNode-controller/manage_virtualnode_controller_finalizer.go create mode 100644 pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_creation.go delete mode 100644 pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_lifecycle.go delete mode 100644 pkg/liqo-controller-manager/virtualNode-controller/node_controller.go delete mode 100644 pkg/liqo-controller-manager/virtualNode-controller/node_controller_test.go create mode 100644 pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller.go create mode 100644 pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller_test.go create mode 100644 pkg/liqo-controller-manager/virtualNode-controller/virtualnode_deletion_logic.go diff --git a/pkg/consts/namespace_mapping.go b/pkg/consts/namespace_mapping.go index afa978dee8..0caf709440 100644 --- a/pkg/consts/namespace_mapping.go +++ b/pkg/consts/namespace_mapping.go @@ -3,8 +3,8 @@ package consts const ( // RemoteClusterID is used to obtain cluster-id from different Liqo resources. RemoteClusterID = "cluster-id" // "remote.liqo.io/clusterId" - // MapNamespaceName is the namespace where NamespaceMap are created. - MapNamespaceName = "default" + // TechnicalNamespace is the namespace where NamespaceMap are created. + TechnicalNamespace = "default" // TypeLabel is the key of a Liqo label that identifies different types of nodes. TypeLabel = "liqo.io/type" // TypeNode is the value of a Liqo label that identifies Liqo virtual nodes. diff --git a/pkg/liqo-controller-manager/virtualNode-controller/manage_virtualnode_controller_finalizer.go b/pkg/liqo-controller-manager/virtualNode-controller/manage_virtualnode_controller_finalizer.go new file mode 100644 index 0000000000..ce0cf48abd --- /dev/null +++ b/pkg/liqo-controller-manager/virtualNode-controller/manage_virtualnode_controller_finalizer.go @@ -0,0 +1,34 @@ +package virtualnodectrl + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrlutils "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (r *VirtualNodeReconciler) checkVirtualNodeFinalizerPresence(ctx context.Context, n *corev1.Node) error { + if !ctrlutils.ContainsFinalizer(n, virtualNodeControllerFinalizer) { + original := n.DeepCopy() + ctrlutils.AddFinalizer(n, virtualNodeControllerFinalizer) + if err := r.Patch(ctx, n, client.MergeFrom(original)); err != nil { + klog.Errorf("%s --> Unable to add finalizer to the virtual-node '%s'", err, n.GetName()) + return err + } + klog.Infof("Finalizer correctly added on the virtual-node '%s'", n.GetName()) + } + return nil +} + +func (r *VirtualNodeReconciler) removeVirtualNodeFinalizer(ctx context.Context, n *corev1.Node) error { + original := n.DeepCopy() + ctrlutils.RemoveFinalizer(n, virtualNodeControllerFinalizer) + if err := r.Patch(ctx, n, client.MergeFrom(original)); err != nil { + klog.Errorf("%s --> Unable to remove finalizer from the virtual-node '%s'", err, n.GetName()) + return err + } + klog.Infof("Finalizer is correctly removed from the virtual-node '%s'", n.GetName()) + return nil +} diff --git a/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_creation.go b/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_creation.go new file mode 100644 index 0000000000..f0b4e59cf0 --- /dev/null +++ b/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_creation.go @@ -0,0 +1,56 @@ +package virtualnodectrl + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog" + ctrlutils "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" + liqoconst "github.com/liqotech/liqo/pkg/consts" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// create a new NamespaceMap with OwnerReference. +func (r *VirtualNodeReconciler) createNamespaceMap(ctx context.Context, n *corev1.Node) error { + nm := &mapsv1alpha1.NamespaceMap{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-", n.GetAnnotations()[liqoconst.RemoteClusterID]), + Namespace: liqoconst.TechnicalNamespace, + Labels: map[string]string{ + liqoconst.RemoteClusterID: n.GetAnnotations()[liqoconst.RemoteClusterID], + }, + }, + } + + if err := ctrlutils.SetControllerReference(n, nm, r.Scheme); err != nil { + return err + } + + if err := r.Create(ctx, nm); err != nil { + klog.Errorf("%s --> Problems in NamespaceMap creation for the virtual node '%s'", err, n.GetName()) + return err + } + klog.Infof(" Create the NamespaceMap '%s' for the virtual node '%s'", nm.GetName(), n.GetName()) + return nil +} + +// This function manages NamespaceMaps Lifecycle on the basis of NamespaceMaps' number. +func (r *VirtualNodeReconciler) checkNamespaceMapPresence(ctx context.Context, n *corev1.Node) error { + nms := &mapsv1alpha1.NamespaceMapList{} + if err := r.List(ctx, nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: n.GetAnnotations()[liqoconst.RemoteClusterID]}); err != nil { + klog.Errorf("%s --> Unable to List NamespaceMaps of virtual-node '%s'", err, n.GetName()) + return err + } + + if len(nms.Items) == 0 { + return r.createNamespaceMap(ctx, n) + } + + return nil +} diff --git a/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_lifecycle.go b/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_lifecycle.go deleted file mode 100644 index 7362156b91..0000000000 --- a/pkg/liqo-controller-manager/virtualNode-controller/namespaceMap_lifecycle.go +++ /dev/null @@ -1,153 +0,0 @@ -package virtualnodectrl - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" - ctrlutils "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" - liqoconst "github.com/liqotech/liqo/pkg/consts" - - "time" - - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func (r *VirtualNodeReconciler) removeAllDesiredMappings(nm *mapsv1alpha1.NamespaceMap) error { - for localName := range nm.Spec.DesiredMapping { - delete(nm.Spec.DesiredMapping, localName) - } - - ctrlutils.RemoveFinalizer(nm, virtualNodeControllerFinalizer) - klog.Infof("The NamespaceMap '%s' is requested to be deleted", nm.GetName()) - - if err := r.Update(context.TODO(), nm); err != nil { - klog.Errorf(" %s --> Problems while removing finalizer from '%s'", err, nm.GetName()) - return err - } - klog.Infof("Finalizer is correctly removed from the NamespaceMap '%s'", nm.GetName()) - - return nil -} - -// remove Finalizer and Update the NamespaceMap. -func (r *VirtualNodeReconciler) removeNamespaceMapFinalizers(nm *mapsv1alpha1.NamespaceMap) error { - ctrlutils.RemoveFinalizer(nm, virtualNodeControllerFinalizer) - ctrlutils.RemoveFinalizer(nm, liqoconst.NamespaceMapControllerFinalizer) - - klog.Infof("The NamespaceMap '%s' is requested to be deleted", nm.GetName()) - - if err := r.Update(context.TODO(), nm); err != nil { - // WARNING: Is possible that this Update is called on a resource that is no more here - // it doesn't return "NotFound" but "Conflict" - klog.Errorf(" %s --> Problems while removing finalizer from '%s'", err, nm.GetName()) - return err - } - klog.Infof("Finalizer is correctly removed from the NamespaceMap '%s'", nm.GetName()) - - return nil -} - -// create a new NamespaceMap with Finalizer and OwnerReference. -func (r *VirtualNodeReconciler) createNamespaceMap(n *corev1.Node, stat mapsv1alpha1.NamespaceMapStatus, - spec mapsv1alpha1.NamespaceMapSpec) error { - if _, ok := n.GetAnnotations()[liqoconst.RemoteClusterID]; !ok { - err := fmt.Errorf("label '%s' is not found on node '%s'", liqoconst.RemoteClusterID, n.GetName()) - klog.Error(err) - return err - } - - nm := &mapsv1alpha1.NamespaceMap{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("%s-", n.GetAnnotations()[liqoconst.RemoteClusterID]), - Namespace: liqoconst.MapNamespaceName, - Labels: map[string]string{ - liqoconst.RemoteClusterID: n.GetAnnotations()[liqoconst.RemoteClusterID], - }, - }, - Spec: spec, - Status: stat, - } - - if len(nm.Status.CurrentMapping) > 0 { - ctrlutils.AddFinalizer(nm, liqoconst.NamespaceMapControllerFinalizer) - } - - ctrlutils.AddFinalizer(nm, virtualNodeControllerFinalizer) - if err := ctrlutils.SetControllerReference(n, nm, r.Scheme); err != nil { - return err - } - - if err := r.Create(context.TODO(), nm); err != nil { - klog.Errorf("%s --> Problems in NamespaceMap creation for the virtual node '%s'", err, n.GetName()) - return err - } - klog.Infof(" Create NamespaceMap '%s' for the virtual node '%s'", nm.GetName(), n.GetName()) - return nil -} - -// first create a new NamespaceMap which preserves the Status and then delete the other. -func (r *VirtualNodeReconciler) regenerateNamespaceMap(nm *mapsv1alpha1.NamespaceMap, n *corev1.Node) error { - // create a new namespaceMap with same Status but with different Name - if err := r.createNamespaceMap(n, nm.Status, nm.Spec); err != nil { - return err - } - - if err := r.removeNamespaceMapFinalizers(nm); err != nil { - return err - } - return nil -} - -// This function manages NamespaceMaps Lifecycle on the basis of NamespaceMaps' number. -func (r *VirtualNodeReconciler) namespaceMapLifecycle(n *corev1.Node) error { - nms := &mapsv1alpha1.NamespaceMapList{} - if err := r.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: n.GetAnnotations()[liqoconst.RemoteClusterID]}); err != nil { - klog.Errorf("%s --> Unable to List NamespaceMaps of virtual node '%s'", err, n.GetName()) - return err - } - - if len(nms.Items) == 0 { - return r.createNamespaceMap(n, mapsv1alpha1.NamespaceMapStatus{}, mapsv1alpha1.NamespaceMapSpec{}) - } - - if len(nms.Items) == 1 { - if !nms.Items[0].GetDeletionTimestamp().IsZero() { - return r.regenerateNamespaceMap(&nms.Items[0], n) - } - return nil - } - - if len(nms.Items) > 1 { - oldestCreation := metav1.Time{Time: time.Now()} - var oldestMap int - - for i := range nms.Items { - if nms.Items[i].CreationTimestamp.Before(&oldestCreation) && nms.Items[i].DeletionTimestamp.IsZero() { - oldestCreation = nms.Items[i].CreationTimestamp - oldestMap = i - } - } - - for i := range nms.Items { - if i != oldestMap { - if nms.Items[i].GetDeletionTimestamp().IsZero() { - if err := r.Delete(context.TODO(), &nms.Items[i]); err != nil { - klog.Errorf(" %s --> Unable to remove NamespaceMap '%s'", err, nms.Items[i].GetName()) - return err - } - continue - } - if err := r.removeNamespaceMapFinalizers(&nms.Items[i]); err != nil { - return err - } - } - } - } - return nil -} diff --git a/pkg/liqo-controller-manager/virtualNode-controller/node_controller.go b/pkg/liqo-controller-manager/virtualNode-controller/node_controller.go deleted file mode 100644 index c030e24771..0000000000 --- a/pkg/liqo-controller-manager/virtualNode-controller/node_controller.go +++ /dev/null @@ -1,148 +0,0 @@ -/* - - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package virtualnodectrl - -import ( - "context" - - ctrlutils "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" - liqoconst "github.com/liqotech/liqo/pkg/consts" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/klog" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -const ( - virtualNodeControllerFinalizer = "virtualnode-controller.liqo.io/finalizer" -) - -// VirtualNodeReconciler manage NamespaceMap lifecycle. -type VirtualNodeReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=core,resources=nodes/status,verbs=get;update;patch - -// Reconcile checks if virtual-node must be deleted or manages its NamespaceMap. -func (r *VirtualNodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - node := &corev1.Node{} - if err := r.Get(context.TODO(), req.NamespacedName, node); err != nil { - klog.Errorf(" %s --> Unable to get virtual-node '%s'", err, req.Name) - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - if !ctrlutils.ContainsFinalizer(node, virtualNodeControllerFinalizer) { - ctrlutils.AddFinalizer(node, virtualNodeControllerFinalizer) - if err := r.Patch(context.TODO(), node, client.Merge); err != nil { - klog.Errorf("%s --> Unable to add '%s' to the virtual node '%s'", - err, virtualNodeControllerFinalizer, node.GetName()) - return ctrl.Result{}, err - } - } - - if !node.GetDeletionTimestamp().IsZero() { - klog.Infof("The virtual node '%s' is requested to be deleted", node.GetName()) - - nms := &mapsv1alpha1.NamespaceMapList{} - if err := r.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: node.GetAnnotations()[liqoconst.RemoteClusterID]}); err != nil { - klog.Errorf("%s --> Unable to List NamespaceMaps of virtual node '%s'", err, node.GetName()) - return ctrl.Result{}, err - } - - // delete all NamespaceMaps associated with this node, in normal conditions only one - for i := range nms.Items { - if err := r.removeAllDesiredMappings(&nms.Items[i]); err != nil { - return ctrl.Result{}, err - } - } - - ctrlutils.RemoveFinalizer(node, virtualNodeControllerFinalizer) - if err := r.Update(context.TODO(), node); err != nil { - klog.Errorf(" %s --> Unable to remove %s from the virtual node '%s'", - err, virtualNodeControllerFinalizer, node.GetName()) - return ctrl.Result{}, err - } - klog.Infof("Finalizer is correctly removed from the virtual node '%s'", node.GetName()) - return ctrl.Result{}, nil - } - - if err := r.namespaceMapLifecycle(node); err != nil { - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// Events not filtered: -// 1 -- creation of a new Virtual-node -// 2 -- creation of a new NamespaceMap -// 3 -- update deletionTimestamp on NamespaceMap or on Virtual-node, due to deletion request. -func filterVirtualNodes() predicate.Predicate { - return predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - // if the resource has no namespace, it surely a node, so we have to check if it is virtual or not, we are - // interested only in virtual-nodes' deletion, not common nodes' deletion. - if e.ObjectNew.GetNamespace() == "" { - if value, ok := (e.ObjectNew.GetLabels())[liqoconst.TypeLabel]; !ok || value != liqoconst.TypeNode { - return false - } - } - // so here we monitor only NamespaceMaps' and virtual-nodes' deletion - return !(e.ObjectNew.GetDeletionTimestamp().IsZero()) - }, - CreateFunc: func(e event.CreateEvent) bool { - // listen only virtual-node creation, not simple node - if e.Object.GetNamespace() == "" { - if value, ok := (e.Object.GetLabels())[liqoconst.TypeLabel]; !ok || value != liqoconst.TypeNode { - return false - } - } - // so here we monitor only NamespaceMaps' and virtual-nodes' creation - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return false - }, - GenericFunc: func(e event.GenericEvent) bool { - if e.Object.GetNamespace() == "" { - if value, ok := (e.Object.GetLabels())[liqoconst.TypeLabel]; !ok || value != liqoconst.TypeNode { - return false - } - } - return !(e.Object.GetDeletionTimestamp().IsZero()) - }, - } -} - -// SetupWithManager monitors Virtual-nodes and their associated NamespaceMaps. -func (r *VirtualNodeReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Node{}). - Owns(&mapsv1alpha1.NamespaceMap{}). - WithEventFilter(filterVirtualNodes()). - Complete(r) -} diff --git a/pkg/liqo-controller-manager/virtualNode-controller/node_controller_test.go b/pkg/liqo-controller-manager/virtualNode-controller/node_controller_test.go deleted file mode 100644 index 50ddaf430e..0000000000 --- a/pkg/liqo-controller-manager/virtualNode-controller/node_controller_test.go +++ /dev/null @@ -1,407 +0,0 @@ -package virtualnodectrl - -import ( - mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" - liqoconst "github.com/liqotech/liqo/pkg/consts" - - "context" - "fmt" - "strings" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/util/slice" - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - testLabel = "namespace1" - testValue = "namespace1-remote" -) - -var _ = Describe("VirtualNode controller", func() { - - nms = &mapsv1alpha1.NamespaceMapList{} - - Context("Check if resources VirtualNodes and NamespaceMaps are correctly initialized", func() { - - It("Check presence of NamespaceMaps and virtual nodes", func() { - - By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId1)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode1)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, - virtualNode1); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId2)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode2)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), - types.NamespacedName{Name: nameVirtualNode2}, virtualNode2); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - }) - - It(fmt.Sprintf("Check if finalizers and ownerReference are correctly created for %s", nameVirtualNode1), func() { - - By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode1)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, - virtualNode1); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId1)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - expectedOwnerReference := v1.OwnerReference{ - APIVersion: "v1", - BlockOwnerDeletion: pointer.BoolPtr(true), - Kind: "Node", - Name: virtualNode1.GetName(), - UID: virtualNode1.GetUID(), - Controller: pointer.BoolPtr(true), - } - - By(fmt.Sprintf("Try to check ownership of NamespaceMap: %s", nms.Items[0].GetName())) - Expect(nms.Items[0].GetOwnerReferences()).To(ContainElement(expectedOwnerReference)) - - By(fmt.Sprintf("Try to check presence of finalizer in NamespaceMap: %s", nms.Items[0].GetName())) - Expect(nms.Items[0].GetFinalizers()).To(ContainElement(virtualNodeControllerFinalizer)) - - By(fmt.Sprintf("Try to check presence of finalizer in VirtualNode: %s", virtualNode1.GetName())) - // i have to update my node instance, because finalizer could be updated after my first get - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, - virtualNode1); err != nil { - return false - } - return slice.ContainsString(virtualNode1.GetFinalizers(), virtualNodeControllerFinalizer, nil) - }, timeout, interval).Should(BeTrue()) - - }) - - It(fmt.Sprintf("Check if finalizers and ownerReference are correctly created for %s", nameVirtualNode2), func() { - - By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode2)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, - virtualNode2); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId2)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - expectedOwnerReference := v1.OwnerReference{ - APIVersion: "v1", - BlockOwnerDeletion: pointer.BoolPtr(true), - Kind: "Node", - Name: virtualNode2.GetName(), - UID: virtualNode2.GetUID(), - Controller: pointer.BoolPtr(true), - } - - By(fmt.Sprintf("Try to check ownership of NamespaceMap: %s", nms.Items[0].GetName())) - Expect(nms.Items[0].GetOwnerReferences()).To(ContainElement(expectedOwnerReference)) - - By(fmt.Sprintf("Try to check presence of finalizer in NamespaceMap: %s", nms.Items[0].GetName())) - Expect(nms.Items[0].GetFinalizers()).To(ContainElement(virtualNodeControllerFinalizer)) - - By(fmt.Sprintf("Try to check presence of finalizer in VirtualNode: %s", virtualNode2.GetName())) - // i have to update my node instance, because finalizer could be updated after my first get - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, - virtualNode2); err != nil { - return false - } - return slice.ContainsString(virtualNode2.GetFinalizers(), virtualNodeControllerFinalizer, nil) - }, timeout, interval).Should(BeTrue()) - - }) - - }) - - Context("Check if a non-virtual node is monitored", func() { - - It("Check absence of NamespaceMap and of finalizer", func() { - - By(fmt.Sprintf("Try to get not virtual-node: %s", nameSimpleNode)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameSimpleNode}, - simpleNode); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Check absence of NamespaceMap associated to %s", remoteClusterIdSimpleNode)) - Consistently(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterIdSimpleNode}); err != nil { - return false - } - if len(nms.Items) == 0 { - return true - } - return false - }, timeout/5, interval).Should(BeTrue()) - - By(fmt.Sprintf("Check absence of finalizer %s: ", virtualNodeControllerFinalizer)) - Consistently(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameSimpleNode}, - simpleNode); err != nil { - return true - } - return slice.ContainsString(simpleNode.GetFinalizers(), virtualNodeControllerFinalizer, nil) - }, timeout/5, interval).ShouldNot(BeTrue()) - - }) - - }) - - Context("Check deletion lifecycle of Namespacemaps and virtual-nodes ", func() { - - BeforeEach(func() { - buffer.Reset() - }) - - It(fmt.Sprintf("Check regeneration of NamespaceMap associated to %s", remoteClusterId1), func() { - - oldName := "" - By(fmt.Sprintf("Try to delete NamespaceMap associated to: %s", remoteClusterId1)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - - if nms.Items[0].Spec.DesiredMapping == nil { - nms.Items[0].Spec.DesiredMapping = map[string]string{} - } - - // random state - if nms.Items[0].Status.CurrentMapping == nil { - nms.Items[0].Status.CurrentMapping = map[string]mapsv1alpha1.RemoteNamespaceStatus{} - } - nms.Items[0].Spec.DesiredMapping[testLabel] = testValue - nms.Items[0].Status.CurrentMapping[testLabel] = mapsv1alpha1.RemoteNamespaceStatus{ - RemoteNamespace: testValue, - Phase: mapsv1alpha1.MappingAccepted, - } - - By(fmt.Sprintf("Try to update NamespaceMap: %s", nms.Items[0].GetName())) - if err := k8sClient.Update(context.TODO(), &nms.Items[0]); err != nil { - return false - } - - oldName = nms.Items[0].GetName() - By(fmt.Sprintf("Try to delete NamespaceMap: %s", nms.Items[0].GetName())) - if err := k8sClient.Delete(context.TODO(), &nms.Items[0]); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get new NamespaceMap associated to: %s", remoteClusterId1)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - if oldName != nms.Items[0].GetName() && nms.Items[0].Spec.DesiredMapping[testLabel] == testValue && - nms.Items[0].Status.CurrentMapping[testLabel].RemoteNamespace == testValue && - nms.Items[0].Status.CurrentMapping[testLabel].Phase == mapsv1alpha1.MappingAccepted { - return true - } - return false - }, timeout, interval).Should(BeTrue()) - - }) - - It(fmt.Sprintf("Check regeneration of NamespaceMap associated to %s", remoteClusterId2), func() { - - oldName := "" - By(fmt.Sprintf("Try to delete NamespaceMap associated to: %s", remoteClusterId2)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - - if nms.Items[0].Spec.DesiredMapping == nil { - nms.Items[0].Spec.DesiredMapping = map[string]string{} - } - - if nms.Items[0].Status.CurrentMapping == nil { - nms.Items[0].Status.CurrentMapping = map[string]mapsv1alpha1.RemoteNamespaceStatus{} - } - nms.Items[0].Spec.DesiredMapping[testLabel] = testValue - nms.Items[0].Status.CurrentMapping[testLabel] = mapsv1alpha1.RemoteNamespaceStatus{ - RemoteNamespace: testValue, - Phase: mapsv1alpha1.MappingAccepted, - } - - By(fmt.Sprintf("Try to update NamespaceMap: %s", nms.Items[0].GetName())) - if err := k8sClient.Update(context.TODO(), &nms.Items[0]); err != nil { - return false - } - - oldName = nms.Items[0].GetName() - By(fmt.Sprintf("Try to delete NamespaceMap: %s", nms.Items[0].GetName())) - if err := k8sClient.Delete(context.TODO(), &nms.Items[0]); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get new NamespaceMap associated to: %s", remoteClusterId2)) - Eventually(func() bool { - if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.MapNamespaceName), - client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { - return false - } - if len(nms.Items) != 1 { - return false - } - if oldName != nms.Items[0].GetName() && nms.Items[0].Spec.DesiredMapping[testLabel] == testValue && - nms.Items[0].Status.CurrentMapping[testLabel].RemoteNamespace == testValue && - nms.Items[0].Status.CurrentMapping[testLabel].Phase == mapsv1alpha1.MappingAccepted { - return true - } - return false - }, timeout, interval).Should(BeTrue()) - - }) - - It(fmt.Sprintf("Check deletion of virtualNode: %s", nameVirtualNode1), func() { - - By(fmt.Sprintf("Try to delete virtual-node: %s", nameVirtualNode1)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, - virtualNode1); err != nil { - return false - } - if err := k8sClient.Delete(context.TODO(), virtualNode1); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By("Try to catch right virtual-node log: ") - Eventually(func() bool { - return strings.Contains(buffer.String(), - fmt.Sprintf("The virtual node '%s' is requested to be deleted", nameVirtualNode1)) - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get if virtual-node %s is removed", nameVirtualNode1)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, - virtualNode1); err != nil { - if errors.IsNotFound(err) { - return true - } - } - return false - }, timeout*2, interval).Should(BeTrue()) - }) - - It(fmt.Sprintf("Check deletion of virtualNode: %s", nameVirtualNode2), func() { - - By(fmt.Sprintf("Try to delete virtual-node: %s", nameVirtualNode2)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, - virtualNode2); err != nil { - return false - } - if err := k8sClient.Delete(context.TODO(), virtualNode2); err != nil { - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - By("Try to catch right virtual-node log: ") - Eventually(func() bool { - return strings.Contains(buffer.String(), - fmt.Sprintf("The virtual node '%s' is requested to be deleted", nameVirtualNode2)) - }, timeout, interval).Should(BeTrue()) - - By(fmt.Sprintf("Try to get if virtual-node %s is removed", nameVirtualNode2)) - Eventually(func() bool { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, - virtualNode2); err != nil { - if errors.IsNotFound(err) { - return true - } - } - return false - }, timeout, interval).Should(BeTrue()) - }) - - }) - -}) diff --git a/pkg/liqo-controller-manager/virtualNode-controller/suite_test.go b/pkg/liqo-controller-manager/virtualNode-controller/suite_test.go index 215d8a926f..af9351ccad 100644 --- a/pkg/liqo-controller-manager/virtualNode-controller/suite_test.go +++ b/pkg/liqo-controller-manager/virtualNode-controller/suite_test.go @@ -17,6 +17,9 @@ import ( "context" "flag" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" liqoconst "github.com/liqotech/liqo/pkg/consts" @@ -24,9 +27,7 @@ import ( "testing" "time" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" . "github.com/onsi/ginkgo" @@ -64,13 +65,13 @@ const ( ) var ( - nms *mapsv1alpha1.NamespaceMapList - virtualNode1 *corev1.Node - virtualNode2 *corev1.Node - simpleNode *corev1.Node - mapNamespace *corev1.Namespace - flags *flag.FlagSet - buffer *bytes.Buffer + nms *mapsv1alpha1.NamespaceMapList + virtualNode1 *corev1.Node + virtualNode2 *corev1.Node + simpleNode *corev1.Node + technicalNamespace *corev1.Namespace + flags *flag.FlagSet + buffer *bytes.Buffer ) func TestAPIs(t *testing.T) { @@ -128,6 +129,26 @@ var _ = BeforeSuite(func(done Done) { k8sClient = k8sManager.GetClient() Expect(k8sClient).ToNot(BeNil()) + nms = &mapsv1alpha1.NamespaceMapList{} + technicalNamespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: liqoconst.TechnicalNamespace, + }, + } + + Eventually(func() bool { + if err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: liqoconst.TechnicalNamespace}, + technicalNamespace); err != nil { + if errors.IsNotFound(err) { + if err = k8sClient.Create(context.TODO(), technicalNamespace); err == nil { + return true + } + } + return false + } + return true + }, timeout, interval).Should(BeTrue()) + virtualNode1 = &corev1.Node{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -164,6 +185,10 @@ var _ = BeforeSuite(func(done Done) { }, } + // create 2 virtual-nodes and 1 simple node (not virtual) + Expect(k8sClient.Create(context.TODO(), virtualNode1)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), virtualNode2)).Should(Succeed()) + simpleNode = &corev1.Node{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -181,23 +206,6 @@ var _ = BeforeSuite(func(done Done) { }, } - mapNamespace = &corev1.Namespace{} - Eventually(func() bool { - if err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: liqoconst.MapNamespaceName}, - mapNamespace); err != nil { - if errors.IsNotFound(err) { - if err = k8sClient.Create(context.TODO(), virtualNode1); err == nil { - return true - } - } - return false - } - return true - }, timeout, interval).Should(BeTrue()) - - // create 2 virtual-nodes and 1 simple node (not virtual) - Expect(k8sClient.Create(context.TODO(), virtualNode1)).Should(Succeed()) - Expect(k8sClient.Create(context.TODO(), virtualNode2)).Should(Succeed()) Expect(k8sClient.Create(context.TODO(), simpleNode)).Should(Succeed()) close(done) diff --git a/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller.go b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller.go new file mode 100644 index 0000000000..6fa2867ee4 --- /dev/null +++ b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller.go @@ -0,0 +1,114 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package virtualnodectrl + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" + liqoconst "github.com/liqotech/liqo/pkg/consts" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +const ( + virtualNodeControllerFinalizer = "virtualnode-controller.liqo.io/finalizer" +) + +// VirtualNodeReconciler manage NamespaceMap lifecycle. +type VirtualNodeReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=nodes/status,verbs=get;update;patch + +// Reconcile manage NamespaceMaps associated with the virtual-node. +func (r *VirtualNodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + virtualNode := &corev1.Node{} + if err := r.Get(ctx, req.NamespacedName, virtualNode); err != nil { + if apierrors.IsNotFound(err) { + klog.Infof("There is no a virtual-node called '%s'", req.Name) + return ctrl.Result{}, nil + } + klog.Errorf(" %s --> Unable to get the virtual-node '%s'", err, req.Name) + return ctrl.Result{}, err + } + + // If the deletion timestamp is set all the NamespaceMaps associated with the virtual-node must be deleted. + if !virtualNode.GetDeletionTimestamp().IsZero() { + return ctrl.Result{}, r.removeAssociatedNamespaceMaps(ctx, virtualNode) + } + + // The virtual-node must have the cluster-id annotation + if _, ok := virtualNode.GetAnnotations()[liqoconst.RemoteClusterID]; !ok { + err := fmt.Errorf("the annotation '%s' is not found on node '%s'", liqoconst.RemoteClusterID, virtualNode.GetName()) + klog.Error(err) + return ctrl.Result{}, err + } + + // It is necessary to have a finalizer on the virtual-virtualNode. + if err := r.checkVirtualNodeFinalizerPresence(ctx, virtualNode); err != nil { + return ctrl.Result{}, err + } + + // If there is no NamespaceMap associated with this virtual-node, it creates a new one. + if err := r.checkNamespaceMapPresence(ctx, virtualNode); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// All the events on virtual-nodes are monitored. +// Only the deletion event on NamespaceMaps is monitored. +func filterVirtualNodes() predicate.Predicate { + return predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + value, ok := (e.ObjectNew.GetLabels())[liqoconst.TypeLabel] + return ok && value == liqoconst.TypeNode && !e.ObjectNew.GetDeletionTimestamp().IsZero() + }, + CreateFunc: func(e event.CreateEvent) bool { + value, ok := (e.Object.GetLabels())[liqoconst.TypeLabel] + return ok && value == liqoconst.TypeNode + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // It is necessary to monitor also the deletion of the NamespaceMap. + value, ok := (e.Object.GetLabels())[liqoconst.TypeLabel] + return (ok && value == liqoconst.TypeNode) || e.Object.GetNamespace() == liqoconst.TechnicalNamespace + }, + } +} + +// SetupWithManager monitors Virtual-nodes and their associated NamespaceMaps. +func (r *VirtualNodeReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Node{}). + Owns(&mapsv1alpha1.NamespaceMap{}). + WithEventFilter(filterVirtualNodes()). + Complete(r) +} diff --git a/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller_test.go b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller_test.go new file mode 100644 index 0000000000..1d230c818a --- /dev/null +++ b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_controller_test.go @@ -0,0 +1,214 @@ +package virtualnodectrl + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + + liqoconst "github.com/liqotech/liqo/pkg/consts" + + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/util/slice" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("VirtualNode controller", func() { + + Context("Check if resources VirtualNodes and NamespaceMaps are correctly initialized", func() { + + It("Check NamespaceMaps presence", func() { + + By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId1)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { + return false + } + return len(nms.Items) == 1 + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId2)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { + return false + } + return len(nms.Items) == 1 + }, timeout, interval).Should(BeTrue()) + + }) + + It(fmt.Sprintf("Check if finalizers and ownerReference are correctly created for %s", nameVirtualNode1), func() { + + By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode1)) + Eventually(func() bool { + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, virtualNode1) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId1)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { + return false + } + return len(nms.Items) == 1 + }, timeout, interval).Should(BeTrue()) + + expectedOwnerReference := v1.OwnerReference{ + APIVersion: "v1", + BlockOwnerDeletion: pointer.BoolPtr(true), + Kind: "Node", + Name: virtualNode1.GetName(), + UID: virtualNode1.GetUID(), + Controller: pointer.BoolPtr(true), + } + + By(fmt.Sprintf("Try to check ownership of NamespaceMap: %s", nms.Items[0].GetName())) + Expect(nms.Items[0].GetOwnerReferences()).To(ContainElement(expectedOwnerReference)) + + By(fmt.Sprintf("Try to check presence of finalizer in VirtualNode: %s", virtualNode1.GetName())) + Eventually(func() bool { + if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, + virtualNode1); err != nil { + return false + } + return slice.ContainsString(virtualNode1.GetFinalizers(), virtualNodeControllerFinalizer, nil) + }, timeout, interval).Should(BeTrue()) + + }) + + It(fmt.Sprintf("Check if finalizers and ownerReference are correctly created for %s", nameVirtualNode2), func() { + + By(fmt.Sprintf("Try to get virtual-node: %s", nameVirtualNode2)) + Eventually(func() bool { + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, virtualNode2) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Try to get NamespaceMap associated to: %s", remoteClusterId2)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId2}); err != nil { + return false + } + return len(nms.Items) == 1 + }, timeout, interval).Should(BeTrue()) + + expectedOwnerReference := v1.OwnerReference{ + APIVersion: "v1", + BlockOwnerDeletion: pointer.BoolPtr(true), + Kind: "Node", + Name: virtualNode2.GetName(), + UID: virtualNode2.GetUID(), + Controller: pointer.BoolPtr(true), + } + + By(fmt.Sprintf("Try to check ownership of NamespaceMap: %s", nms.Items[0].GetName())) + Expect(nms.Items[0].GetOwnerReferences()).To(ContainElement(expectedOwnerReference)) + + By(fmt.Sprintf("Try to check presence of finalizer in VirtualNode: %s", virtualNode2.GetName())) + // i have to update my node instance, because finalizer could be updated after my first get + Eventually(func() bool { + if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode2}, + virtualNode2); err != nil { + return false + } + return slice.ContainsString(virtualNode2.GetFinalizers(), virtualNodeControllerFinalizer, nil) + }, timeout, interval).Should(BeTrue()) + + }) + + }) + + Context("Check if a non-virtual node is monitored", func() { + + It("Check absence of NamespaceMap and of finalizer", func() { + + By(fmt.Sprintf("Try to get not virtual-node: %s", nameSimpleNode)) + Eventually(func() bool { + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameSimpleNode}, simpleNode) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Check absence of NamespaceMap associated to %s", remoteClusterIdSimpleNode)) + Consistently(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterIdSimpleNode}); err != nil { + return false + } + return len(nms.Items) == 0 + }, timeout/5, interval).Should(BeTrue()) + + By(fmt.Sprintf("Check absence of finalizer %s: ", virtualNodeControllerFinalizer)) + Consistently(func() bool { + if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameSimpleNode}, + simpleNode); err != nil { + return false + } + return !slice.ContainsString(simpleNode.GetFinalizers(), virtualNodeControllerFinalizer, nil) + }, timeout/5, interval).Should(BeTrue()) + + }) + + }) + + Context("Check deletion lifecycle of Namespacemaps associated with virtual-node 1 ", func() { + + It(fmt.Sprintf("Check regeneration of NamespaceMap associated to %s", remoteClusterId1), func() { + + oldName := "" + By(fmt.Sprintf("Try to delete NamespaceMap associated to: %s", remoteClusterId1)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { + return false + } + if len(nms.Items) != 1 { + return false + } + oldName = nms.Items[0].Name + err := k8sClient.Delete(context.TODO(), &nms.Items[0]) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Try to get new NamespaceMap associated to: %s", remoteClusterId1)) + Eventually(func() bool { + if err := k8sClient.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: remoteClusterId1}); err != nil { + return false + } + return len(nms.Items) == 1 && oldName != nms.Items[0].Name + }, timeout, interval).Should(BeTrue()) + + }) + + It(fmt.Sprintf("Check deletion of virtualNode: %s", nameVirtualNode1), func() { + + By(fmt.Sprintf("Try to delete virtual-node: %s", nameVirtualNode1)) + Eventually(func() bool { + if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, + virtualNode1); err != nil { + return false + } + err := k8sClient.Delete(context.TODO(), virtualNode1) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By(fmt.Sprintf("Check if virtual-node '%s' is deleted", nameVirtualNode1)) + Eventually(func() bool { + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nameVirtualNode1}, + virtualNode1) + return apierrors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + + }) + + }) + +}) diff --git a/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_deletion_logic.go b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_deletion_logic.go new file mode 100644 index 0000000000..30850ec4c8 --- /dev/null +++ b/pkg/liqo-controller-manager/virtualNode-controller/virtualnode_deletion_logic.go @@ -0,0 +1,42 @@ +package virtualnodectrl + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" + liqoconst "github.com/liqotech/liqo/pkg/consts" +) + +func (r *VirtualNodeReconciler) removeAssociatedNamespaceMaps(ctx context.Context, n *corev1.Node) error { + klog.Infof("The virtual virtualNode '%s' is requested to be deleted", n.GetName()) + + // The deletion timestamp is automatically set on the NamespaceMaps associated with the virtual-node, + // it's only necessary to wait until the NamespaceMaps are deleted. + nms := &mapsv1alpha1.NamespaceMapList{} + if err := r.List(context.TODO(), nms, client.InNamespace(liqoconst.TechnicalNamespace), + client.MatchingLabels{liqoconst.RemoteClusterID: n.GetAnnotations()[liqoconst.RemoteClusterID]}); err != nil { + klog.Errorf("%s --> Unable to List NamespaceMaps of virtual virtualNode '%s'", err, n.GetName()) + return err + } + + if len(nms.Items) == 0 { + return r.removeVirtualNodeFinalizer(ctx, n) + } + + for i := range nms.Items { + if nms.Items[i].GetDeletionTimestamp().IsZero() { + if err := r.Delete(ctx, &nms.Items[i]); err != nil { + klog.Errorf("%s -> unable to delete the NamespaceMap '%s'", err, nms.Items[i].Name) + } + } + } + + log := fmt.Errorf("waiting for deletion of NamespaceMaps associated with the virtual-node '%s'", n.Name) + klog.Info(log) + return log +}