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 }