Skip to content

Commit

Permalink
New NamespaceOffloading Controller
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Andreagit97 committed Jun 1, 2021
1 parent 58a7dbd commit 703c0d0
Show file tree
Hide file tree
Showing 14 changed files with 1,394 additions and 56 deletions.
7 changes: 3 additions & 4 deletions apis/offloading/v1alpha1/namespaceoffloading_types.go
@@ -1,12 +1,8 @@
/*
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.
Expand Down Expand Up @@ -35,6 +31,8 @@ const (
SomeFailedOffloadingPhaseType OffloadingPhaseType = "SomeFailed"
// AllFailedOffloadingPhaseType -> there was an error during creation of all remote Namespaces.
AllFailedOffloadingPhaseType OffloadingPhaseType = "AllFailed"
// TerminatingOffloadingPhaseType -> means remote namespaces are undergoing graceful termination.
TerminatingOffloadingPhaseType OffloadingPhaseType = "Terminating"
)

// NamespaceMappingStrategyType represents different strategies to map local and remote namespace names.
Expand Down Expand Up @@ -127,6 +125,7 @@ type NamespaceOffloadingStatus struct {
// "InProgress" (i.e. remote Namespaces' creation is still ongoing.)
// "SomeFailed" (i.e. there was an error during creation of some remote Namespaces.)
// "AllFailed" (i.e. there was an error during creation of all remote Namespaces.)
// "Terminating" (i.e. remote namespaces are undergoing graceful termination.)
OffloadingPhase OffloadingPhaseType `json:"offloadingPhase,omitempty"`
// RemoteNamespacesConditions -> allows user to verify remote Namespaces' presence and status on all remote
// clusters through RemoteNamespaceCondition.
Expand Down
Expand Up @@ -175,7 +175,8 @@ spec:
matches user constraints.) "InProgress" (i.e. remote Namespaces''
creation is still ongoing.) "SomeFailed" (i.e. there was an error
during creation of some remote Namespaces.) "AllFailed" (i.e. there
was an error during creation of all remote Namespaces.)'
was an error during creation of all remote Namespaces.) "Terminating"
(i.e. remote namespaces are undergoing graceful termination.)'
type: string
remoteNamespaceName:
description: RemoteNamespaceName is the remote namespace name chosen
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -39,6 +39,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
Expand Down
1 change: 1 addition & 0 deletions go.sum
Expand Up @@ -1449,6 +1449,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=
Expand Down
Expand Up @@ -2,9 +2,10 @@ package resourcerequestoperator

import (
"context"
crdreplicator "github.com/liqotech/liqo/internal/crdReplicator"
"time"

crdreplicator "github.com/liqotech/liqo/internal/crdReplicator"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
Expand Down Expand Up @@ -66,7 +67,7 @@ var _ = Describe("ResourceRequest controller", func() {
Name: ResourceRequestName,
Namespace: ResourcesNamespace,
Labels: map[string]string{
crdreplicator.RemoteLabelSelector: clusterId,
crdreplicator.RemoteLabelSelector: clusterId,
crdreplicator.ReplicationStatuslabel: "true",
},
},
Expand Down
7 changes: 6 additions & 1 deletion pkg/consts/namespace_mapping.go
Expand Up @@ -16,8 +16,13 @@ const (
// DefaultNamespaceOffloadingName is the default name of NamespaceOffloading resources. Every namespace that has
// to be offloaded with Liqo, must have a NamespaceOffloading resource with this name.
DefaultNamespaceOffloadingName = "offloading"
// EnablingLiqoLabel is necessary in order to allow Pods to be scheduled on remote clusters.
// EnablingLiqoLabel is used to created a default NamespaceOffloading resource for the labeled namespace, this
// is an alternative way to start Liqo offloading.
EnablingLiqoLabel = "liqo.io/enabled"
// EnablingLiqoLabelValue unique value allowed for EnablingLiqoLabel.
EnablingLiqoLabelValue = "true"
// SchedulingLiqoLabel is necessary in order to allow Pods to be scheduled on remote clusters.
SchedulingLiqoLabel = "liqo.io/scheduling-enabled"
// SchedulingLiqoLabelValue unique value allowed for SchedulingLiqoLabel.
SchedulingLiqoLabelValue = "true"
)
@@ -0,0 +1,85 @@
package namespaceoffloadingctrl

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
k8shelper "k8s.io/component-helpers/scheduling/corev1"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"

offv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1"
mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1"
liqoconst "github.com/liqotech/liqo/pkg/consts"
)

func (r *NamespaceOffloadingReconciler) selectCompliantVirtualNodes(noff *offv1alpha1.NamespaceOffloading,
clusterIDMap map[string]*mapsv1alpha1.NamespaceMap) error {
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 err
}

// If here there are no virtual nodes is an error because it means that in the cluster there are NamespaceMap
// but not their associated virtual nodes
if len(virtualNodes.Items) == 0 {
err := fmt.Errorf(" No VirtualNodes at the moment in the cluster")
klog.Info(err)
return err
}

errorCondition := false
for i := range virtualNodes.Items {
match, err := k8shelper.MatchNodeSelectorTerms(&virtualNodes.Items[i], &noff.Spec.ClusterSelector)
if err != nil {
klog.Infof("%s -> Unable to offload the namespace '%s', there is an error in ClusterSelectorField",
err, noff.Namespace)
patch := noff.DeepCopy()
if noff.Annotations == nil {
noff.Annotations = map[string]string{}
}
noff.Annotations[liqoconst.SchedulingLiqoLabel] = fmt.Sprintf("Invalid Cluster Selector : %s", err)
if err = r.Patch(context.TODO(), noff, client.MergeFrom(patch)); err != nil {
klog.Errorf("%s -> unable to add the liqo scheduling annotation to the NamespaceOffloading in the namespace '%s'",
err, noff.Namespace)
return err
}
klog.Infof("The liqo scheduling annotation is correctly added to the NamespaceOffloading in the namespace '%s'",
noff.Namespace)
break
}
if match {
if err = addDesiredMapping(r.Client, noff.Namespace, noff.Status.RemoteNamespaceName,
clusterIDMap[virtualNodes.Items[i].Annotations[liqoconst.RemoteClusterID]]); err != nil {
errorCondition = true
continue
}
delete(clusterIDMap, virtualNodes.Items[i].Annotations[liqoconst.RemoteClusterID])
}
}
if errorCondition {
err := fmt.Errorf("some desiredMappings has not been added")
klog.Error(err)
return err
}
return nil
}

func (r *NamespaceOffloadingReconciler) enforceClusterSelector(noff *offv1alpha1.NamespaceOffloading,
clusterIDMap map[string]*mapsv1alpha1.NamespaceMap) error {
if noff.Spec.ClusterSelector.Size() == 0 {
klog.Infof(" The namespace '%s' is requested to be offloaded on all remote clusters", noff.Namespace)
if err := addDesiredMappings(r.Client, noff.Namespace, noff.Status.RemoteNamespaceName, clusterIDMap); err != nil {
return err
}
for key := range clusterIDMap {
delete(clusterIDMap, key)
}
return nil
}

return r.selectCompliantVirtualNodes(noff, clusterIDMap)
}
@@ -0,0 +1,3 @@
// Package namespaceoffloadingctrl contains NamespaceOffloading Controller logic and some functions for adding
// DesiredMappings to NamespaceMaps Spec
package namespaceoffloadingctrl
@@ -0,0 +1,90 @@
/*
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"

"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"

mapsv1alpha1 "github.com/liqotech/liqo/apis/virtualKubelet/v1alpha1"
)

// Removes right entry from one NamespaceMap, if present.
func removeDesiredMapping(c client.Client, localName string, nm *mapsv1alpha1.NamespaceMap) error {
if _, ok := nm.Spec.DesiredMapping[localName]; ok {
patch := nm.DeepCopy()
delete(nm.Spec.DesiredMapping, localName)
if err := c.Patch(context.TODO(), nm, client.MergeFrom(patch)); err != nil {
klog.Errorf("%s --> Unable to patch NamespaceMap '%s'", err, nm.GetName())
return err
}
klog.Infof(" Entry for the namespace '%s' is correctly deleted from the NamespaceMap '%s'", nm.GetName(), nm.GetName())
}
return nil
}

// Removes right entries from more than one NamespaceMap (it depends on len(nms)).
func removeDesiredMappings(c client.Client, localName string, nms map[string]*mapsv1alpha1.NamespaceMap) error {
errorCondition := false
for _, nm := range nms {
if err := removeDesiredMapping(c, localName, nm); err != nil {
errorCondition = true
}
}
if errorCondition {
err := fmt.Errorf("some desiredMappings has not been deleted")
klog.Error(err)
return err
}
return nil
}

// Adds right entry on one NamespaceMap, if it isn't already there.
func addDesiredMapping(c client.Client, 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 {
patch := nm.DeepCopy()
nm.Spec.DesiredMapping[localName] = remoteName
if err := c.Patch(context.TODO(), nm, client.MergeFrom(patch)); 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 the namespace '%s' is successfully added on the NamespaceMap '%s' ", localName, nm.GetName())
}
return nil
}

// Adds right entries on more than one NamespaceMap (it depends on len(nms)).
func addDesiredMappings(c client.Client, localName, remoteName string,
nms map[string]*mapsv1alpha1.NamespaceMap) error {
errorCondition := false
for _, nm := range nms {
if err := addDesiredMapping(c, localName, remoteName, nm); err != nil {
errorCondition = true
}
}
if errorCondition {
err := fmt.Errorf("some desiredMappings has not been added")
klog.Error(err)
return err
}
return nil
}
@@ -0,0 +1,51 @@
package namespaceoffloadingctrl

import (
"context"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"

liqoconst "github.com/liqotech/liqo/pkg/consts"
)

func addLiqoSchedulingLabel(ctx context.Context, c client.Client, namespaceName string) error {
namespace := &corev1.Namespace{}
if err := c.Get(ctx, types.NamespacedName{Name: namespaceName}, namespace); err != nil {
klog.Errorf("%s --> Unable to get the namespace '%s'", err, namespaceName)
return err
}

if value, ok := namespace.Labels[liqoconst.SchedulingLiqoLabel]; !ok || value != liqoconst.SchedulingLiqoLabelValue {
if namespace.Labels == nil {
namespace.Labels = map[string]string{}
}
namespace.Labels[liqoconst.SchedulingLiqoLabel] = liqoconst.SchedulingLiqoLabelValue
if err := c.Update(ctx, namespace); err != nil {
klog.Errorf(" %s --> Unable to add liqo scheduling label to the namespace '%s'", err, namespace.GetName())
return err
}
klog.Infof(" Liqo scheduling label successfully added to the namespace '%s'", namespace.GetName())
}
return nil
}

func removeLiqoSchedulingLabel(ctx context.Context, c client.Client, namespaceName string) error {
namespace := &corev1.Namespace{}
if err := c.Get(ctx, types.NamespacedName{Name: namespaceName}, namespace); err != nil {
klog.Errorf("%s --> Unable to get the namespace '%s'", err, namespaceName)
return err
}

if value, ok := namespace.Labels[liqoconst.SchedulingLiqoLabel]; ok && value == liqoconst.SchedulingLiqoLabelValue {
delete(namespace.Labels, liqoconst.SchedulingLiqoLabel)
if err := c.Update(ctx, namespace); err != nil {
klog.Errorf(" %s --> Unable to remove Liqo scheduling label from the namespace '%s'", err, namespace.GetName())
return err
}
klog.Infof(" Liqo scheduling label successfully removed from the namespace '%s'", namespace.GetName())
}
return nil
}

0 comments on commit 703c0d0

Please sign in to comment.