Skip to content

Commit

Permalink
New OffloadingStatus Controller
Browse files Browse the repository at this point in the history
This new controller reconciles the status of the NamespaceOffloading resource. It checks every 'n' seconds the actual status of all remote namespaces associated with that NamespaceOffloading resource, by means of NamespaceMap Status. So based on these just read values, the controller sets the global offloading status and the remote namespace condition in the NamespaceOffloading Status.
  • Loading branch information
Andreagit97 authored and adamjensenbot committed Jun 9, 2021
1 parent 7b2b9f0 commit adc07a4
Show file tree
Hide file tree
Showing 7 changed files with 928 additions and 4 deletions.
4 changes: 0 additions & 4 deletions apis/virtualKubelet/v1alpha1/namespacemap_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
@@ -0,0 +1,2 @@
// Package offloadingstatuscontroller contains OffloadingStatus Controller logic.
package offloadingstatuscontroller
@@ -0,0 +1,131 @@
package offloadingstatuscontroller

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/klog"

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"
)

// mapPhaseToRemoteNamespaceCondition selects the right remote condition according to the remote namespace
// phase obtained by means of NamespaceMap.Status.CurrentMapping.
func mapPhaseToRemoteNamespaceCondition(phase mapsv1alpha1.MappingPhase) offv1alpha1.RemoteNamespaceCondition {
var remoteCondition offv1alpha1.RemoteNamespaceCondition
switch {
case phase == mapsv1alpha1.MappingAccepted:
remoteCondition = offv1alpha1.RemoteNamespaceCondition{
Type: offv1alpha1.NamespaceReady,
Status: corev1.ConditionTrue,
Reason: "RemoteNamespaceCreated",
Message: "Namespace correctly offloaded on this cluster",
}
case phase == mapsv1alpha1.MappingCreationLoopBackOff:
remoteCondition = offv1alpha1.RemoteNamespaceCondition{
Type: offv1alpha1.NamespaceReady,
Status: corev1.ConditionFalse,
Reason: "CreationLoopBackOff",
Message: "Some problems occurred during remote Namespace creation",
}
case phase == mapsv1alpha1.MappingTerminating:
remoteCondition = offv1alpha1.RemoteNamespaceCondition{
Type: offv1alpha1.NamespaceReady,
Status: corev1.ConditionFalse,
Reason: "TerminatingNamespace",
Message: "The remote Namespace is requested to be deleted",
}
// If phase is not specified.
default:
remoteCondition = offv1alpha1.RemoteNamespaceCondition{
Type: offv1alpha1.NamespaceOffloadingRequired,
Status: corev1.ConditionFalse,
Reason: "ClusterNotSelected",
Message: "You have not selected this cluster through ClusterSelector fields",
}
}
return remoteCondition
}

// assignClusterRemoteCondition sets the right remote namespace condition according to the remote namespace phase
// written in NamespaceMap.Status.CurrentMapping.
// If phase==nil the remote namespace condition OffloadingRequired=False is set.
func assignClusterRemoteCondition(noff *offv1alpha1.NamespaceOffloading, phase mapsv1alpha1.MappingPhase, clusterID string) {
if noff.Status.RemoteNamespacesConditions == nil {
noff.Status.RemoteNamespacesConditions = map[string]offv1alpha1.RemoteNamespaceConditions{}
}

newCondition := mapPhaseToRemoteNamespaceCondition(phase)
// if the condition is already there, do nothing
if liqoutils.IsStatusConditionPresentAndEqual(noff.Status.RemoteNamespacesConditions[clusterID], newCondition.Type, newCondition.Status) {
return
}
var remoteConditions []offv1alpha1.RemoteNamespaceCondition
liqoutils.AddRemoteNamespaceCondition(&remoteConditions, &newCondition)
noff.Status.RemoteNamespacesConditions[clusterID] = remoteConditions
klog.Infof("Remote condition of type '%s' with Status '%s' for the remote namespace '%s' associated with the cluster '%s'",
remoteConditions[0].Type, remoteConditions[0].Status, noff.Namespace, clusterID)
}

// todo: at the moment the global status InProgress is not implemented, at every reconcile the controller sets a global
// OffloadingStatus that reflects the current Status of NamespaceMaps
// If the NamespaceMap has a remote Status for that remote Namespace, the right remote condition is set according to the
// remote Namespace Phase. If there is no remote Status in the NamespaceMap, the OffloadingRequired=false condition is set
// this condition could be only transient until the NamespaceMap Status is updated or permanent if the local Namespace
// is not requested to be offloaded inside this cluster.
func setRemoteConditionsForEveryCluster(noff *offv1alpha1.NamespaceOffloading, nml *mapsv1alpha1.NamespaceMapList) {
for i := range nml.Items {
if remoteNamespaceStatus, ok := nml.Items[i].Status.CurrentMapping[noff.Namespace]; ok {
assignClusterRemoteCondition(noff, remoteNamespaceStatus.Phase, nml.Items[i].Labels[liqoconst.RemoteClusterID])
continue
}
// Two cases in which there are no entry in NamespaceMap Status:
// - when the local namespace is not offloaded inside this cluster.
// - when the remote namespace previously created has been correctly removed from this cluster.
// In these cases the remote condition will be "OffloadingRequired=false"
assignClusterRemoteCondition(noff, "", nml.Items[i].Labels[liqoconst.RemoteClusterID])
}
}

// setNamespaceOffloadingStatus sets global offloading status according to the remote namespace conditions.
func setNamespaceOffloadingStatus(noff *offv1alpha1.NamespaceOffloading) {
ready := 0
notReady := 0

for i := range noff.Status.RemoteNamespacesConditions {
condition := liqoutils.FindRemoteNamespaceCondition(noff.Status.RemoteNamespacesConditions[i], offv1alpha1.NamespaceReady)
if condition == nil {
continue
}
if condition.Status == corev1.ConditionTrue {
ready++
} else {
notReady++
}
}

switch {
case !noff.DeletionTimestamp.IsZero():
noff.Status.OffloadingPhase = offv1alpha1.TerminatingOffloadingPhaseType
if ready+notReady == 0 {
// The NamespaceOffloading is deleted only when there are no more remoteNamespaceCondition and
// the deletion timestamp is set.
for key := range noff.Status.RemoteNamespacesConditions {
delete(noff.Status.RemoteNamespacesConditions, key)
}
klog.Infof("NamespaceOffloading, in the namespace '%s', ready to be deleted", noff.Namespace)
}
case ready+notReady == 0:
noff.Status.OffloadingPhase = offv1alpha1.NoClusterSelectedOffloadingPhaseType
case ready == 0:
noff.Status.OffloadingPhase = offv1alpha1.AllFailedOffloadingPhaseType
case notReady == 0:
noff.Status.OffloadingPhase = offv1alpha1.ReadyOffloadingPhaseType
default:
noff.Status.OffloadingPhase = offv1alpha1.SomeFailedOffloadingPhaseType
}

klog.Infof("The OffloadingStatus for NamespaceOffloading in the namespace '%s' is set to '%s'",
noff.Namespace, noff.Status.OffloadingPhase)
}
@@ -0,0 +1,107 @@
/*
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 offloadingstatuscontroller

import (
"context"
"time"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"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"

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

// OffloadingStatusReconciler checks the status of all remote namespaces associated with this
// namespaceOffloading Resource, and sets the global offloading status in according to the feedbacks received
// from all remote clusters.
type OffloadingStatusReconciler struct {
client.Client
Scheme *runtime.Scheme
RequeueTime time.Duration
}

// Controller Ownership:
// --> NamespaceOffloading.Status.RemoteConditions
// --> NamespaceOffloading.Status.OffloadingPhase

// Reconcile sets the NamespaceOffloading Status checking the actual status of all remote Namespace.
// This reconcile is performed every RequeueTime.
func (r *OffloadingStatusReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
namespaceOffloading := &offv1alpha1.NamespaceOffloading{}
if err := r.Get(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: liqoconst.DefaultNamespaceOffloadingName,
}, namespaceOffloading); err != nil {
if apierrors.IsNotFound(err) {
klog.Infof("There is no NamespaceOffloading resource in Namespace '%s'", req.Namespace)
return ctrl.Result{}, nil
}
klog.Errorf("%s --> Unable to get namespaceOffloading for the namespace '%s'", err, req.Namespace)
return ctrl.Result{}, err
}

// Get all NamespaceMaps in the cluster
namespaceMapList := &mapsv1alpha1.NamespaceMapList{}
if err := r.List(ctx, namespaceMapList); err != nil {
klog.Errorf("%s -> unable to get NamespaceMaps", err)
return ctrl.Result{}, err
}

if len(namespaceMapList.Items) == 0 {
klog.Info("No NamespaceMaps in the cluster at the moment")
return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
}

original := namespaceOffloading.DeepCopy()

setRemoteConditionsForEveryCluster(namespaceOffloading, namespaceMapList)

setNamespaceOffloadingStatus(namespaceOffloading)

// Patch the status just one time at the end of the logic.
if err := r.Patch(ctx, namespaceOffloading, client.MergeFrom(original)); err != nil {
klog.Errorf("%s --> Unable to Patch NamespaceOffloading in the namespace '%s'",
err, namespaceOffloading.Namespace)
return ctrl.Result{}, err
}

return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
}

// checkNamespaceOffloadingStatus calls Reconcile only when a new NamespaceOffloading is created.
func checkNamespaceOffloadingStatus() predicate.Predicate {
return predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
return false
},
}
}

// SetupWithManager reconciles when a new NamespaceOffloading is created.
func (r *OffloadingStatusReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&offv1alpha1.NamespaceOffloading{}).
WithEventFilter(checkNamespaceOffloadingStatus()).
Complete(r)
}

0 comments on commit adc07a4

Please sign in to comment.