github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/rancher/rancher_cluster_controller_test.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 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "go.uber.org/zap" 12 "k8s.io/apimachinery/pkg/api/errors" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 "k8s.io/apimachinery/pkg/runtime" 16 "k8s.io/apimachinery/pkg/types" 17 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 18 ctrl "sigs.k8s.io/controller-runtime" 19 "sigs.k8s.io/controller-runtime/pkg/client" 20 "sigs.k8s.io/controller-runtime/pkg/client/fake" 21 22 clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 23 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 24 ) 25 26 const ( 27 clusterName = "c-m-hcknpvs7" 28 displayName = "unit-test-cluster" 29 ) 30 31 // GIVEN a Rancher cluster resource is created 32 // WHEN the reconciler runs 33 // THEN a VMC is created and the cluster id is set in the status 34 func TestReconcileCreateVMC(t *testing.T) { 35 asserts := assert.New(t) 36 37 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName)).Build() 38 reconciler := newRancherClusterReconciler(fakeClient) 39 request := newRequest(clusterName) 40 41 _, err := reconciler.Reconcile(context.TODO(), request) 42 asserts.NoError(err) 43 44 // expect that a VMC was created and that the cluster id is set in the status 45 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{} 46 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 47 asserts.NoError(err) 48 asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID) 49 } 50 51 // GIVEN a Rancher cluster resource is created with labels 52 // AND the user has specified a cluster selector that matches the labels 53 // WHEN the reconciler runs 54 // THEN a VMC is created and the cluster id is set in the status 55 func TestReconcileWithClusterSelector(t *testing.T) { 56 asserts := assert.New(t) 57 58 const ( 59 labelName = "test-label" 60 labelValue = "test-label-value" 61 ) 62 63 cluster := newCattleCluster(clusterName, displayName) 64 cluster.SetLabels(map[string]string{labelName: labelValue}) 65 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build() 66 67 reconciler := newRancherClusterReconciler(fakeClient) 68 reconciler.ClusterSelector = &metav1.LabelSelector{ 69 MatchExpressions: []metav1.LabelSelectorRequirement{ 70 { 71 Key: labelName, 72 Operator: metav1.LabelSelectorOpIn, 73 Values: []string{labelValue}, 74 }, 75 }, 76 } 77 request := newRequest(clusterName) 78 79 _, err := reconciler.Reconcile(context.TODO(), request) 80 asserts.NoError(err) 81 82 // expect that a VMC was created and that the cluster id is set in the status 83 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{} 84 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 85 asserts.NoError(err) 86 asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID) 87 } 88 89 // GIVEN a Rancher cluster resource is created with labels 90 // AND the user has specified a cluster selector that DOES NOT match the labels 91 // WHEN the reconciler runs 92 // THEN a VMC is not created 93 func TestReconcileClusterSelectorMismatch(t *testing.T) { 94 asserts := assert.New(t) 95 96 const ( 97 labelName = "test-label" 98 labelValue = "test-label-value" 99 ) 100 101 cluster := newCattleCluster(clusterName, displayName) 102 cluster.SetLabels(map[string]string{"label": "does-not-match"}) 103 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build() 104 105 reconciler := newRancherClusterReconciler(fakeClient) 106 reconciler.ClusterSelector = &metav1.LabelSelector{ 107 MatchExpressions: []metav1.LabelSelectorRequirement{ 108 { 109 Key: labelName, 110 Operator: metav1.LabelSelectorOpIn, 111 Values: []string{labelValue}, 112 }, 113 }, 114 } 115 request := newRequest(clusterName) 116 117 _, err := reconciler.Reconcile(context.TODO(), request) 118 asserts.NoError(err) 119 120 // expect that a VMC was not created 121 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{} 122 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 123 asserts.Error(err) 124 asserts.True(errors.IsNotFound(err)) 125 } 126 127 // GIVEN a Rancher cluster resource is created 128 // AND Rancher cluster sync is disabled 129 // WHEN the reconciler runs 130 // THEN a VMC is not created 131 func TestReconcileClusterSyncDisabled(t *testing.T) { 132 asserts := assert.New(t) 133 134 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName)).Build() 135 reconciler := newRancherClusterReconciler(fakeClient) 136 reconciler.ClusterSyncEnabled = false 137 request := newRequest(clusterName) 138 139 _, err := reconciler.Reconcile(context.TODO(), request) 140 asserts.NoError(err) 141 142 // expect that a VMC was not created 143 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{} 144 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 145 asserts.Error(err) 146 asserts.True(errors.IsNotFound(err)) 147 } 148 149 // GIVEN a Rancher cluster resource is created and a VMC already exists for the cluster 150 // WHEN the reconciler runs 151 // THEN the VMC is updated and the cluster id is set in the status 152 func TestReconcileCreateVMCAlreadyExists(t *testing.T) { 153 asserts := assert.New(t) 154 155 vmc := newVMC(displayName) 156 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName), vmc).Build() 157 reconciler := newRancherClusterReconciler(fakeClient) 158 request := newRequest(clusterName) 159 160 _, err := reconciler.Reconcile(context.TODO(), request) 161 asserts.NoError(err) 162 163 // expect the VMC still exists and that the cluster id is set in the status 164 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 165 asserts.NoError(err) 166 asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID) 167 } 168 169 // TestReconcileDeleteVMC tests reconciling and deleting VMCs. 170 func TestReconcileDeleteVMC(t *testing.T) { 171 asserts := assert.New(t) 172 173 // GIVEN a Rancher cluster resource is being deleted 174 // AND cluster sync is enabled 175 // WHEN the reconciler runs 176 // THEN the corresponding VMC is deleted 177 cluster := newCattleCluster(clusterName, displayName) 178 now := metav1.Now() 179 cluster.SetDeletionTimestamp(&now) 180 cluster.SetFinalizers([]string{finalizerName}) 181 vmc := newVMC(displayName) 182 vmc.Status.RancherRegistration.ClusterID = clusterName 183 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build() 184 185 reconciler := newRancherClusterReconciler(fakeClient) 186 request := newRequest(clusterName) 187 188 _, err := reconciler.Reconcile(context.TODO(), request) 189 asserts.NoError(err) 190 191 // expect that the VMC was deleted 192 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 193 asserts.Error(err) 194 asserts.True(errors.IsNotFound(err)) 195 196 // since the last finalizer was removed from the Rancher cluster, the cluster should be gone as well 197 cluster = &unstructured.Unstructured{} 198 cluster.SetGroupVersionKind(gvk) 199 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: clusterName}, cluster) 200 asserts.Error(err) 201 asserts.True(errors.IsNotFound(err)) 202 203 // GIVEN a Rancher cluster resource is being deleted 204 // AND cluster sync is disabled 205 // WHEN the reconciler runs 206 // THEN the corresponding VMC is deleted 207 cluster = newCattleCluster(clusterName, displayName) 208 cluster.SetDeletionTimestamp(&now) 209 cluster.SetFinalizers([]string{finalizerName}) 210 vmc = newVMC(displayName) 211 vmc.Status.RancherRegistration.ClusterID = clusterName 212 fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build() 213 214 reconciler = newRancherClusterReconciler(fakeClient) 215 // disable cluster sync, the VMC (if it exists) should be deleted even if this flag is false 216 reconciler.ClusterSyncEnabled = false 217 request = newRequest(clusterName) 218 219 _, err = reconciler.Reconcile(context.TODO(), request) 220 asserts.NoError(err) 221 222 // expect that the VMC was deleted 223 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 224 asserts.Error(err) 225 asserts.True(errors.IsNotFound(err)) 226 227 // since the last finalizer was removed from the Rancher cluster, the cluster should be gone as well 228 cluster = &unstructured.Unstructured{} 229 cluster.SetGroupVersionKind(gvk) 230 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: clusterName}, cluster) 231 asserts.Error(err) 232 asserts.True(errors.IsNotFound(err)) 233 } 234 235 // GIVEN a Rancher cluster resource is being deleted and the VMC does not exist 236 // WHEN the reconciler runs 237 // THEN no error is returned 238 func TestReconcileDeleteVMCNotFound(t *testing.T) { 239 asserts := assert.New(t) 240 241 cluster := newCattleCluster(clusterName, displayName) 242 now := metav1.Now() 243 cluster.SetDeletionTimestamp(&now) 244 cluster.SetFinalizers([]string{finalizerName}) 245 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build() 246 reconciler := newRancherClusterReconciler(fakeClient) 247 request := newRequest(clusterName) 248 249 _, err := reconciler.Reconcile(context.TODO(), request) 250 asserts.NoError(err) 251 } 252 253 // GIVEN a Rancher cluster resource has been deleted 254 // WHEN the reconciler runs 255 // THEN no error is returned 256 func TestReconcileClusterGone(t *testing.T) { 257 asserts := assert.New(t) 258 259 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).Build() 260 reconciler := newRancherClusterReconciler(fakeClient) 261 request := newRequest(clusterName) 262 263 _, err := reconciler.Reconcile(context.TODO(), request) 264 asserts.NoError(err) 265 } 266 267 // GIVEN the local Rancher cluster resource is being reconciled 268 // WHEN the reconciler runs 269 // THEN no VMC is created 270 func TestReconcileLocalCluster(t *testing.T) { 271 asserts := assert.New(t) 272 273 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(localClusterName, localClusterName)).Build() 274 reconciler := newRancherClusterReconciler(fakeClient) 275 request := newRequest(clusterName) 276 277 _, err := reconciler.Reconcile(context.TODO(), request) 278 asserts.NoError(err) 279 280 // expect no VMC created for the local cluster 281 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{} 282 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: localClusterName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 283 asserts.True(errors.IsNotFound(err)) 284 } 285 286 // GIVEN a GVK exists for cattle clusters resources 287 // WHEN we fetch the client object for the cattle clusters GVK 288 // THEN the returned client object is not nil 289 func TestCattleClusterClientObject(t *testing.T) { 290 asserts := assert.New(t) 291 obj := CattleClusterClientObject() 292 asserts.NotNil(obj) 293 } 294 295 func TestParseClusterErrorCases(t *testing.T) { 296 // GIVEN a cluster resource with no spec displayName field set 297 // WHEN we attempt to get the displayName from the cluster object 298 // THEN the expected error is returned 299 asserts := assert.New(t) 300 cluster := &unstructured.Unstructured{} 301 cluster.SetGroupVersionKind(gvk) 302 reconciler := newRancherClusterReconciler(nil) 303 _, err := reconciler.getClusterDisplayName(cluster) 304 asserts.ErrorContains(err, "Could not find spec displayName field") 305 306 // GIVEN a cluster resource with a bad type for the spec field 307 // WHEN we attempt to get the displayName from the cluster object 308 // THEN the expected error is returned 309 unstructured.SetNestedField(cluster.Object, true, "spec") 310 _, err = reconciler.getClusterDisplayName(cluster) 311 asserts.ErrorContains(err, ".spec.displayName accessor error") 312 } 313 314 // TestDeleteVMC tests the DeleteVMC function 315 func TestDeleteVMC(t *testing.T) { 316 asserts := assert.New(t) 317 cluster := newCattleCluster(clusterName, displayName) 318 now := metav1.Now() 319 cluster.SetDeletionTimestamp(&now) 320 cluster.SetFinalizers([]string{finalizerName}) 321 vmc := newVMC(displayName) 322 vmc.Status.RancherRegistration.ClusterID = clusterName 323 fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build() 324 reconciler := newRancherClusterReconciler(fakeClient) 325 326 // GIVEN a VMC exists 327 // WHEN DeleteVMC is called 328 // THEN the VMC is deleted 329 err := reconciler.DeleteVMC(cluster) 330 asserts.NoError(err) 331 332 // expect that the VMC was deleted 333 err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc) 334 asserts.True(errors.IsNotFound(err)) 335 // GIVEN no VMC exists 336 // WHEN DeleteVMC is called 337 // THEN no error is returned 338 err = reconciler.DeleteVMC(cluster) 339 asserts.NoError(err) 340 } 341 342 func newRequest(name string) ctrl.Request { 343 return ctrl.Request{ 344 NamespacedName: types.NamespacedName{ 345 Name: name, 346 }, 347 } 348 } 349 350 func newRancherClusterReconciler(c client.Client) RancherClusterReconciler { 351 return RancherClusterReconciler{ 352 Client: c, 353 Scheme: newScheme(), 354 ClusterSyncEnabled: true, 355 Log: zap.S(), 356 } 357 } 358 359 func newScheme() *runtime.Scheme { 360 scheme := runtime.NewScheme() 361 clientgoscheme.AddToScheme(scheme) 362 clustersv1alpha1.AddToScheme(scheme) 363 return scheme 364 } 365 366 func newCattleCluster(name, displayName string) *unstructured.Unstructured { 367 cluster := &unstructured.Unstructured{} 368 cluster.SetGroupVersionKind(gvk) 369 cluster.SetName(name) 370 unstructured.SetNestedField(cluster.Object, displayName, "spec", "displayName") 371 return cluster 372 }