github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/rancher/rancher_cluster_controller.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package rancher
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/prometheus/client_golang/prometheus/promauto"
    13  	"go.uber.org/zap"
    14  	"k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    17  	"k8s.io/apimachinery/pkg/labels"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	ctrl "sigs.k8s.io/controller-runtime"
    22  	"sigs.k8s.io/controller-runtime/pkg/client"
    23  
    24  	clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    25  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    26  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    27  )
    28  
    29  const (
    30  	CreatedByLabel      = "app.kubernetes.io/created-by"
    31  	CreatedByVerrazzano = "verrazzano"
    32  	localClusterName    = "local"
    33  
    34  	finalizerName = "verrazzano.io/rancher-cluster"
    35  )
    36  
    37  type RancherClusterReconciler struct {
    38  	client.Client
    39  	Scheme             *runtime.Scheme
    40  	ClusterSyncEnabled bool
    41  	ClusterSelector    *metav1.LabelSelector
    42  	Log                *zap.SugaredLogger
    43  }
    44  
    45  var gvk = schema.GroupVersionKind{
    46  	Group:   "management.cattle.io",
    47  	Version: "v3",
    48  	Kind:    "Cluster",
    49  }
    50  
    51  var (
    52  	reconcileTimeMetric = promauto.NewGauge(prometheus.GaugeOpts{
    53  		Name: "vz_cluster_operator_reconcile_cluster_duration_seconds",
    54  		Help: "The duration of the reconcile process for cluster objects",
    55  	})
    56  	reconcileErrorCount = promauto.NewCounter(prometheus.CounterOpts{
    57  		Name: "vz_cluster_operator_reconcile_cluster_error_total",
    58  		Help: "The amount of errors encountered in the reconcile process",
    59  	})
    60  	reconcileSuccessCount = promauto.NewCounter(prometheus.CounterOpts{
    61  		Name: "vz_cluster_operator_reconcile_cluster_success_total",
    62  		Help: "The number of times the reconcile process succeeded",
    63  	})
    64  )
    65  
    66  func CattleClusterClientObject() client.Object {
    67  	obj := &unstructured.Unstructured{}
    68  	obj.SetGroupVersionKind(gvk)
    69  	return obj
    70  }
    71  
    72  // SetupWithManager creates a new controller and adds it to the manager
    73  func (r *RancherClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
    74  	return ctrl.NewControllerManagedBy(mgr).
    75  		For(CattleClusterClientObject()).
    76  		Complete(r)
    77  }
    78  
    79  // Reconcile is the main controller reconcile function
    80  func (r *RancherClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    81  	r.Log.Debugf("Reconciling Rancher cluster: %v", req.NamespacedName)
    82  
    83  	// Time the reconcile process and set the metric with the elapsed time
    84  	startTime := time.Now()
    85  	defer reconcileTimeMetric.Set(time.Since(startTime).Seconds())
    86  
    87  	cluster := &unstructured.Unstructured{}
    88  	cluster.SetGroupVersionKind(gvk)
    89  	err := r.Get(context.TODO(), req.NamespacedName, cluster)
    90  	if err != nil && !errors.IsNotFound(err) {
    91  		reconcileSuccessCount.Inc()
    92  		return ctrl.Result{}, err
    93  	}
    94  
    95  	if errors.IsNotFound(err) {
    96  		r.Log.Debugf("Rancher cluster %v not found, nothing to do", req.NamespacedName)
    97  		reconcileSuccessCount.Inc()
    98  		return ctrl.Result{}, nil
    99  	}
   100  
   101  	// if the deletion timestamp is set, delete the corresponding VMC
   102  	if !cluster.GetDeletionTimestamp().IsZero() {
   103  		if vzstring.SliceContainsString(cluster.GetFinalizers(), finalizerName) {
   104  			if err := r.DeleteVMC(cluster); err != nil {
   105  				reconcileErrorCount.Inc()
   106  				return ctrl.Result{}, err
   107  			}
   108  		}
   109  
   110  		if err := r.removeFinalizer(cluster); err != nil {
   111  			reconcileErrorCount.Inc()
   112  			return ctrl.Result{}, err
   113  		}
   114  		reconcileSuccessCount.Inc()
   115  		return ctrl.Result{}, nil
   116  
   117  	}
   118  
   119  	// add a finalizer to the Rancher cluster if it doesn't already exist
   120  	if err := r.ensureFinalizer(cluster); err != nil {
   121  		reconcileErrorCount.Inc()
   122  		return ctrl.Result{}, err
   123  	}
   124  
   125  	if !r.ClusterSyncEnabled {
   126  		r.Log.Debug("Cluster sync is disabled, skipping VMC creation")
   127  		reconcileSuccessCount.Inc()
   128  		return ctrl.Result{}, nil
   129  	}
   130  
   131  	var l labels.Set = cluster.GetLabels()
   132  	var selector labels.Selector
   133  
   134  	if r.ClusterSelector != nil {
   135  		selector, err = metav1.LabelSelectorAsSelector(r.ClusterSelector)
   136  		if err != nil {
   137  			r.Log.Errorf("Error parsing cluster label selector: %v", err)
   138  			reconcileErrorCount.Inc()
   139  			return ctrl.Result{}, err
   140  		}
   141  	}
   142  
   143  	if selector == nil || selector.Matches(l) {
   144  		// ensure the VMC exists
   145  		if err = r.ensureVMC(cluster); err != nil {
   146  			reconcileErrorCount.Inc()
   147  			return ctrl.Result{}, err
   148  		}
   149  	}
   150  
   151  	reconcileSuccessCount.Inc()
   152  	return ctrl.Result{}, nil
   153  }
   154  
   155  // ensureFinalizer adds a finalizer to the Rancher cluster if the finalizer is not already present
   156  func (r *RancherClusterReconciler) ensureFinalizer(cluster *unstructured.Unstructured) error {
   157  	// do not add finalizer to the "local" cluster resource
   158  	if localClusterName == cluster.GetName() {
   159  		return nil
   160  	}
   161  	if finalizers, added := vzstring.SliceAddString(cluster.GetFinalizers(), finalizerName); added {
   162  		cluster.SetFinalizers(finalizers)
   163  		if err := r.Update(context.TODO(), cluster); err != nil {
   164  			return err
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  // removeFinalizer removes the finalizer from the Rancher cluster resource
   171  func (r *RancherClusterReconciler) removeFinalizer(cluster *unstructured.Unstructured) error {
   172  	finalizers := vzstring.RemoveStringFromSlice(cluster.GetFinalizers(), finalizerName)
   173  	cluster.SetFinalizers(finalizers)
   174  
   175  	if err := r.Update(context.TODO(), cluster); err != nil {
   176  		return err
   177  	}
   178  	return nil
   179  }
   180  
   181  // getClusterDisplayName returns the displayName spec field from the Rancher cluster resource
   182  func (r *RancherClusterReconciler) getClusterDisplayName(cluster *unstructured.Unstructured) (string, error) {
   183  	displayName, ok, err := unstructured.NestedString(cluster.Object, "spec", "displayName")
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	if !ok {
   188  		return "", fmt.Errorf("Could not find spec displayName field in Cattle Cluster resource: %s", cluster.GetName())
   189  	}
   190  	return displayName, nil
   191  }
   192  
   193  // ensureVMC ensures that a VMC exists for the Rancher cluster. It will also set the Rancher cluster id in the VMC status
   194  // if it is not already set.
   195  func (r *RancherClusterReconciler) ensureVMC(cluster *unstructured.Unstructured) error {
   196  	// ignore the "local" cluster
   197  	if localClusterName == cluster.GetName() {
   198  		return nil
   199  	}
   200  
   201  	displayName, err := r.getClusterDisplayName(cluster)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	// attempt to create the VMC, if it already exists we just ignore the error
   207  	vmc := newVMC(displayName)
   208  	if err := r.Create(context.TODO(), vmc); err != nil {
   209  		if !errors.IsAlreadyExists(err) {
   210  			r.Log.Errorf("Unable to create VMC with name %s: %v", displayName, err)
   211  			return err
   212  		}
   213  		r.Log.Debugf("VMC %s already exists", displayName)
   214  	} else {
   215  		r.Log.Infof("Created VMC for discovered Rancher cluster with name: %s", displayName)
   216  	}
   217  
   218  	// read back the VMC and if the cluster id isn't set in the status, set it
   219  	if err := r.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc); err != nil {
   220  		r.Log.Errorf("Unable to get VMC with name: %s", displayName)
   221  		return err
   222  	}
   223  	if vmc.Status.RancherRegistration.ClusterID == "" {
   224  		// Rancher cattle cluster resource name is also the Rancher cluster id
   225  		r.Log.Debugf("Updating VMC %s status with cluster id: %s", displayName, cluster.GetName())
   226  		vmc.Status.RancherRegistration.ClusterID = cluster.GetName()
   227  		if err := r.Status().Update(context.TODO(), vmc); err != nil {
   228  			r.Log.Errorf("Unable to update VMC %s status: %v", displayName, err)
   229  			return err
   230  		}
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  // DeleteVMC deletes the VMC associated with a Rancher cluster
   237  func (r *RancherClusterReconciler) DeleteVMC(cluster *unstructured.Unstructured) error {
   238  	// ignore the "local" cluster
   239  	if localClusterName == cluster.GetName() {
   240  		return nil
   241  	}
   242  
   243  	displayName, err := r.getClusterDisplayName(cluster)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
   249  	if err := r.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc); err != nil {
   250  		if errors.IsNotFound(err) {
   251  			r.Log.Debugf("VMC %s does not exist, nothing to do", displayName)
   252  			return nil
   253  		}
   254  		return err
   255  	}
   256  
   257  	// if the VMC has a cluster id in the status, delete the VMC
   258  	if len(vmc.Status.RancherRegistration.ClusterID) > 0 {
   259  		r.Log.Infof("Deleting VMC %s because it is no longer in Rancher", vmc.Name)
   260  		if err := r.Delete(context.TODO(), vmc); client.IgnoreNotFound(err) != nil {
   261  			r.Log.Errorf("Unable to delete VMC %s: %v", vmc.Name, err)
   262  			return err
   263  		}
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  // newVMC returns a minimally populated VMC object
   270  func newVMC(name string) *clustersv1alpha1.VerrazzanoManagedCluster {
   271  	return &clustersv1alpha1.VerrazzanoManagedCluster{
   272  		ObjectMeta: metav1.ObjectMeta{
   273  			Name:      name,
   274  			Namespace: vzconst.VerrazzanoMultiClusterNamespace,
   275  			Labels: map[string]string{
   276  				CreatedByLabel:                    CreatedByVerrazzano,
   277  				vzconst.VerrazzanoManagedLabelKey: "true",
   278  			},
   279  		},
   280  	}
   281  }