From 20080ed55dec3ba7f587ae31c8c964f17aab1170 Mon Sep 17 00:00:00 2001 From: Andreagit97 Date: Fri, 21 May 2021 10:17:03 +0200 Subject: [PATCH] New NamespaceOffloading Controller This controller gets a just created NamespaceOffloading resource and adds entries to NamespaceMaps (Spec->DesiredMappings) in order to require the creation of remote namespaces, based on requirements specified by users. When the NamespaceOffloading resource is deleted also the remote namespaces are requested to be deleted, so the entries in NamespaceMaps are removed. --- go.mod | 1 + go.sum | 1 + pkg/consts/namespaceMapping.go | 7 + .../namespaceOffloading-controller/doc.go | 3 + .../manage_desiredMappings.go | 76 ++++++ .../namespaceOffloading_controller.go | 256 ++++++++++++++++++ 6 files changed, 344 insertions(+) create mode 100644 pkg/liqo-controller-manager/namespaceOffloading-controller/doc.go create mode 100644 pkg/liqo-controller-manager/namespaceOffloading-controller/manage_desiredMappings.go create mode 100644 pkg/liqo-controller-manager/namespaceOffloading-controller/namespaceOffloading_controller.go diff --git a/go.mod b/go.mod index fe3db92ebb..2792890305 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( k8s.io/api v0.21.0 k8s.io/apimachinery v0.21.0 k8s.io/client-go v12.0.0+incompatible + k8s.io/component-helpers v0.21.0 k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.8.0 k8s.io/kubectl v0.21.0 diff --git a/go.sum b/go.sum index 0c750fdc6a..48ea819247 100644 --- a/go.sum +++ b/go.sum @@ -1444,6 +1444,7 @@ k8s.io/cluster-bootstrap v0.21.0/go.mod h1:rs7i1JpBCa56YNmnYxFJuoUghIwpMzDidY8Zm k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= k8s.io/component-base v0.21.0 h1:tLLGp4BBjQaCpS/KiuWh7m2xqvAdsxLm4ATxHSe5Zpg= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= +k8s.io/component-helpers v0.21.0 h1:SoWLsd63LI5uwofcHVSO4jtlmZEJRycfwNBKU4eAGPQ= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= k8s.io/controller-manager v0.21.0/go.mod h1:Ohy0GRNRKPVjB8C8G+dV+4aPn26m8HYUI6ejloUBvUA= k8s.io/cri-api v0.21.0/go.mod h1:nJbXlTpXwYCYuGMR7v3PQb1Du4WOGj2I9085xMVjr3I= diff --git a/pkg/consts/namespaceMapping.go b/pkg/consts/namespaceMapping.go index 6b6482b3ae..edfbd2d625 100644 --- a/pkg/consts/namespaceMapping.go +++ b/pkg/consts/namespaceMapping.go @@ -13,4 +13,11 @@ const ( NamespaceMapControllerFinalizer = "namespacemap-controller.liqo.io/finalizer" // DocumentationURL is the URL to official Liqo Documentation. DocumentationURL = "https://doc.liqo.io/" + // DefaultNameNamespaceOffloading is the default name of NamespaceOffloading resources, every namespace that have + // to be offloaded with Liqo, must have a NamespaceOffloading resource with this name. + DefaultNameNamespaceOffloading = "offloading" + // EnablingLiqoLabel is necessary in order to allow Pods to be scheduled on remote clusters. + EnablingLiqoLabel = "liqo.io/enabled" + // EnablingLiqoLabelValue unique value allowed for EnablingLiqoLabel. + EnablingLiqoLabelValue = "true" ) diff --git a/pkg/liqo-controller-manager/namespaceOffloading-controller/doc.go b/pkg/liqo-controller-manager/namespaceOffloading-controller/doc.go new file mode 100644 index 0000000000..66a2011859 --- /dev/null +++ b/pkg/liqo-controller-manager/namespaceOffloading-controller/doc.go @@ -0,0 +1,3 @@ +// Package namespaceoffloadingctrl contains NamespaceOffloading Controller logic and some functions for adding +// DesiredMappings to NamespaceMaps Spec +package namespaceoffloadingctrl diff --git a/pkg/liqo-controller-manager/namespaceOffloading-controller/manage_desiredMappings.go b/pkg/liqo-controller-manager/namespaceOffloading-controller/manage_desiredMappings.go new file mode 100644 index 0000000000..472ce57c86 --- /dev/null +++ b/pkg/liqo-controller-manager/namespaceOffloading-controller/manage_desiredMappings.go @@ -0,0 +1,76 @@ +/* +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 namespaceoffloadingctrl + +import ( + "context" + + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" +) + +// Removes right entry from one NamespaceMap. +func (r *NamespaceOffloadingReconciler) removeDesiredMapping(localName string, nm *mapsv1alpha1.NamespaceMap) error { + if _, ok := nm.Spec.DesiredMapping[localName]; ok { + delete(nm.Spec.DesiredMapping, localName) + if err := r.Update(context.TODO(), nm); err != nil { + klog.Errorf("%s --> Unable to update NamespaceMap '%s'", err, nm.GetName()) + return err + } + klog.Infof(" Entries deleted correctly from '%s'", nm.GetName()) + } + return nil +} + +// Removes right entries from more than one NamespaceMap (it depends on len(nms)). +func (r *NamespaceOffloadingReconciler) removeDesiredMappings(localName string, nms map[string]*mapsv1alpha1.NamespaceMap) error { + for _, nm := range nms { + if err := r.removeDesiredMapping(localName, nm); err != nil { + return err + } + } + return nil +} + +// Adds right entry on one NamespaceMap, if it isn't already there. +func (r *NamespaceOffloadingReconciler) addDesiredMapping(localName, remoteName string, + nm *mapsv1alpha1.NamespaceMap) error { + if nm.Spec.DesiredMapping == nil { + nm.Spec.DesiredMapping = map[string]string{} + } + + if _, ok := nm.Spec.DesiredMapping[localName]; !ok { + nm.Spec.DesiredMapping[localName] = remoteName + if err := r.Patch(context.TODO(), nm, client.Merge); err != nil { + klog.Errorf("%s --> Unable to add entry for namespace '%s' on NamespaceMap '%s'", + err, localName, nm.GetName()) + return err + } + klog.Infof("Entry for namespace '%s' successfully added on NamespaceMap '%s' ", + localName, nm.GetName()) + } + return nil +} + +// Adds right entries on more than one NamespaceMap (it depends on len(nms)), if entries aren't already there. +func (r *NamespaceOffloadingReconciler) addDesiredMappings(localName, remoteName string, + nms map[string]*mapsv1alpha1.NamespaceMap) error { + for _, nm := range nms { + if err := r.addDesiredMapping(localName, remoteName, nm); err != nil { + return err + } + } + return nil +} diff --git a/pkg/liqo-controller-manager/namespaceOffloading-controller/namespaceOffloading_controller.go b/pkg/liqo-controller-manager/namespaceOffloading-controller/namespaceOffloading_controller.go new file mode 100644 index 0000000000..57c8fd499b --- /dev/null +++ b/pkg/liqo-controller-manager/namespaceOffloading-controller/namespaceOffloading_controller.go @@ -0,0 +1,256 @@ +/* +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 namespaceoffloadingctrl + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + k8shelper "k8s.io/component-helpers/scheduling/corev1" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/util/slice" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrlutils "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + offv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" + mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1" + liqoconst "github.com/liqotech/liqo/pkg/consts" + liqoutils "github.com/liqotech/liqo/pkg/utils" +) + +// NamespaceOffloadingReconciler changes DesiredMapping field of NamespaceMaps if a remote Namespace must be +// created or deleted. +type NamespaceOffloadingReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +const ( + namespaceOffloadingControllerFinalizer = "namespaceoffloading-controller.liqo.io/finalizer" +) + +// Reconcile requires creation of remote namespaces according to ClusterSelector field. +func (r *NamespaceOffloadingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + namespaceOffloading := &offv1alpha1.NamespaceOffloading{} + if err := r.Get(context.TODO(), types.NamespacedName{ + Namespace: req.Namespace, + Name: liqoconst.DefaultNameNamespaceOffloading, + }, namespaceOffloading); err != nil { + klog.Errorf("%s --> Unable to get namespaceOffloading for the namespace '%s'", err, req.Namespace) + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // get all NamespaceMaps in the clsuter + clusterNamespaceMaps := &mapsv1alpha1.NamespaceMapList{} + if err := r.List(context.TODO(), clusterNamespaceMaps); err != nil { + klog.Error(err, " --> Unable to List NamespaceMaps") + return ctrl.Result{}, err + } + + if len(clusterNamespaceMaps.Items) == 0 { + err := fmt.Errorf("no NamespaceMaps at the moment in the cluster") + klog.Info(err) + return ctrl.Result{}, err + } + + clusterWithNoOffloading := make(map[string]*mapsv1alpha1.NamespaceMap) + for i := range clusterNamespaceMaps.Items { + clusterWithNoOffloading[clusterNamespaceMaps.Items[i].Labels[liqoconst.RemoteClusterID]] = &clusterNamespaceMaps.Items[i] + } + + // get Namespace associated with this NamespaceOffloading Resource + namespace := &corev1.Namespace{} + if err := r.Get(context.TODO(), types.NamespacedName{ + Name: namespaceOffloading.Namespace, + }, namespace); err != nil { + klog.Errorf("%s --> Unable to get the namespace '%s'", err, namespaceOffloading.Namespace) + return ctrl.Result{}, err + } + + // Remove DesiredMappings, namespaceOffloadingControllerFinalizer, and Liqo label from associated Namespace + if !namespaceOffloading.GetDeletionTimestamp().IsZero() { + // remove DesiredMappings, namespaceOffloadingControllerFinalizer + klog.Infof("The namespaceOffloading of namespace '%s' is requested to be deleted", + namespaceOffloading.Namespace) + if err := r.removeDesiredMappings(namespaceOffloading.Namespace, clusterWithNoOffloading); err != nil { + return ctrl.Result{}, err + } + + // remove Liqo label from the associated Namespace + delete(namespace.Labels, liqoconst.EnablingLiqoLabel) + if err := r.Update(context.TODO(), namespace); err != nil { + klog.Errorf(" %s --> Unable to remove Liqo label from the namespace '%s'", err, namespace.GetName()) + return ctrl.Result{}, err + } + + ctrlutils.RemoveFinalizer(namespaceOffloading, namespaceOffloadingControllerFinalizer) + if err := r.Update(context.TODO(), namespaceOffloading); err != nil { + klog.Errorf("%s --> Unable to remove finalizer from namespaceOffloading", err) + return ctrl.Result{}, err + } + klog.Info("Finalizer removed from namespaceOffloading") + return ctrl.Result{}, nil + } + + // If there is no namespaceOffloadingControllerFinalizer, we have to perform some initialization + if !ctrlutils.ContainsFinalizer(namespaceOffloading, namespaceOffloadingControllerFinalizer) { + // 1 - Add namespaceOffloadingControllerFinalizer + ctrlutils.AddFinalizer(namespaceOffloading, namespaceOffloadingControllerFinalizer) + // 2 - Add empty cluster selector if not specified by the user + if namespaceOffloading.Spec.ClusterSelector.Size() == 0 { + namespaceOffloading.Spec.ClusterSelector = corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{}} + } + // 3 - Add remoteNamespaceName + switch { + case namespaceOffloading.Spec.NamespaceMappingStrategy == offv1alpha1.EnforceSameNameMappingStrategyType: + namespaceOffloading.Status.RemoteNamespaceName = namespaceOffloading.Namespace + default: + clusterID, err := liqoutils.GetClusterID(r.Client) + if err != nil { + return ctrl.Result{}, err + } + namespaceOffloading.Status.RemoteNamespaceName = fmt.Sprintf("%s-%s", namespaceOffloading.Namespace, clusterID) + } + // 4 - Set global OffloadingPhase=InProgress + namespaceOffloading.Status.OffloadingPhase = offv1alpha1.InProgressOffloadingPhaseType + + if err := r.Update(ctx, namespaceOffloading); err != nil { + klog.Errorf("%s --> Unable to update NamespaceOffloading in namespace '%s'", + err, namespaceOffloading.Namespace) + return ctrl.Result{}, err + } + } + + // Add Liqo label to associated Namespace, if it is not already there + if value, ok := namespace.Labels[liqoconst.EnablingLiqoLabel]; !ok || value != liqoconst.EnablingLiqoLabelValue { + if namespace.Labels == nil { + namespace.Labels = map[string]string{} + } + namespace.Labels[liqoconst.EnablingLiqoLabel] = liqoconst.EnablingLiqoLabelValue + if err := r.Patch(context.TODO(), namespace, client.Merge); err != nil { + klog.Errorf(" %s --> Unable to add liqo-label on namespace '%s'", err, namespace.GetName()) + return ctrl.Result{}, err + } + } + + // Add dediredMappings to NamespaceMaps + if namespaceOffloading.Spec.ClusterSelector.Size() == 0 { + klog.Infof(" Offload namespace '%s' on all remote clusters", namespaceOffloading.Namespace) + if err := r.addDesiredMappings(namespaceOffloading.Namespace, + namespaceOffloading.Status.RemoteNamespaceName, + clusterWithNoOffloading); err != nil { + return ctrl.Result{}, err + } + clusterWithNoOffloading = nil + } else { + virtualNodes := &corev1.NodeList{} + if err := r.List(context.TODO(), virtualNodes, + client.MatchingLabels{liqoconst.TypeLabel: liqoconst.TypeNode}); err != nil { + klog.Error(err, " --> Unable to List all virtual nodes") + return ctrl.Result{}, err + } + + if len(virtualNodes.Items) == 0 { + err := fmt.Errorf(" No VirtualNodes at the moment in the cluster") + klog.Info(err) + return ctrl.Result{}, err + } + + for i := range virtualNodes.Items { + match, err := k8shelper.MatchNodeSelectorTerms(&virtualNodes.Items[i], + &namespaceOffloading.Spec.ClusterSelector) + if err != nil { + klog.Infof("%s -> Unable to offload the namespace '%s'", err, namespaceOffloading.Namespace) + if namespaceOffloading.Annotations == nil { + namespaceOffloading.Annotations = map[string]string{} + } + namespaceOffloading.Annotations[liqoconst.EnablingLiqoLabel] = fmt.Sprintf("Invalid Cluster Selector : %s", err) + break + } + + if match { + if err = r.addDesiredMapping(namespaceOffloading.Namespace, + namespaceOffloading.Status.RemoteNamespaceName, + clusterWithNoOffloading[virtualNodes.Items[i].Annotations[liqoconst.RemoteClusterID]]); err != nil { + return ctrl.Result{}, err + } + delete(clusterWithNoOffloading, virtualNodes.Items[i].Annotations[liqoconst.RemoteClusterID]) + klog.Infof(" Offload namespace '%s' on remote cluster: %s", namespaceOffloading.Namespace, + virtualNodes.Items[i].Annotations[liqoconst.RemoteClusterID]) + } + } + } + + // Namespace not offloaded will have only the condition OffloadingRequired="false" + setOffloadingRequiredCondition(namespaceOffloading, clusterWithNoOffloading) + + if len(clusterWithNoOffloading) == len(clusterNamespaceMaps.Items) { + namespaceOffloading.Status.OffloadingPhase = offv1alpha1.NoClusterSelectedOffloadingPhaseType + } + + if err := r.Update(ctx, namespaceOffloading); err != nil { + klog.Errorf("%s --> Unable to update NamespaceOffloading in namespace '%s'", + err, namespaceOffloading.Namespace) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil +} + +func setOffloadingRequiredCondition(nsof *offv1alpha1.NamespaceOffloading, + clusterWithNoOff map[string]*mapsv1alpha1.NamespaceMap) { + for clusterID := range clusterWithNoOff { + liqoutils.UpdateNamespaceOffloadingCondition(&nsof.Status, &offv1alpha1.RemoteNamespaceCondition{ + Type: offv1alpha1.NamespaceOffloadingRequired, + Status: corev1.ConditionFalse, + Reason: "ClusterNotSelected", + Message: "You have not selected this cluster through ClusterSelector fields", + }, clusterID) + } +} + +// Events not filtered: +// 1 -- creation of a NamespaceOffloading resource with the right default name. +// 2 -- deletion of a NamespaceOffloading resource with the right finalizer. +func namespaceOffloadingPredicate() predicate.Predicate { + return predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + if !(e.ObjectNew.GetDeletionTimestamp().IsZero()) && slice.ContainsString(e.ObjectNew.GetFinalizers(), + namespaceOffloadingControllerFinalizer, nil) { + return true + } + return false + }, + CreateFunc: func(e event.CreateEvent) bool { + return e.Object.GetName() == liqoconst.DefaultNameNamespaceOffloading + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} + +// SetupWithManager reconciles NamespaceOffloading Resources. +func (r *NamespaceOffloadingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&offv1alpha1.NamespaceOffloading{}). + WithEventFilter(namespaceOffloadingPredicate()). + Complete(r) +}