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 May 30, 2021
1 parent 1e9aabc commit d31a7b4
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 6 deletions.
7 changes: 3 additions & 4 deletions apis/offloading/v1alpha1/namespaceoffloading_types.go
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion pkg/consts/namespace_mapping.go
Original file line number Diff line number Diff line change
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 labelled 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"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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)
if noff.Annotations == nil {
noff.Annotations = map[string]string{}
}
noff.Annotations[liqoconst.EnablingLiqoLabel] = fmt.Sprintf("Invalid Cluster Selector : %s", err)
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
}
clusterIDMap = nil
} else {
if err := r.selectCompliantVirtualNodes(noff, clusterIDMap); err != nil {
return err
}
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package namespaceoffloadingctrl contains NamespaceOffloading Controller logic and some functions for adding
// DesiredMappings to NamespaceMaps Spec
package namespaceoffloadingctrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
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(" Entries deleted correctly from '%s'", 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 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))
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
}
Original file line number Diff line number Diff line change
@@ -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-label on namespace '%s'", err, namespace.GetName())
return err
}
}
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.EnablingLiqoLabel)
if err := c.Update(ctx, namespace); err != nil {
klog.Errorf(" %s --> Unable to remove Liqo label from the namespace '%s'", err, namespace.GetName())
return err
}
}
return nil
}
Loading

0 comments on commit d31a7b4

Please sign in to comment.