github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/controllers/vmc/vmc_controller_test.go (about) 1 // Copyright (c) 2021, 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 vmc 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "net/http" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/Jeffail/gabs/v2" 17 "github.com/golang/mock/gomock" 18 "github.com/prometheus/client_golang/prometheus/testutil" 19 "github.com/stretchr/testify/assert" 20 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 21 "github.com/verrazzano/verrazzano/pkg/constants" 22 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 23 "github.com/verrazzano/verrazzano/pkg/mcconstants" 24 "github.com/verrazzano/verrazzano/pkg/metricsutils" 25 "github.com/verrazzano/verrazzano/pkg/rancherutil" 26 "github.com/verrazzano/verrazzano/pkg/test/mockmatchers" 27 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 28 vpoconstants "github.com/verrazzano/verrazzano/platform-operator/constants" 29 "github.com/verrazzano/verrazzano/platform-operator/mocks" 30 corev1 "k8s.io/api/core/v1" 31 networkingv1 "k8s.io/api/networking/v1" 32 rbacv1 "k8s.io/api/rbac/v1" 33 apiv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 34 "k8s.io/apimachinery/pkg/api/errors" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 37 "k8s.io/apimachinery/pkg/runtime" 38 "k8s.io/apimachinery/pkg/runtime/schema" 39 "k8s.io/apimachinery/pkg/types" 40 "k8s.io/apimachinery/pkg/util/wait" 41 "k8s.io/client-go/rest" 42 ctrl "sigs.k8s.io/controller-runtime" 43 "sigs.k8s.io/controller-runtime/pkg/client" 44 ) 45 46 const apiVersion = "clusters.verrazzano.io/v1alpha1" 47 const kind = "VerrazzanoManagedCluster" 48 49 const ( 50 token = "tokenData" 51 testManagedCluster = "test" 52 rancherAgentRegistry = "ghcr.io" 53 rancherAgentImage = rancherAgentRegistry + "/verrazzano/rancher-agent:v1.0.0" 54 rancherAdminSecret = "rancher-admin-secret" 55 unitTestRancherClusterID = "unit-test-rancher-cluster-id" 56 ) 57 58 var ( 59 loginURLParts = strings.Split(loginPath, "?") 60 loginURIPath = loginURLParts[0] 61 loginQueryString = loginURLParts[1] 62 ) 63 64 const rancherManifestYAML = ` 65 apiVersion: apps/v1 66 kind: Deployment 67 metadata: 68 name: cattle-cluster-agent 69 namespace: cattle-system 70 spec: 71 template: 72 spec: 73 containers: 74 - name: cluster-register 75 image: ` + rancherAgentImage + ` 76 imagePullPolicy: IfNotPresent 77 ` 78 79 type AssertFn func(configMap *corev1.ConfigMap) error 80 type secretAssertFn func(secret *corev1.Secret) error 81 82 func init() { 83 // stub out keycloak client creation for these tests 84 createClient = func(r *VerrazzanoManagedClusterReconciler, vmc *v1alpha1.VerrazzanoManagedCluster) error { 85 return nil 86 } 87 } 88 89 // TestCreateVMC tests the Reconcile method for the following use case 90 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 91 // WHEN a VerrazzanoManagedCluster resource has been applied in a cluster where Rancher is enabled 92 // THEN ensure all the objects are created correctly 93 func TestCreateVMCRancherEnabled(t *testing.T) { 94 // with feature flag disabled (which triggers different asserts/mocks from enabled) 95 doTestCreateVMC(t, true) 96 } 97 98 // TestCreateVMC tests the Reconcile method for the following use case 99 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 100 // WHEN a VerrazzanoManagedCluster resource has been applied in a cluster where Rancher is DISABLED 101 // THEN ensure all the objects are created correctly 102 func TestCreateVMCRancherDisabled(t *testing.T) { 103 doTestCreateVMC(t, false) 104 } 105 106 func doTestCreateVMC(t *testing.T, rancherEnabled bool) { 107 // clear any cached user auth tokens when the test completes 108 defer rancherutil.DeleteStoredTokens() 109 110 namespace := constants.VerrazzanoMultiClusterNamespace 111 asserts := assert.New(t) 112 mocker := gomock.NewController(t) 113 mock := mocks.NewMockClient(mocker) 114 mockStatus := mocks.NewMockStatusWriter(mocker) 115 asserts.NotNil(mockStatus) 116 117 mockRequestSender := mocks.NewMockRequestSender(mocker) 118 savedRancherHTTPClient := rancherutil.RancherHTTPClient 119 defer func() { 120 rancherutil.RancherHTTPClient = savedRancherHTTPClient 121 }() 122 rancherutil.RancherHTTPClient = mockRequestSender 123 124 defer setConfigFunc(getConfigFunc) 125 setConfigFunc(fakeGetConfig) 126 127 rancherClusterState := rancherClusterStateInactive 128 caSecretExistsInVMC := true 129 expectVmcGetAndUpdate(t, mock, testManagedCluster, caSecretExistsInVMC, false) 130 expectSyncServiceAccount(t, mock, testManagedCluster, true) 131 expectSyncRoleBinding(t, mock, testManagedCluster, true) 132 // Agent secret sync checks depend on whether Rancher is enabled 133 expectSyncAgent(t, mock, testManagedCluster, rancherEnabled, false) 134 expectMockCallsForListingRancherUsers(mock) 135 expectSyncRegistration(t, mock, testManagedCluster, false, true) 136 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 137 expectRancherConfigK8sCalls(t, mock, false) 138 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 139 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 140 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 141 // expect status updated with condition Ready=true 142 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 143 expectSyncPrometheusScraper(t, mock, testManagedCluster, "", "", true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 144 asserts.Len(configMap.Data, 2, "no data found") 145 asserts.NotEmpty(configMap.Data["ca-test"], "No cert entry found") 146 prometheusYaml := configMap.Data["prometheus.yml"] 147 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 148 149 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 150 if err != nil { 151 asserts.Fail("failed due to error %v", err) 152 } 153 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 154 return nil 155 }, func(secret *corev1.Secret) error { 156 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 157 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 158 if err != nil { 159 asserts.Fail("failed due to error %v", err) 160 } 161 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 162 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 163 return nil 164 }, func(secret *corev1.Secret) error { 165 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 166 return nil 167 }) 168 169 // Create and make the request 170 request := newRequest(namespace, testManagedCluster) 171 reconciler := newVMCReconciler(mock) 172 result, err := reconciler.Reconcile(context.TODO(), request) 173 174 // Validate the results 175 mocker.Finish() 176 asserts.NoError(err) 177 asserts.Equal(true, result.Requeue) 178 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 179 } 180 181 // TestCreateVMC tests the Reconcile method for the following use case 182 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 183 // WHEN a VerrazzanoManagedCluster resource has been applied on a Verrazzano install configured with external ES 184 // THEN ensure all the objects are created 185 func TestCreateVMCWithExternalES(t *testing.T) { 186 // clear any cached user auth tokens when the test completes 187 defer rancherutil.DeleteStoredTokens() 188 189 namespace := constants.VerrazzanoMultiClusterNamespace 190 asserts := assert.New(t) 191 mocker := gomock.NewController(t) 192 mock := mocks.NewMockClient(mocker) 193 mockStatus := mocks.NewMockStatusWriter(mocker) 194 asserts.NotNil(mockStatus) 195 196 mockRequestSender := mocks.NewMockRequestSender(mocker) 197 savedRancherHTTPClient := rancherutil.RancherHTTPClient 198 defer func() { 199 rancherutil.RancherHTTPClient = savedRancherHTTPClient 200 }() 201 rancherutil.RancherHTTPClient = mockRequestSender 202 203 defer setConfigFunc(getConfigFunc) 204 setConfigFunc(fakeGetConfig) 205 rancherClusterState := rancherClusterStateInactive 206 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, false) 207 expectSyncServiceAccount(t, mock, testManagedCluster, true) 208 expectSyncRoleBinding(t, mock, testManagedCluster, true) 209 expectSyncAgent(t, mock, testManagedCluster, false, true) 210 expectMockCallsForListingRancherUsers(mock) 211 expectSyncRegistration(t, mock, testManagedCluster, true, true) 212 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 213 expectRancherConfigK8sCalls(t, mock, false) 214 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 215 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 216 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 217 expectSyncPrometheusScraper(nil, mock, testManagedCluster, "", "", true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 218 asserts.Len(configMap.Data, 2, "no data found") 219 asserts.NotEmpty(configMap.Data["ca-test"], "No cert entry found") 220 prometheusYaml := configMap.Data["prometheus.yml"] 221 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 222 223 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 224 if err != nil { 225 asserts.Fail("failed due to error %v", err) 226 } 227 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 228 return nil 229 }, func(secret *corev1.Secret) error { 230 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 231 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 232 if err != nil { 233 asserts.Fail("failed due to error %v", err) 234 } 235 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 236 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 237 return nil 238 }, func(secret *corev1.Secret) error { 239 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 240 return nil 241 }) 242 243 // expect status updated with condition Ready=true 244 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 245 246 // Create and make the request 247 request := newRequest(namespace, testManagedCluster) 248 reconciler := newVMCReconciler(mock) 249 result, err := reconciler.Reconcile(context.TODO(), request) 250 251 // Validate the results 252 mocker.Finish() 253 asserts.NoError(err) 254 asserts.Equal(true, result.Requeue) 255 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 256 } 257 258 // TestCreateVMC tests the Reconcile method for the following use case 259 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource for an OCI DNS cluster 260 // WHEN a VerrazzanoManagedCluster resource has been applied 261 // THEN ensure all the objects are created 262 func TestCreateVMCOCIDNS(t *testing.T) { 263 // clear any cached user auth tokens when the test completes 264 defer rancherutil.DeleteStoredTokens() 265 266 namespace := "verrazzano-mc" 267 asserts := assert.New(t) 268 mocker := gomock.NewController(t) 269 mock := mocks.NewMockClient(mocker) 270 mockStatus := mocks.NewMockStatusWriter(mocker) 271 asserts.NotNil(mockStatus) 272 273 mockRequestSender := mocks.NewMockRequestSender(mocker) 274 savedRancherHTTPClient := rancherutil.RancherHTTPClient 275 defer func() { 276 rancherutil.RancherHTTPClient = savedRancherHTTPClient 277 }() 278 rancherutil.RancherHTTPClient = mockRequestSender 279 280 defer setConfigFunc(getConfigFunc) 281 setConfigFunc(fakeGetConfig) 282 rancherClusterState := rancherClusterStateInactive 283 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, false) 284 expectSyncServiceAccount(t, mock, testManagedCluster, true) 285 expectSyncRoleBinding(t, mock, testManagedCluster, true) 286 expectSyncAgent(t, mock, testManagedCluster, false, true) 287 expectMockCallsForListingRancherUsers(mock) 288 expectSyncRegistration(t, mock, testManagedCluster, false, true) 289 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 290 expectRancherConfigK8sCalls(t, mock, false) 291 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 292 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 293 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 294 expectSyncPrometheusScraper(nil, mock, testManagedCluster, "", "", true, "", func(configMap *corev1.ConfigMap) error { 295 asserts.Len(configMap.Data, 2, "no data found") 296 asserts.Empty(configMap.Data["ca-test"], "Cert entry found") 297 prometheusYaml := configMap.Data["prometheus.yml"] 298 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 299 300 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 301 if err != nil { 302 asserts.Fail("failed due to error %v", err) 303 } 304 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, false) 305 return nil 306 }, func(secret *corev1.Secret) error { 307 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 308 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 309 if err != nil { 310 asserts.Fail("failed due to error %v", err) 311 } 312 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 313 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, false) 314 return nil 315 }, func(secret *corev1.Secret) error { 316 asserts.Empty(secret.Data["ca-test"]) 317 return nil 318 }) 319 320 // expect status updated with condition Ready=true 321 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 322 323 // Create and make the request 324 request := newRequest(namespace, testManagedCluster) 325 reconciler := newVMCReconciler(mock) 326 result, err := reconciler.Reconcile(context.TODO(), request) 327 328 // Validate the results 329 mocker.Finish() 330 asserts.NoError(err) 331 asserts.Equal(true, result.Requeue) 332 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 333 } 334 335 // TestCreateVMCNoCACert tests the Reconcile method for the following use case 336 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 337 // WHEN a VerrazzanoManagedCluster resource has been applied with no CA Cert 338 // THEN ensure all the objects are created 339 func TestCreateVMCNoCACert(t *testing.T) { 340 // clear any cached user auth tokens when the test completes 341 defer rancherutil.DeleteStoredTokens() 342 343 namespace := constants.VerrazzanoMultiClusterNamespace 344 asserts := assert.New(t) 345 mocker := gomock.NewController(t) 346 mock := mocks.NewMockClient(mocker) 347 mockStatus := mocks.NewMockStatusWriter(mocker) 348 asserts.NotNil(mockStatus) 349 350 mockRequestSender := mocks.NewMockRequestSender(mocker) 351 savedRancherHTTPClient := rancherutil.RancherHTTPClient 352 defer func() { 353 rancherutil.RancherHTTPClient = savedRancherHTTPClient 354 }() 355 rancherutil.RancherHTTPClient = mockRequestSender 356 357 defer setConfigFunc(getConfigFunc) 358 setConfigFunc(fakeGetConfig) 359 rancherClusterState := rancherClusterStateActive 360 vzNSExists := false 361 expectVmcGetAndUpdate(t, mock, testManagedCluster, false, false) 362 expectSyncServiceAccount(t, mock, testManagedCluster, true) 363 expectSyncRoleBinding(t, mock, testManagedCluster, true) 364 expectSyncAgent(t, mock, testManagedCluster, false, true) 365 expectMockCallsForListingRancherUsers(mock) 366 expectSyncRegistration(t, mock, testManagedCluster, true, true) 367 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 368 expectRancherConfigK8sCalls(t, mock, true) 369 expectSyncCACertRancherHTTPCalls(t, mockRequestSender, "", rancherClusterState) 370 expectRancherConfigK8sCalls(t, mock, false) 371 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 372 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, vzNSExists) 373 expectSyncPrometheusScraper(nil, mock, testManagedCluster, "", "", false, getCaCrt(), func(configMap *corev1.ConfigMap) error { 374 asserts.Len(configMap.Data, 2, "no data found") 375 prometheusYaml := configMap.Data["prometheus.yml"] 376 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 377 378 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 379 if err != nil { 380 asserts.Fail("failed due to error %v", err) 381 } 382 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, false) 383 return nil 384 }, func(secret *corev1.Secret) error { 385 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 386 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 387 if err != nil { 388 asserts.Fail("failed due to error %v", err) 389 } 390 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 391 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, false) 392 return nil 393 }, func(secret *corev1.Secret) error { 394 asserts.Empty(secret.Data["ca-test"]) 395 return nil 396 }) 397 398 // expect status updated with condition Ready=true and ManagedCARetrieved condition is not set because we don't provide 399 // a non-zero length managed ca cert 400 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 401 402 // Create and make the request 403 request := newRequest(namespace, testManagedCluster) 404 reconciler := newVMCReconciler(mock) 405 result, err := reconciler.Reconcile(context.TODO(), request) 406 407 // Validate the results 408 mocker.Finish() 409 asserts.NoError(err) 410 asserts.Equal(true, result.Requeue) 411 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 412 } 413 414 // TestCreateVMCFetchCACertFromManagedCluster tests the Reconcile method for the following use case 415 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 416 // WHEN a VerrazzanoManagedCluster resource has been applied and the caSecret field is NOT empty 417 // THEN ensure that we fetch the CA cert secret from the managed cluster and populate the caSecret field 418 func TestCreateVMCFetchCACertFromManagedCluster(t *testing.T) { 419 // clear any cached user auth tokens when the test completes 420 defer rancherutil.DeleteStoredTokens() 421 422 namespace := constants.VerrazzanoMultiClusterNamespace 423 asserts := assert.New(t) 424 mocker := gomock.NewController(t) 425 mock := mocks.NewMockClient(mocker) 426 mockStatus := mocks.NewMockStatusWriter(mocker) 427 asserts.NotNil(mockStatus) 428 429 mockRequestSender := mocks.NewMockRequestSender(mocker) 430 savedRancherHTTPClient := rancherutil.RancherHTTPClient 431 defer func() { 432 rancherutil.RancherHTTPClient = savedRancherHTTPClient 433 }() 434 rancherutil.RancherHTTPClient = mockRequestSender 435 436 defer setConfigFunc(getConfigFunc) 437 setConfigFunc(fakeGetConfig) 438 rancherClusterState := rancherClusterStateActive 439 vzNSExists := false 440 expectVmcGetAndUpdate(t, mock, testManagedCluster, false, false) 441 expectSyncServiceAccount(t, mock, testManagedCluster, true) 442 expectSyncRoleBinding(t, mock, testManagedCluster, true) 443 expectSyncAgent(t, mock, testManagedCluster, true, false) 444 expectMockCallsForListingRancherUsers(mock) 445 expectSyncRegistration(t, mock, testManagedCluster, true, true) 446 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 447 expectSyncCACertRancherHTTPCalls(t, mockRequestSender, `{"data":{"ca.crt":"base64-ca-cert"}}`, rancherClusterState) 448 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, true) 449 expectRancherConfigK8sCalls(t, mock, false) 450 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 451 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, vzNSExists) 452 expectSyncPrometheusScraper(nil, mock, testManagedCluster, "", "", true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 453 asserts.Len(configMap.Data, 2, "no data found") 454 prometheusYaml := configMap.Data["prometheus.yml"] 455 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 456 457 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 458 if err != nil { 459 asserts.Fail("failed due to error %v", err) 460 } 461 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 462 return nil 463 }, func(secret *corev1.Secret) error { 464 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 465 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 466 if err != nil { 467 asserts.Fail("failed due to error %v", err) 468 } 469 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 470 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 471 return nil 472 }, func(secret *corev1.Secret) error { 473 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 474 return nil 475 }) 476 477 // expect status updated with condition Ready=true and ManagedCARetrieved 478 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", true) 479 480 // Create and make the request 481 request := newRequest(namespace, testManagedCluster) 482 reconciler := newVMCReconciler(mock) 483 result, err := reconciler.Reconcile(context.TODO(), request) 484 485 // Validate the results 486 mocker.Finish() 487 asserts.NoError(err) 488 asserts.Equal(true, result.Requeue) 489 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 490 } 491 492 // TestCreateVMCWithExistingScrapeConfiguration tests the Reconcile method for the following use case 493 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 494 // WHEN a VerrazzanoManagedCluster resource has been applied and prometheus is already configured with a scrape config for the cluster 495 // THEN ensure all the objects are created 496 func TestCreateVMCWithExistingScrapeConfiguration(t *testing.T) { 497 // clear any cached user auth tokens when the test completes 498 defer rancherutil.DeleteStoredTokens() 499 500 namespace := "verrazzano-mc" 501 jobs := ` - ` + constants.PrometheusJobNameKey + `: cluster1 502 scrape_interval: 20s 503 scrape_timeout: 15s 504 scheme: http` 505 prometheusYaml := `global: 506 scrape_interval: 20s 507 scrape_timeout: 10s 508 evaluation_interval: 30s 509 scrape_configs: 510 ` + jobs 511 asserts := assert.New(t) 512 mocker := gomock.NewController(t) 513 mock := mocks.NewMockClient(mocker) 514 mockStatus := mocks.NewMockStatusWriter(mocker) 515 asserts.NotNil(mockStatus) 516 517 mockRequestSender := mocks.NewMockRequestSender(mocker) 518 savedRancherHTTPClient := rancherutil.RancherHTTPClient 519 defer func() { 520 rancherutil.RancherHTTPClient = savedRancherHTTPClient 521 }() 522 rancherutil.RancherHTTPClient = mockRequestSender 523 524 defer setConfigFunc(getConfigFunc) 525 setConfigFunc(fakeGetConfig) 526 rancherClusterState := rancherClusterStateInactive 527 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, false) 528 expectSyncServiceAccount(t, mock, testManagedCluster, true) 529 expectSyncRoleBinding(t, mock, testManagedCluster, true) 530 expectSyncAgent(t, mock, testManagedCluster, false, false) 531 expectMockCallsForListingRancherUsers(mock) 532 expectSyncRegistration(t, mock, testManagedCluster, false, true) 533 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 534 expectRancherConfigK8sCalls(t, mock, false) 535 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 536 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 537 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 538 expectSyncPrometheusScraper(nil, mock, testManagedCluster, prometheusYaml, jobs, true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 539 540 // check for the modified entry 541 asserts.Len(configMap.Data, 2, "no data found") 542 asserts.NotEmpty(configMap.Data["ca-test"], "No cert entry found") 543 prometheusYaml := configMap.Data["prometheus.yml"] 544 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 545 546 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 547 if err != nil { 548 asserts.Fail("failed due to error %v", err) 549 } 550 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 551 return nil 552 }, func(secret *corev1.Secret) error { 553 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 554 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 555 if err != nil { 556 asserts.Fail("failed due to error %v", err) 557 } 558 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 559 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 560 return nil 561 }, func(secret *corev1.Secret) error { 562 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 563 return nil 564 }) 565 566 // expect status updated with condition Ready=true 567 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 568 569 // Create and make the request 570 request := newRequest(namespace, testManagedCluster) 571 reconciler := newVMCReconciler(mock) 572 result, err := reconciler.Reconcile(context.TODO(), request) 573 574 // Validate the results 575 mocker.Finish() 576 asserts.NoError(err) 577 asserts.Equal(true, result.Requeue) 578 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 579 } 580 581 // TestReplaceExistingScrapeConfiguration tests the Reconcile method for the following use case 582 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 583 // WHEN a VerrazzanoManagedCluster resource has been applied and prometheus is already configured with a scrape configuration for the same cluster 584 // THEN ensure all the objects are created (existing configuration is replaced) 585 func TestReplaceExistingScrapeConfiguration(t *testing.T) { 586 // clear any cached user auth tokens when the test completes 587 defer rancherutil.DeleteStoredTokens() 588 589 namespace := "verrazzano-mc" 590 jobs := ` - ` + constants.PrometheusJobNameKey + `: test 591 scrape_interval: 20s 592 scrape_timeout: 15s 593 scheme: http` 594 prometheusYaml := `global: 595 scrape_interval: 20s 596 scrape_timeout: 10s 597 evaluation_interval: 30s 598 scrape_configs: 599 ` + jobs 600 asserts := assert.New(t) 601 mocker := gomock.NewController(t) 602 mock := mocks.NewMockClient(mocker) 603 mockStatus := mocks.NewMockStatusWriter(mocker) 604 asserts.NotNil(mockStatus) 605 606 mockRequestSender := mocks.NewMockRequestSender(mocker) 607 savedRancherHTTPClient := rancherutil.RancherHTTPClient 608 defer func() { 609 rancherutil.RancherHTTPClient = savedRancherHTTPClient 610 }() 611 rancherutil.RancherHTTPClient = mockRequestSender 612 613 defer setConfigFunc(getConfigFunc) 614 setConfigFunc(fakeGetConfig) 615 rancherClusterState := rancherClusterStateInactive 616 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, false) 617 expectSyncServiceAccount(t, mock, testManagedCluster, true) 618 expectSyncRoleBinding(t, mock, testManagedCluster, true) 619 expectSyncAgent(t, mock, testManagedCluster, false, true) 620 expectMockCallsForListingRancherUsers(mock) 621 expectSyncRegistration(t, mock, testManagedCluster, false, true) 622 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, false, rancherManifestYAML, true, rancherClusterState) 623 expectRancherConfigK8sCalls(t, mock, false) 624 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 625 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 626 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 627 expectSyncPrometheusScraper(nil, mock, testManagedCluster, prometheusYaml, jobs, true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 628 629 asserts.Len(configMap.Data, 2, "no data found") 630 asserts.NotNil(configMap.Data["ca-test"], "No cert entry found") 631 prometheusYaml := configMap.Data["prometheus.yml"] 632 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 633 634 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 635 if err != nil { 636 asserts.Fail("failed due to error %v", err) 637 } 638 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 639 return nil 640 }, func(secret *corev1.Secret) error { 641 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 642 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 643 if err != nil { 644 asserts.Fail("failed due to error %v", err) 645 } 646 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 647 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 648 return nil 649 }, func(secret *corev1.Secret) error { 650 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 651 return nil 652 }) 653 654 // expect status updated with condition Ready=true 655 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 656 657 // Create and make the request 658 request := newRequest(namespace, testManagedCluster) 659 reconciler := newVMCReconciler(mock) 660 result, err := reconciler.Reconcile(context.TODO(), request) 661 662 // Validate the results 663 mocker.Finish() 664 asserts.NoError(err) 665 asserts.Equal(true, result.Requeue) 666 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 667 } 668 669 // TestCreateVMC tests the Reconcile method for the following use case 670 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 671 // WHEN a VerrazzanoManagedCluster resource has been applied 672 // AND the cluster has already been registered with Rancher 673 // THEN ensure all the objects are created 674 func TestCreateVMCClusterAlreadyRegistered(t *testing.T) { 675 // clear any cached user auth tokens when the test completes 676 defer rancherutil.DeleteStoredTokens() 677 678 namespace := constants.VerrazzanoMultiClusterNamespace 679 asserts := assert.New(t) 680 mocker := gomock.NewController(t) 681 mock := mocks.NewMockClient(mocker) 682 mockStatus := mocks.NewMockStatusWriter(mocker) 683 asserts.NotNil(mockStatus) 684 685 mockRequestSender := mocks.NewMockRequestSender(mocker) 686 savedRancherHTTPClient := rancherutil.RancherHTTPClient 687 defer func() { 688 rancherutil.RancherHTTPClient = savedRancherHTTPClient 689 }() 690 rancherutil.RancherHTTPClient = mockRequestSender 691 692 defer setConfigFunc(getConfigFunc) 693 setConfigFunc(fakeGetConfig) 694 rancherClusterState := rancherClusterStateInactive 695 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, true) 696 expectSyncServiceAccount(t, mock, testManagedCluster, true) 697 expectSyncRoleBinding(t, mock, testManagedCluster, true) 698 expectSyncAgent(t, mock, testManagedCluster, false, true) 699 expectMockCallsForListingRancherUsers(mock) 700 expectSyncRegistration(t, mock, testManagedCluster, false, true) 701 expectSyncManifest(t, mock, mockStatus, mockRequestSender, testManagedCluster, true, rancherManifestYAML, true, rancherClusterState) 702 expectRancherConfigK8sCalls(t, mock, false) 703 expectMockCallsForCreateClusterRoleBindingTemplate(mock, unitTestRancherClusterID) 704 expectPushManifestRequests(t, mockRequestSender, mock, rancherClusterState, true) 705 expectSyncCACertRancherK8sCalls(t, mock, mockRequestSender, false) 706 expectSyncPrometheusScraper(nil, mock, testManagedCluster, "", "", true, getCaCrt(), func(configMap *corev1.ConfigMap) error { 707 asserts.Len(configMap.Data, 2, "no data found") 708 asserts.NotEmpty(configMap.Data["ca-test"], "No cert entry found") 709 prometheusYaml := configMap.Data["prometheus.yml"] 710 asserts.NotEmpty(prometheusYaml, "No prometheus config yaml found") 711 712 scrapeConfig, err := getScrapeConfig(prometheusYaml, testManagedCluster) 713 if err != nil { 714 asserts.Fail("failed due to error %v", err) 715 } 716 validateScrapeConfig(t, scrapeConfig, prometheusConfigBasePath, true) 717 return nil 718 }, func(secret *corev1.Secret) error { 719 scrapeConfigYaml := secret.Data[constants.PromAdditionalScrapeConfigsSecretKey] 720 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(scrapeConfigYaml)) 721 if err != nil { 722 asserts.Fail("failed due to error %v", err) 723 } 724 scrapeConfig := getJob(scrapeConfigs.Children(), testManagedCluster) 725 validateScrapeConfig(t, scrapeConfig, managedCertsBasePath, true) 726 return nil 727 }, func(secret *corev1.Secret) error { 728 asserts.NotEmpty(secret.Data["ca-test"], "Expected to find a managed cluster TLS cert") 729 return nil 730 }) 731 732 // expect status updated with condition Ready=true 733 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionTrue, "", false) 734 735 // Create and make the request 736 request := newRequest(namespace, testManagedCluster) 737 reconciler := newVMCReconciler(mock) 738 result, err := reconciler.Reconcile(context.TODO(), request) 739 740 // Validate the results 741 mocker.Finish() 742 asserts.NoError(err) 743 asserts.Equal(true, result.Requeue) 744 asserts.Equal(time.Duration(vpoconstants.ReconcileLoopRequeueInterval), result.RequeueAfter) 745 } 746 747 // TestCreateVMCSyncSvcAccountFailed tests the Reconcile method for the following use case 748 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 749 // WHEN syncing of service account fails 750 // THEN ensure that the VMC status is updated to Ready=false with an appropriate message 751 func TestCreateVMCSyncSvcAccountFailed(t *testing.T) { 752 // clear any cached user auth tokens when the test completes 753 defer rancherutil.DeleteStoredTokens() 754 755 namespace := constants.VerrazzanoMultiClusterNamespace 756 asserts := assert.New(t) 757 mocker := gomock.NewController(t) 758 mock := mocks.NewMockClient(mocker) 759 mockStatus := mocks.NewMockStatusWriter(mocker) 760 asserts.NotNil(mockStatus) 761 762 defer setConfigFunc(getConfigFunc) 763 setConfigFunc(fakeGetConfig) 764 765 expectVmcGetAndUpdate(t, mock, testManagedCluster, true, false) 766 expectSyncServiceAccount(t, mock, testManagedCluster, false) 767 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 768 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 769 return nil 770 }) 771 772 // expect status updated with condition Ready=true 773 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionFalse, "failing syncServiceAccount", false) 774 775 errCount := testutil.ToFloat64(reconcileErrorCount) 776 777 // Create and make the request 778 request := newRequest(namespace, testManagedCluster) 779 reconciler := newVMCReconciler(mock) 780 result, err := reconciler.Reconcile(context.TODO(), request) 781 782 // Validate the results - there should have been no error returned for failing to sync svc account, but the reconcile 783 // error metric should have been incremented and the request should be requeued 784 mocker.Finish() 785 asserts.NoError(err) 786 asserts.Equal(true, result.Requeue) 787 asserts.NotEqual(time.Duration(0), result.RequeueAfter) 788 asserts.Equal(errCount+1, testutil.ToFloat64(reconcileErrorCount)) 789 } 790 791 // TestCreateVMCSyncRoleBindingFailed tests the Reconcile method for the following use case 792 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 793 // WHEN syncing of role binding fails 794 // THEN ensure that the VMC status is updated to Ready=false with an appropriate message 795 func TestCreateVMCSyncRoleBindingFailed(t *testing.T) { 796 // clear any cached user auth tokens when the test completes 797 defer rancherutil.DeleteStoredTokens() 798 799 namespace := constants.VerrazzanoMultiClusterNamespace 800 name := "test" 801 802 asserts := assert.New(t) 803 mocker := gomock.NewController(t) 804 mock := mocks.NewMockClient(mocker) 805 mockStatus := mocks.NewMockStatusWriter(mocker) 806 asserts.NotNil(mockStatus) 807 808 defer setConfigFunc(getConfigFunc) 809 setConfigFunc(fakeGetConfig) 810 811 expectVmcGetAndUpdate(t, mock, name, true, false) 812 expectSyncServiceAccount(t, mock, name, true) 813 expectSyncRoleBinding(t, mock, name, false) 814 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 815 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 816 return nil 817 }) 818 819 // expect status updated with condition Ready=true 820 expectStatusUpdateReadyCondition(asserts, mock, mockStatus, corev1.ConditionFalse, "failing syncRoleBinding", false) 821 822 // Create and make the request 823 request := newRequest(namespace, name) 824 reconciler := newVMCReconciler(mock) 825 result, err := reconciler.Reconcile(context.TODO(), request) 826 827 // Validate the results - there should have been an error returned 828 mocker.Finish() 829 asserts.Nil(err) 830 asserts.Equal(true, result.Requeue) 831 asserts.NotEqual(time.Duration(0), result.RequeueAfter) 832 } 833 834 // TestDeleteVMC tests the Reconcile method for the following use case 835 // GIVEN a request to reconcile an VerrazzanoManagedCluster resource 836 // WHEN a VerrazzanoManagedCluster resource has been deleted 837 // THEN ensure the object is not processed 838 func TestDeleteVMC(t *testing.T) { 839 // clear any cached user auth tokens when the test completes 840 defer rancherutil.DeleteStoredTokens() 841 842 namespace := "verrazzano-install" 843 asserts := assert.New(t) 844 mocker := gomock.NewController(t) 845 mock := mocks.NewMockClient(mocker) 846 847 mockRequestSender := mocks.NewMockRequestSender(mocker) 848 savedRancherHTTPClient := rancherutil.RancherHTTPClient 849 defer func() { 850 rancherutil.RancherHTTPClient = savedRancherHTTPClient 851 }() 852 rancherutil.RancherHTTPClient = mockRequestSender 853 854 // Expect all of the calls when deleting a VMC 855 expectMockCallsForDelete(t, mock, namespace) 856 expectRancherGetAuthTokenHTTPCall(t, mockRequestSender) 857 expectThanosDelete(t, mock) 858 859 // Expect an API call to delete the Rancher cluster 860 mockRequestSender.EXPECT(). 861 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURIMethod("DELETE", clustersPath+"/"+unitTestRancherClusterID)). 862 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 863 r := io.NopCloser(bytes.NewReader([]byte(""))) 864 resp := &http.Response{ 865 StatusCode: http.StatusOK, 866 Body: r, 867 } 868 return resp, nil 869 }) 870 871 // Expect a call to update the VerrazzanoManagedCluster finalizer 872 mock.EXPECT(). 873 Update(gomock.Any(), gomock.Any(), gomock.Any()). 874 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 875 asserts.True(len(vmc.ObjectMeta.Finalizers) == 0, "Wrong number of finalizers") 876 return nil 877 }) 878 879 mock.EXPECT(). 880 List(gomock.Any(), &v1beta1.VerrazzanoList{}, gomock.Not(gomock.Nil())). 881 DoAndReturn(func(ctx context.Context, list *v1beta1.VerrazzanoList, opts ...client.ListOption) error { 882 vz := v1beta1.Verrazzano{} 883 list.Items = append(list.Items, vz) 884 return nil 885 }) 886 887 // Create and make the request 888 request := newRequest(namespace, testManagedCluster) 889 reconciler := newVMCReconciler(mock) 890 result, err := reconciler.Reconcile(context.TODO(), request) 891 892 // Validate the results 893 mocker.Finish() 894 asserts.NoError(err) 895 asserts.Equal(false, result.Requeue) 896 asserts.Equal(time.Duration(0), result.RequeueAfter) 897 } 898 899 // TestDeleteVMCFailedDeletingRancherCluster tests deleting a VMC when there are errors attempting to 900 // delete the Rancher cluster. 901 func TestDeleteVMCFailedDeletingRancherCluster(t *testing.T) { 902 // clear any cached user auth tokens when the test completes 903 defer rancherutil.DeleteStoredTokens() 904 905 namespace := "verrazzano-install" 906 asserts := assert.New(t) 907 mocker := gomock.NewController(t) 908 mock := mocks.NewMockClient(mocker) 909 mockStatus := mocks.NewMockStatusWriter(mocker) 910 asserts.NotNil(mockStatus) 911 912 mockRequestSender := mocks.NewMockRequestSender(mocker) 913 savedRancherHTTPClient := rancherutil.RancherHTTPClient 914 defer func() { 915 rancherutil.RancherHTTPClient = savedRancherHTTPClient 916 }() 917 rancherutil.RancherHTTPClient = mockRequestSender 918 919 // GIVEN a VMC is being deleted 920 // WHEN we fail creating a Rancher API client that will be used to delete the cluster in Rancher 921 // THEN the appropriate status is set on the VMC and the finalizer is not removed 922 923 // Expect all of the calls when deleting a VMC 924 expectMockCallsForDelete(t, mock, namespace) 925 expectThanosDelete(t, mock) 926 927 // Expect an HTTP request to fetch the admin token from Rancher - return an error 928 mockRequestSender.EXPECT(). 929 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 930 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 931 asserts.Equal(loginQueryString, req.URL.RawQuery) 932 933 r := io.NopCloser(bytes.NewReader([]byte(""))) 934 resp := &http.Response{ 935 StatusCode: http.StatusBadRequest, 936 Body: r, 937 Request: &http.Request{Method: http.MethodDelete}, 938 } 939 return resp, nil 940 }) 941 942 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 943 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 944 return nil 945 }) 946 947 mock.EXPECT().Status().Return(mockStatus) 948 mockStatus.EXPECT(). 949 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 950 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 951 asserts.Equal(v1alpha1.DeleteFailed, vmc.Status.RancherRegistration.Status) 952 asserts.Equal("Failed to create Rancher API client", vmc.Status.RancherRegistration.Message) 953 return nil 954 }) 955 956 mock.EXPECT(). 957 List(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil())). 958 DoAndReturn(func(ctx context.Context, list *v1beta1.VerrazzanoList, opts ...client.ListOption) error { 959 vz := v1beta1.Verrazzano{} 960 list.Items = append(list.Items, vz) 961 return nil 962 }) 963 964 // Create and make the request 965 request := newRequest(namespace, testManagedCluster) 966 reconciler := newVMCReconciler(mock) 967 result, err := reconciler.Reconcile(context.TODO(), request) 968 969 // Validate the results 970 mocker.Finish() 971 asserts.NoError(err) 972 asserts.Equal(true, result.Requeue) 973 974 // GIVEN a VMC is being deleted 975 // WHEN we fail attempting to delete the cluster in Rancher 976 // THEN the appropriate status is set on the VMC and the finalizer is not removed 977 978 mock = mocks.NewMockClient(mocker) 979 mockStatus = mocks.NewMockStatusWriter(mocker) 980 mockRequestSender = mocks.NewMockRequestSender(mocker) 981 rancherutil.RancherHTTPClient = mockRequestSender 982 983 // Expect all of the calls when deleting a VMC 984 expectMockCallsForDelete(t, mock, namespace) 985 expectRancherGetAuthTokenHTTPCall(t, mockRequestSender) 986 expectThanosDelete(t, mock) 987 988 // Expect an API call to delete the Rancher cluster - return an error 989 mockRequestSender.EXPECT(). 990 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURIMethod("DELETE", clustersPath+"/"+unitTestRancherClusterID)). 991 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 992 r := io.NopCloser(bytes.NewReader([]byte(""))) 993 resp := &http.Response{ 994 StatusCode: http.StatusConflict, 995 Body: r, 996 Request: &http.Request{Method: http.MethodDelete}, 997 } 998 return resp, nil 999 }) 1000 1001 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1002 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 1003 return nil 1004 }) 1005 1006 mock.EXPECT().Status().Return(mockStatus) 1007 mockStatus.EXPECT(). 1008 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1009 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1010 asserts.Equal(v1alpha1.DeleteFailed, vmc.Status.RancherRegistration.Status) 1011 asserts.Equal("Failed deleting cluster", vmc.Status.RancherRegistration.Message) 1012 return nil 1013 }) 1014 1015 mock.EXPECT(). 1016 List(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil())). 1017 DoAndReturn(func(ctx context.Context, list *v1beta1.VerrazzanoList, opts ...client.ListOption) error { 1018 vz := v1beta1.Verrazzano{} 1019 list.Items = append(list.Items, vz) 1020 return nil 1021 }) 1022 1023 // Create and make the request 1024 request = newRequest(namespace, testManagedCluster) 1025 reconciler = newVMCReconciler(mock) 1026 result, err = reconciler.Reconcile(context.TODO(), request) 1027 1028 // Validate the results 1029 mocker.Finish() 1030 asserts.NoError(err) 1031 asserts.Equal(true, result.Requeue) 1032 } 1033 1034 // TestSyncManifestSecretFailRancherRegistration tests syncing the manifest secret 1035 // when Rancher registration fails 1036 // GIVEN a call to sync the manifest secret 1037 // WHEN Rancher registration fails 1038 // THEN the manifest secret is still created and syncManifestSecret returns no error 1039 func TestSyncManifestSecretFailRancherRegistration(t *testing.T) { 1040 // clear any cached user auth tokens when the test completes 1041 defer rancherutil.DeleteStoredTokens() 1042 1043 asserts := assert.New(t) 1044 mocker := gomock.NewController(t) 1045 mock := mocks.NewMockClient(mocker) 1046 mockStatus := mocks.NewMockStatusWriter(mocker) 1047 asserts.NotNil(mockStatus) 1048 mockRequestSender := mocks.NewMockRequestSender(mocker) 1049 savedRancherHTTPClient := rancherutil.RancherHTTPClient 1050 defer func() { 1051 rancherutil.RancherHTTPClient = savedRancherHTTPClient 1052 }() 1053 rancherutil.RancherHTTPClient = mockRequestSender 1054 1055 clusterName := "cluster1" 1056 1057 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.RancherSystemNamespace, Name: rancherAdminSecret}, gomock.AssignableToTypeOf(&corev1.Secret{}), gomock.Any()). 1058 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1059 secret.Data = map[string][]byte{ 1060 "password": []byte("super-secret"), 1061 } 1062 return nil 1063 }) 1064 // Expect a call to get the Rancher ingress and return no spec rules, which will cause registration to fail 1065 mock.EXPECT(). 1066 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherIngressName}), gomock.Not(gomock.Nil()), gomock.Any()). 1067 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1068 return nil 1069 }) 1070 1071 // Expect to get existing VMC for status update 1072 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: clusterName}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1073 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 1074 return nil 1075 }) 1076 1077 mock.EXPECT().Status().Return(mockStatus) 1078 mockStatus.EXPECT(). 1079 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1080 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1081 asserts.Equal(v1alpha1.RegistrationFailed, vmc.Status.RancherRegistration.Status) 1082 asserts.Contains(vmc.Status.RancherRegistration.Message, "Failed to create Rancher API client") 1083 return nil 1084 }) 1085 1086 // Expect a call to get the manifest secret - return that it does not exist 1087 mock.EXPECT(). 1088 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetManifestSecretName(clusterName)}, gomock.Not(gomock.Nil()), gomock.Any()). 1089 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoMultiClusterNamespace, Resource: "Secret"}, GetManifestSecretName(clusterName))) 1090 1091 // Expect a call to create the manifest secret 1092 mock.EXPECT(). 1093 Create(gomock.Any(), gomock.AssignableToTypeOf(&corev1.Secret{}), gomock.Any()). 1094 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 1095 return nil 1096 }) 1097 1098 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: clusterName}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1099 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 1100 return nil 1101 }) 1102 1103 // Expect a call to update the VerrazzanoManagedCluster kubeconfig secret testManagedCluster - return success 1104 mock.EXPECT(). 1105 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1106 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1107 asserts.Equal(vmc.Spec.ManagedClusterManifestSecret, GetManifestSecretName(clusterName), "Manifest secret testManagedCluster did not match") 1108 return nil 1109 }) 1110 1111 // Create a reconciler and call the function to sync the manifest secret - the call to register the cluster with Rancher will 1112 // fail but the result of syncManifestSecret should be success 1113 vmc := v1alpha1.VerrazzanoManagedCluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: constants.VerrazzanoMultiClusterNamespace}} 1114 reconciler := newVMCReconciler(mock) 1115 reconciler.log = vzlog.DefaultLogger() 1116 1117 vzVMCWaitingForClusterID, err := reconciler.syncManifestSecret(context.TODO(), true, &vmc) 1118 1119 // Validate the results 1120 mocker.Finish() 1121 asserts.NoError(err) 1122 asserts.False(vzVMCWaitingForClusterID) 1123 } 1124 1125 // TestSyncManifestSecretEmptyRancherManifest tests syncing the manifest secret 1126 // when Rancher returns an empty registration manifest YAML string. 1127 // GIVEN a call to sync the manifest secret 1128 // WHEN Rancher returns an empty manifest 1129 // THEN the status is set to failed on the VMC with an appropriate message and the syncManifestSecret call returns an error 1130 func TestSyncManifestSecretEmptyRancherManifest(t *testing.T) { 1131 // clear any cached user auth tokens when the test completes 1132 defer rancherutil.DeleteStoredTokens() 1133 1134 asserts := assert.New(t) 1135 mocker := gomock.NewController(t) 1136 mock := mocks.NewMockClient(mocker) 1137 mockStatus := mocks.NewMockStatusWriter(mocker) 1138 asserts.NotNil(mockStatus) 1139 1140 mockRequestSender := mocks.NewMockRequestSender(mocker) 1141 savedRancherHTTPClient := rancherutil.RancherHTTPClient 1142 defer func() { 1143 rancherutil.RancherHTTPClient = savedRancherHTTPClient 1144 }() 1145 rancherutil.RancherHTTPClient = mockRequestSender 1146 1147 defer setConfigFunc(getConfigFunc) 1148 setConfigFunc(fakeGetConfig) 1149 1150 // Expect all the calls needed to register the cluster with Rancher - note we are passing an empty string for the Rancher manifest YAML 1151 // that will be returned when calling the Rancher API 1152 expectRegisterClusterWithRancher(t, mock, mockRequestSender, nil, testManagedCluster, false, "") 1153 1154 // Expect the Rancher registration status to be set appropriately 1155 mock.EXPECT().Status().Return(mockStatus) 1156 mockStatus.EXPECT(). 1157 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1158 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1159 asserts.Equal(v1alpha1.RegistrationFailed, vmc.Status.RancherRegistration.Status) 1160 asserts.Equal(unitTestRancherClusterID, vmc.Status.RancherRegistration.ClusterID) 1161 asserts.Equal("Empty Rancher manifest YAML", vmc.Status.RancherRegistration.Message) 1162 return nil 1163 }) 1164 1165 // Create a reconciler and call the function to sync the manifest secret 1166 vmc := v1alpha1.VerrazzanoManagedCluster{ObjectMeta: metav1.ObjectMeta{Name: testManagedCluster, Namespace: constants.VerrazzanoMultiClusterNamespace}} 1167 reconciler := newVMCReconciler(mock) 1168 reconciler.log = vzlog.DefaultLogger() 1169 1170 vzVMCWaitingForClusterID, err := reconciler.syncManifestSecret(context.TODO(), true, &vmc) 1171 1172 // Validate the results 1173 mocker.Finish() 1174 asserts.ErrorContains(err, "Failed retrieving Rancher manifest, YAML is an empty string") 1175 asserts.False(vzVMCWaitingForClusterID) 1176 } 1177 1178 // TestRegisterClusterWithRancherK8sErrorCases tests errors cases using the Kubernetes 1179 // client when registering with Rancher. 1180 func TestRegisterClusterWithRancherK8sErrorCases(t *testing.T) { 1181 // clear any cached user auth tokens when the test completes 1182 defer rancherutil.DeleteStoredTokens() 1183 1184 asserts := assert.New(t) 1185 mocker := gomock.NewController(t) 1186 mock := mocks.NewMockClient(mocker) 1187 1188 // GIVEN a call to register a managed cluster with Rancher 1189 // WHEN the call to get the ingress host name returns no ingress rules 1190 // THEN the registration call returns an error 1191 1192 // Expect a call to get the ingress host name but there are no ingress rules 1193 mock.EXPECT(). 1194 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherIngressName}), gomock.Not(gomock.Nil()), gomock.Any()). 1195 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1196 return nil 1197 }) 1198 1199 // Expect a call for the verrazzano cluser user secret 1200 mock.EXPECT(). 1201 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: constants.VerrazzanoClusterRancherName}, gomock.AssignableToTypeOf(&corev1.Secret{}), gomock.Any()). 1202 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1203 secret.Data = map[string][]byte{ 1204 "password": []byte("super-secret"), 1205 } 1206 return nil 1207 }) 1208 1209 rc, err := rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1210 1211 mocker.Finish() 1212 asserts.Error(err) 1213 asserts.Nil(rc) 1214 1215 // GIVEN a call to register a managed cluster with Rancher 1216 // WHEN the call to get the Rancher root CA cert secret fails 1217 // THEN the registration call returns an error 1218 mocker = gomock.NewController(t) 1219 mock = mocks.NewMockClient(mocker) 1220 1221 // Expect a call to get the ingress host name 1222 mock.EXPECT(). 1223 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherIngressName}), gomock.Not(gomock.Nil()), gomock.Any()). 1224 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1225 rule := networkingv1.IngressRule{Host: "rancher.unit-test.com"} 1226 ingress.Spec.Rules = append(ingress.Spec.Rules, rule) 1227 return nil 1228 }) 1229 1230 // Expect a call to get the secret with the Rancher root CA cert but the call fails 1231 mock.EXPECT(). 1232 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherTLSSecret}), gomock.Not(gomock.Nil()), gomock.Any()). 1233 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1234 return errors.NewResourceExpired("something bad happened") 1235 }) 1236 1237 // Expect a call for the verrazzano cluser user secret 1238 mock.EXPECT(). 1239 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: constants.VerrazzanoClusterRancherName}, gomock.AssignableToTypeOf(&corev1.Secret{}), gomock.Any()). 1240 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1241 secret.Data = map[string][]byte{ 1242 "password": []byte("super-secret"), 1243 } 1244 return nil 1245 }) 1246 1247 rc, err = rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1248 1249 mocker.Finish() 1250 asserts.Error(err) 1251 asserts.Nil(rc) 1252 } 1253 1254 // TestRegisterClusterWithRancherHTTPErrorCases tests errors cases using the HTTP 1255 // client when registering with Rancher. 1256 func TestRegisterClusterWithRancherHTTPErrorCases(t *testing.T) { 1257 // clear any cached user auth tokens when the test completes 1258 defer rancherutil.DeleteStoredTokens() 1259 1260 asserts := assert.New(t) 1261 mocker := gomock.NewController(t) 1262 mock := mocks.NewMockClient(mocker) 1263 mockRequestSender := mocks.NewMockRequestSender(mocker) 1264 1265 savedRancherHTTPClient := rancherutil.RancherHTTPClient 1266 defer func() { 1267 rancherutil.RancherHTTPClient = savedRancherHTTPClient 1268 }() 1269 rancherutil.RancherHTTPClient = mockRequestSender 1270 1271 // GIVEN a call to register a managed cluster with Rancher 1272 // WHEN the call to get the Rancher admin token fails 1273 // THEN the registration call returns an error 1274 1275 // Expect all of the Kubernetes calls 1276 expectRancherConfigK8sCalls(t, mock, false) 1277 1278 // Expect an HTTP request to fetch the admin token from Rancher but the call fails 1279 mockRequestSender.EXPECT(). 1280 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 1281 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1282 r := io.NopCloser(bytes.NewReader([]byte{})) 1283 resp := &http.Response{ 1284 StatusCode: http.StatusUnauthorized, 1285 Body: r, 1286 Request: &http.Request{Method: http.MethodPost}, 1287 } 1288 return resp, nil 1289 }) 1290 1291 rc, err := rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1292 1293 mocker.Finish() 1294 asserts.Error(err) 1295 asserts.Nil(rc) 1296 rancherutil.DeleteStoredTokens() 1297 1298 // GIVEN a call to register a managed cluster with Rancher 1299 // WHEN the call to import the cluster into Rancher fails 1300 // THEN the registration call returns an error 1301 mocker = gomock.NewController(t) 1302 mock = mocks.NewMockClient(mocker) 1303 mockRequestSender = mocks.NewMockRequestSender(mocker) 1304 rancherutil.RancherHTTPClient = mockRequestSender 1305 1306 // Expect all of the Kubernetes calls 1307 expectRancherConfigK8sCalls(t, mock, false) 1308 1309 // Expect an HTTP request to fetch the admin token from Rancher 1310 mockRequestSender.EXPECT(). 1311 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 1312 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1313 r := io.NopCloser(bytes.NewReader([]byte(`{"token":"unit-test-token"}`))) 1314 resp := &http.Response{ 1315 StatusCode: http.StatusCreated, 1316 Body: r, 1317 Request: &http.Request{Method: http.MethodPost}, 1318 } 1319 return resp, nil 1320 }) 1321 1322 // Expect an HTTP request to import the cluster to Rancher but the call fails 1323 mockRequestSender.EXPECT(). 1324 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterPath)). 1325 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1326 r := io.NopCloser(bytes.NewReader([]byte{})) 1327 resp := &http.Response{ 1328 StatusCode: http.StatusConflict, 1329 Body: r, 1330 Request: &http.Request{Method: http.MethodPost}, 1331 } 1332 return resp, nil 1333 }) 1334 1335 rc, err = rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1336 asserts.NoError(err) 1337 1338 regYAML, _, err := RegisterManagedClusterWithRancher(rc, testManagedCluster, "", vzlog.DefaultLogger()) 1339 1340 mocker.Finish() 1341 asserts.Error(err) 1342 asserts.Empty(regYAML) 1343 rancherutil.DeleteStoredTokens() 1344 1345 // GIVEN a call to register a managed cluster with Rancher 1346 // WHEN the call to create the Rancher registration token fails 1347 // THEN the registration call returns an error 1348 mocker = gomock.NewController(t) 1349 mock = mocks.NewMockClient(mocker) 1350 mockRequestSender = mocks.NewMockRequestSender(mocker) 1351 rancherutil.RancherHTTPClient = mockRequestSender 1352 1353 // Expect all of the Kubernetes calls 1354 expectRancherConfigK8sCalls(t, mock, false) 1355 1356 // Expect an HTTP request to fetch the admin token from Rancher 1357 mockRequestSender.EXPECT(). 1358 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 1359 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1360 r := io.NopCloser(bytes.NewReader([]byte(`{"token":"unit-test-token"}`))) 1361 resp := &http.Response{ 1362 StatusCode: http.StatusCreated, 1363 Body: r, 1364 Request: &http.Request{Method: http.MethodPost}, 1365 } 1366 return resp, nil 1367 }) 1368 1369 // Expect an HTTP request to import the cluster to Rancher 1370 mockRequestSender.EXPECT(). 1371 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterPath)). 1372 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1373 r := io.NopCloser(bytes.NewReader([]byte(`{"id":"some-cluster"}`))) 1374 resp := &http.Response{ 1375 StatusCode: http.StatusCreated, 1376 Body: r, 1377 } 1378 return resp, nil 1379 }) 1380 1381 // Expect an HTTP request to get the registration token in Rancher for the clusterId 1382 mockRequestSender.EXPECT(). 1383 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterRegTokenPath)). 1384 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1385 asserts.Contains(clusterRegTokenPath, req.URL.Path) 1386 1387 _, err := io.ReadAll(req.Body) 1388 asserts.NoError(err) 1389 1390 r := io.NopCloser(bytes.NewReader([]byte(`{"data":[]}`))) 1391 resp := &http.Response{ 1392 StatusCode: http.StatusOK, 1393 Body: r, 1394 Request: &http.Request{Method: http.MethodGet}, 1395 } 1396 return resp, nil 1397 }) 1398 1399 // Expect an HTTP request to create the registration token in Rancher but the call fails 1400 mockRequestSender.EXPECT(). 1401 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterRegTokenPath)). 1402 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1403 r := io.NopCloser(bytes.NewReader([]byte{})) 1404 resp := &http.Response{ 1405 StatusCode: http.StatusBadRequest, 1406 Body: r, 1407 Request: &http.Request{Method: http.MethodPost}, 1408 } 1409 return resp, nil 1410 }) 1411 1412 rc, err = rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1413 asserts.NoError(err) 1414 1415 regYAML, _, err = RegisterManagedClusterWithRancher(rc, testManagedCluster, "", vzlog.DefaultLogger()) 1416 1417 mocker.Finish() 1418 asserts.Error(err) 1419 asserts.Empty(regYAML) 1420 rancherutil.DeleteStoredTokens() 1421 1422 // GIVEN a call to register a managed cluster with Rancher 1423 // WHEN the call to get the Rancher manifest YAML fails 1424 // THEN the registration call returns an error 1425 mocker = gomock.NewController(t) 1426 mock = mocks.NewMockClient(mocker) 1427 mockRequestSender = mocks.NewMockRequestSender(mocker) 1428 rancherutil.RancherHTTPClient = mockRequestSender 1429 1430 // Expect all of the Kubernetes calls 1431 expectRancherConfigK8sCalls(t, mock, false) 1432 1433 // Expect an HTTP request to fetch the admin token from Rancher 1434 mockRequestSender.EXPECT(). 1435 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 1436 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1437 r := io.NopCloser(bytes.NewReader([]byte(`{"token":"unit-test-token"}`))) 1438 resp := &http.Response{ 1439 StatusCode: http.StatusCreated, 1440 Body: r, 1441 Request: &http.Request{Method: http.MethodPost}, 1442 } 1443 return resp, nil 1444 }) 1445 1446 // Expect an HTTP request to import the cluster to Rancher 1447 mockRequestSender.EXPECT(). 1448 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterPath)). 1449 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1450 r := io.NopCloser(bytes.NewReader([]byte(`{"id":"some-cluster"}`))) 1451 resp := &http.Response{ 1452 StatusCode: http.StatusCreated, 1453 Body: r, 1454 } 1455 return resp, nil 1456 }) 1457 1458 // Expect an HTTP request to get the registration token in Rancher for the clusterId 1459 mockRequestSender.EXPECT(). 1460 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterRegTokenPath)). 1461 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1462 asserts.Contains(clusterRegTokenPath, req.URL.Path) 1463 1464 _, err := io.ReadAll(req.Body) 1465 asserts.NoError(err) 1466 1467 // return a response with the CRT 1468 r := io.NopCloser(bytes.NewReader([]byte(`{"data":[{"token":"manifest-token","state":"active","clusterId":"some-cluster"}]}`))) 1469 resp := &http.Response{ 1470 StatusCode: http.StatusOK, 1471 Body: r, 1472 Request: &http.Request{Method: http.MethodGet}, 1473 } 1474 return resp, nil 1475 }) 1476 1477 // Expect an HTTP request to fetch the manifest YAML from Rancher but the call fails 1478 mockRequestSender.EXPECT(). 1479 Do(gomock.Not(gomock.Nil()), gomock.Not(gomock.Nil())). 1480 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1481 r := io.NopCloser(bytes.NewReader([]byte{})) 1482 resp := &http.Response{ 1483 StatusCode: http.StatusUnsupportedMediaType, 1484 Body: r, 1485 Request: &http.Request{Method: http.MethodGet}, 1486 } 1487 return resp, nil 1488 }) 1489 1490 rc, err = rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1491 asserts.NoError(err) 1492 1493 regYAML, _, err = RegisterManagedClusterWithRancher(rc, testManagedCluster, "", vzlog.DefaultLogger()) 1494 1495 mocker.Finish() 1496 asserts.Error(err) 1497 asserts.Empty(regYAML) 1498 } 1499 1500 // GIVEN a call to register a managed cluster with Rancher 1501 // WHEN the call to get the admin token from Rancher fails 1502 // AND the error is retryable 1503 // THEN ensure that the request is retried 1504 func TestRegisterClusterWithRancherRetryRequest(t *testing.T) { 1505 // clear any cached user auth tokens when the test completes 1506 defer rancherutil.DeleteStoredTokens() 1507 1508 asserts := assert.New(t) 1509 mocker := gomock.NewController(t) 1510 mock := mocks.NewMockClient(mocker) 1511 mockRequestSender := mocks.NewMockRequestSender(mocker) 1512 1513 savedRancherHTTPClient := rancherutil.RancherHTTPClient 1514 defer func() { 1515 rancherutil.RancherHTTPClient = savedRancherHTTPClient 1516 }() 1517 rancherutil.RancherHTTPClient = mockRequestSender 1518 1519 // replace the retry configuration so all of the retries happen very quickly 1520 retrySteps := 3 1521 savedRetry := rancherutil.DefaultRetry 1522 defer func() { 1523 rancherutil.DefaultRetry = savedRetry 1524 }() 1525 rancherutil.DefaultRetry = wait.Backoff{ 1526 Steps: retrySteps, 1527 Duration: 1 * time.Millisecond, 1528 Factor: 1.0, 1529 Jitter: 0.1, 1530 } 1531 1532 // Expect all of the Kubernetes calls 1533 expectRancherConfigK8sCalls(t, mock, false) 1534 1535 // Expect an HTTP request to fetch the admin token from Rancher - return an error response and 1536 // the request should be retried for a total of "retrySteps" # of times 1537 mockRequestSender.EXPECT(). 1538 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 1539 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1540 r := io.NopCloser(bytes.NewReader([]byte{})) 1541 resp := &http.Response{ 1542 StatusCode: http.StatusInternalServerError, 1543 Body: r, 1544 Request: &http.Request{Method: http.MethodPost}, 1545 } 1546 return resp, nil 1547 }).Times(retrySteps) 1548 1549 _, err := rancherutil.NewVerrazzanoClusterRancherConfig(mock, rancherutil.RancherIngressServiceHost(), vzlog.DefaultLogger()) 1550 1551 mocker.Finish() 1552 asserts.Error(err) 1553 } 1554 1555 // newScheme creates a new scheme that includes this package's object to use for testing 1556 func newScheme() *runtime.Scheme { 1557 scheme := runtime.NewScheme() 1558 _ = corev1.AddToScheme(scheme) 1559 _ = v1alpha1.AddToScheme(scheme) 1560 return scheme 1561 } 1562 1563 // newRequest creates a new reconciler request for testing 1564 // namespace - The namespace to use in the request 1565 // testManagedCluster - The testManagedCluster to use in the request 1566 func newRequest(namespace string, name string) ctrl.Request { 1567 return ctrl.Request{ 1568 NamespacedName: types.NamespacedName{ 1569 Namespace: namespace, 1570 Name: name}} 1571 } 1572 1573 // newVMCReconciler creates a new reconciler for testing 1574 // c - The Kerberos client to inject into the reconciler 1575 func newVMCReconciler(c client.Client) VerrazzanoManagedClusterReconciler { 1576 scheme := newScheme() 1577 reconciler := VerrazzanoManagedClusterReconciler{ 1578 Client: c, 1579 Scheme: scheme} 1580 return reconciler 1581 } 1582 1583 func fakeGetConfig() (*rest.Config, error) { 1584 conf := rest.Config{ 1585 TLSClientConfig: rest.TLSClientConfig{ 1586 CAData: []byte("fakeCA"), 1587 }, 1588 } 1589 return &conf, nil 1590 } 1591 1592 // Expect syncRoleBinding related calls 1593 func expectSyncRoleBinding(t *testing.T, mock *mocks.MockClient, name string, succeed bool) { 1594 asserts := assert.New(t) 1595 1596 // Expect a call to get the RoleBinding - return that it does not exist 1597 mock.EXPECT(). 1598 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: generateManagedResourceName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1599 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "RoleBinding"}, generateManagedResourceName(name))) 1600 1601 // Expect a call to create the RoleBinding - return success or failure based on the succeed argument 1602 mock.EXPECT(). 1603 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1604 DoAndReturn(func(ctx context.Context, binding *rbacv1.RoleBinding, opts ...client.CreateOption) error { 1605 if succeed { 1606 asserts.Equalf(generateManagedResourceName(name), binding.Name, "RoleBinding testManagedCluster did not match") 1607 asserts.Equalf(vpoconstants.MCClusterRole, binding.RoleRef.Name, "RoleBinding roleref did not match") 1608 asserts.Equalf(generateManagedResourceName(name), binding.Subjects[0].Name, "Subject did not match") 1609 asserts.Equalf(constants.VerrazzanoMultiClusterNamespace, binding.Subjects[0].Namespace, "Subject namespace did not match") 1610 return nil 1611 } 1612 return errors.NewInternalError(fmt.Errorf("failing syncRoleBinding")) 1613 }) 1614 } 1615 1616 // Expect syncServiceAccount related calls 1617 func expectSyncServiceAccount(t *testing.T, mock *mocks.MockClient, name string, succeed bool) { 1618 asserts := assert.New(t) 1619 1620 // Expect a call to get the ServiceAccount - return that it does not exist 1621 mock.EXPECT(). 1622 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: generateManagedResourceName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1623 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "ServiceAccount"}, generateManagedResourceName(name))) 1624 1625 // Expect a call to create the ServiceAccount - return success 1626 mock.EXPECT(). 1627 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1628 DoAndReturn(func(ctx context.Context, serviceAccount *corev1.ServiceAccount, opts ...client.CreateOption) error { 1629 asserts.Equalf(constants.VerrazzanoMultiClusterNamespace, serviceAccount.Namespace, "ServiceAccount namespace did not match") 1630 asserts.Equalf(generateManagedResourceName(name), serviceAccount.Name, "ServiceAccount testManagedCluster did not match") 1631 return nil 1632 }) 1633 1634 // Expect a call to get the Token Secret - return that it does not exist 1635 mock.EXPECT(). 1636 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: generateManagedResourceName(name) + "-token"}, gomock.Not(gomock.Nil()), gomock.Any()). 1637 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, generateManagedResourceName(name)+"-token")) 1638 1639 // Expect a call to create the Token Secret - return success 1640 mock.EXPECT(). 1641 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1642 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 1643 asserts.Equalf(constants.VerrazzanoMultiClusterNamespace, secret.Namespace, "Secret namespace did not match") 1644 asserts.Equalf(generateManagedResourceName(name)+"-token", secret.Name, "Secret testManagedCluster did not match") 1645 return nil 1646 }) 1647 1648 // Expect a call to update the VerrazzanoManagedCluster service account name - return success or 1649 // failure depending on the succeed argument 1650 mock.EXPECT(). 1651 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1652 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1653 if succeed { 1654 asserts.Equal(vmc.Spec.ServiceAccount, generateManagedResourceName(name), "ServiceAccount testManagedCluster did not match") 1655 return nil 1656 } 1657 return errors.NewInternalError(fmt.Errorf("failing syncServiceAccount")) 1658 }) 1659 } 1660 1661 // Expect syncAgent related calls 1662 func expectSyncAgent(t *testing.T, mock *mocks.MockClient, name string, rancherEnabled bool, noSASecret bool) { 1663 saSecretName := "saSecret" 1664 rancherURL := "http://rancher-url" 1665 rancherCAData := "rancherCAData" 1666 userAPIServerURL := "https://testurl" 1667 if noSASecret { 1668 // Expect a call to get the ServiceAccount, return without the secret set 1669 mock.EXPECT(). 1670 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: generateManagedResourceName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1671 DoAndReturn(func(ctx context.Context, name types.NamespacedName, sa *corev1.ServiceAccount, opts ...client.GetOption) error { 1672 sa.Name = name.Name 1673 return nil 1674 }) 1675 // Set the secret name to the service account token created by the VMC controller 1676 saSecretName = generateManagedResourceName(name) + "-token" 1677 1678 } else { 1679 // Expect a call to get the ServiceAccount, return one with the secret testManagedCluster set 1680 mock.EXPECT(). 1681 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: generateManagedResourceName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1682 DoAndReturn(func(ctx context.Context, name types.NamespacedName, sa *corev1.ServiceAccount, opts ...client.GetOption) error { 1683 sa.Secrets = []corev1.ObjectReference{{ 1684 Name: saSecretName, 1685 }} 1686 return nil 1687 }) 1688 } 1689 1690 // Expect a call to list Verrazzanos and return a Verrazzano that has Rancher URL in status only 1691 // if rancherEnabled is true 1692 mock.EXPECT(). 1693 List(gomock.Any(), &v1beta1.VerrazzanoList{}, gomock.Not(gomock.Nil())). 1694 DoAndReturn(func(ctx context.Context, list *v1beta1.VerrazzanoList, opts ...client.ListOption) error { 1695 var status v1beta1.VerrazzanoStatus 1696 if rancherEnabled { 1697 status = v1beta1.VerrazzanoStatus{ 1698 VerrazzanoInstance: &v1beta1.InstanceInfo{RancherURL: &rancherURL}, 1699 } 1700 } 1701 vz := v1beta1.Verrazzano{ 1702 Spec: v1beta1.VerrazzanoSpec{}, 1703 Status: status, 1704 } 1705 list.Items = append(list.Items, vz) 1706 return nil 1707 }) 1708 1709 // Expect a call to get the service token secret, return the secret with the token 1710 mock.EXPECT(). 1711 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: saSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 1712 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1713 secret.Data = map[string][]byte{ 1714 mcconstants.TokenKey: []byte(token), 1715 } 1716 return nil 1717 }) 1718 1719 if rancherEnabled { 1720 // Expect a call to get the tls-ca secret, return the secret as not found 1721 mock.EXPECT(). 1722 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.PrivateCABundle}, gomock.Not(gomock.Nil()), gomock.Any()). 1723 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoSystemNamespace, Resource: "Secret"}, constants.PrivateCABundle)) 1724 1725 // Expect a call to get the Rancher ingress tls secret, return the secret with the fields set 1726 mock.EXPECT(). 1727 Get(gomock.Any(), types.NamespacedName{Namespace: constants.RancherSystemNamespace, Name: rancherTLSSecret}, gomock.Not(gomock.Nil()), gomock.Any()). 1728 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1729 secret.Data = map[string][]byte{ 1730 mcconstants.CaCrtKey: []byte(rancherCAData), 1731 } 1732 return nil 1733 }) 1734 } else { 1735 // Expect a call to get the verrazzano-admin-cluster configmap 1736 mock.EXPECT(). 1737 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: vpoconstants.AdminClusterConfigMapName}, gomock.Not(gomock.Nil()), gomock.Any()). 1738 DoAndReturn(func(ctx context.Context, name types.NamespacedName, cm *corev1.ConfigMap, opts ...client.GetOption) error { 1739 cm.Data = map[string]string{ 1740 vpoconstants.ServerDataKey: userAPIServerURL, 1741 } 1742 return nil 1743 }) 1744 } 1745 1746 // Expect a call to get the Agent secret - return that it does not exist 1747 mock.EXPECT(). 1748 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetAgentSecretName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1749 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoMultiClusterNamespace, Resource: "Secret"}, GetAgentSecretName(name))) 1750 1751 // Expect a call to create the Agent secret 1752 mock.EXPECT(). 1753 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1754 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 1755 adminKubeconfig := string(secret.Data[mcconstants.KubeconfigKey]) 1756 if rancherEnabled { 1757 assert.Contains(t, adminKubeconfig, "server: "+rancherURL) 1758 } else { 1759 assert.Contains(t, adminKubeconfig, "server: "+userAPIServerURL) 1760 } 1761 return nil 1762 }) 1763 } 1764 1765 // Expect syncRegistration related calls 1766 func expectSyncRegistration(t *testing.T, mock *mocks.MockClient, name string, externalES bool, rancherEnabled bool) { 1767 const vzEsURLData = "https://vz-testhost:443" 1768 const vzUserData = "vz-user" 1769 const vzPasswordData = "vz-pw" 1770 const vzCaData = "vz-ca" 1771 1772 const externalEsURLData = "https://external-testhost:443" 1773 const externalUserData = "external-user" 1774 const externalPasswordData = "external-pw" 1775 const externalCaData = "external-ca" 1776 1777 fluentdESURL := "http://verrazzano-authproxy-opensearch:8775" 1778 fluentdESSecret := "verrazzano" 1779 esSecret := constants.VerrazzanoESInternal 1780 if externalES { 1781 fluentdESURL = externalEsURLData 1782 fluentdESSecret = "some-external-es-secret" 1783 esSecret = fluentdESSecret 1784 } 1785 1786 asserts := assert.New(t) 1787 1788 // Expect a call to get the registration secret - return that it does not exist 1789 mock.EXPECT(). 1790 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetRegistrationSecretName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1791 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoMultiClusterNamespace, Resource: "Secret"}, GetRegistrationSecretName(name))) 1792 1793 // Expect a call to list Verrazzanos - return the Verrazzano configured with fluentd 1794 mock.EXPECT(). 1795 List(gomock.Any(), &v1beta1.VerrazzanoList{}, gomock.Not(gomock.Nil())). 1796 DoAndReturn(func(ctx context.Context, list *v1beta1.VerrazzanoList, opts ...client.ListOption) error { 1797 vz := v1beta1.Verrazzano{ 1798 Spec: v1beta1.VerrazzanoSpec{ 1799 Components: v1beta1.ComponentSpec{ 1800 Fluentd: &v1beta1.FluentdComponent{ 1801 OpenSearchURL: fluentdESURL, 1802 OpenSearchSecret: fluentdESSecret, 1803 }, 1804 }, 1805 }, 1806 } 1807 if rancherEnabled { 1808 vz.Spec.Components.Rancher = &v1beta1.RancherComponent{Enabled: &trueValue} 1809 } 1810 list.Items = append(list.Items, vz) 1811 return nil 1812 }).Times(5) 1813 1814 // Expect a call to get the tls ingress and return the ingress. 1815 if !externalES { 1816 mock.EXPECT(). 1817 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: operatorOSIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1818 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1819 ingress.TypeMeta = metav1.TypeMeta{ 1820 APIVersion: "networking.k8s.io/v1", 1821 Kind: "ingress"} 1822 ingress.ObjectMeta = metav1.ObjectMeta{ 1823 Namespace: name.Namespace, 1824 Name: name.Name} 1825 ingress.Spec.Rules = []networkingv1.IngressRule{{ 1826 Host: "vz-testhost", 1827 }} 1828 return nil 1829 }) 1830 } 1831 1832 // Expect a call to get the opensearch secret, return the secret with the fields set 1833 mock.EXPECT(). 1834 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: esSecret}, gomock.Not(gomock.Nil()), gomock.Any()). 1835 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1836 if externalES { 1837 secret.Data = map[string][]byte{ 1838 mcconstants.VerrazzanoUsernameKey: []byte(externalUserData), 1839 mcconstants.VerrazzanoPasswordKey: []byte(externalPasswordData), 1840 mcconstants.FluentdESCaBundleKey: []byte(externalCaData), 1841 } 1842 } else { 1843 secret.Data = map[string][]byte{ 1844 mcconstants.VerrazzanoUsernameKey: []byte(vzUserData), 1845 mcconstants.VerrazzanoPasswordKey: []byte(vzPasswordData), 1846 } 1847 } 1848 return nil 1849 }) 1850 1851 // Expect a call to get the tls secret, return the secret with the fields set 1852 mock.EXPECT(). 1853 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: "verrazzano-tls"}, gomock.Not(gomock.Nil()), gomock.Any()). 1854 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1855 secret.Data = map[string][]byte{ 1856 mcconstants.CaCrtKey: []byte(vzCaData), 1857 } 1858 return nil 1859 }) 1860 1861 // Expect a call to get the tls-ca secret, return the secret as not found 1862 mock.EXPECT(). 1863 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.PrivateCABundle}, gomock.Not(gomock.Nil()), gomock.Any()). 1864 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoSystemNamespace, Resource: "Secret"}, constants.PrivateCABundle)) 1865 1866 // Expect a call to get the keycloak ingress and return the ingress. 1867 mock.EXPECT(). 1868 Get(gomock.Any(), types.NamespacedName{Namespace: "keycloak", Name: "keycloak"}, gomock.Not(gomock.Nil()), gomock.Any()). 1869 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1870 ingress.TypeMeta = metav1.TypeMeta{ 1871 APIVersion: "networking.k8s.io/v1", 1872 Kind: "ingress"} 1873 ingress.ObjectMeta = metav1.ObjectMeta{ 1874 Namespace: name.Namespace, 1875 Name: name.Name} 1876 ingress.Spec.Rules = []networkingv1.IngressRule{{ 1877 Host: "keycloak", 1878 }} 1879 return nil 1880 }) 1881 1882 // Expect a call to get the dex ingress and return the ingress. 1883 mock.EXPECT(). 1884 Get(gomock.Any(), types.NamespacedName{Namespace: "verrazzano-auth", Name: "dex"}, gomock.Not(gomock.Nil()), gomock.Any()). 1885 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 1886 ingress.TypeMeta = metav1.TypeMeta{ 1887 APIVersion: "networking.k8s.io/v1", 1888 Kind: "ingress"} 1889 ingress.ObjectMeta = metav1.ObjectMeta{ 1890 Namespace: name.Namespace, 1891 Name: name.Name} 1892 ingress.Spec.Rules = []networkingv1.IngressRule{{ 1893 Host: "auth", 1894 }} 1895 return nil 1896 }) 1897 1898 // Expect a call to create the registration secret 1899 mock.EXPECT(). 1900 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1901 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 1902 asserts.Equalf(testManagedCluster, string(secret.Data[mcconstants.ManagedClusterNameKey]), "Incorrect cluster testManagedCluster in Registration secret ") 1903 asserts.Equalf("https://keycloak", string(secret.Data[mcconstants.KeycloakURLKey]), "Incorrect admin ca bundle in Registration secret ") 1904 asserts.Equalf(vzCaData, string(secret.Data[mcconstants.AdminCaBundleKey]), "Incorrect admin ca bundle in Registration secret ") 1905 if externalES { 1906 asserts.Equalf(externalEsURLData, string(secret.Data[mcconstants.ESURLKey]), "Incorrect ES URL in Registration secret ") 1907 asserts.Equalf(externalCaData, string(secret.Data[mcconstants.ESCaBundleKey]), "Incorrect ES ca bundle in Registration secret ") 1908 asserts.Equalf(externalUserData, string(secret.Data[mcconstants.RegistrationUsernameKey]), "Incorrect ES user in Registration secret ") 1909 asserts.Equalf(externalPasswordData, string(secret.Data[mcconstants.RegistrationPasswordKey]), "Incorrect ES password in Registration secret ") 1910 } else { 1911 asserts.Equalf(vzEsURLData, string(secret.Data[mcconstants.ESURLKey]), "Incorrect ES URL in Registration secret ") 1912 asserts.Equalf(vzCaData, string(secret.Data[mcconstants.ESCaBundleKey]), "Incorrect ES ca bundle in Registration secret ") 1913 asserts.Equalf(vzUserData, string(secret.Data[mcconstants.RegistrationUsernameKey]), "Incorrect ES user in Registration secret ") 1914 asserts.Equalf(vzPasswordData, string(secret.Data[mcconstants.RegistrationPasswordKey]), "Incorrect ES password in Registration secret ") 1915 } 1916 return nil 1917 }) 1918 } 1919 1920 // Expect syncManifest related calls 1921 func expectSyncManifest(t *testing.T, mock *mocks.MockClient, mockStatus *mocks.MockStatusWriter, mockRequestSender *mocks.MockRequestSender, name string, clusterAlreadyRegistered bool, expectedRancherYAML string, rancherEnabled bool, rancherClusterState string) { 1922 1923 asserts := assert.New(t) 1924 clusterName := "cluster1" 1925 caData := "ca" 1926 userData := "user" 1927 passwordData := "pw" 1928 kubeconfigData := "fakekubeconfig" 1929 urlData := "https://testhost:443" 1930 1931 if rancherEnabled { 1932 // Expect all the calls needed to register the cluster with Rancher 1933 expectRegisterClusterWithRancher(t, mock, mockRequestSender, mockStatus, name, clusterAlreadyRegistered, expectedRancherYAML) 1934 } 1935 1936 expectRancherConfigK8sCalls(t, mock, true) 1937 1938 if rancherClusterState == rancherClusterStateActive { 1939 // expect a check for existence of verrazzano-system namespace, 1940 mockRequestSender.EXPECT(). 1941 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(k8sClustersPath+unitTestRancherClusterID+"/api/v1/namespaces/verrazzano-system")). 1942 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 1943 var resp *http.Response 1944 r := io.NopCloser(bytes.NewReader([]byte(`{"kind":"table", "apiVersion":"meta.k8s.io/v1"}`))) 1945 resp = &http.Response{ 1946 StatusCode: http.StatusOK, 1947 Body: r, 1948 } 1949 return resp, nil 1950 }) 1951 1952 // Expect a call to get the Agent secret 1953 mock.EXPECT(). 1954 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetAgentSecretName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1955 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1956 secret.Data = map[string][]byte{ 1957 mcconstants.KubeconfigKey: []byte(kubeconfigData), 1958 } 1959 return nil 1960 }).MinTimes(1) 1961 1962 // Expect a call to get the registration secret 1963 mock.EXPECT(). 1964 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetRegistrationSecretName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1965 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 1966 secret.Data = map[string][]byte{ 1967 mcconstants.ManagedClusterNameKey: []byte(clusterName), 1968 mcconstants.CaCrtKey: []byte(caData), 1969 mcconstants.RegistrationUsernameKey: []byte(userData), 1970 mcconstants.RegistrationPasswordKey: []byte(passwordData), 1971 mcconstants.ESURLKey: []byte(urlData), 1972 } 1973 return nil 1974 }).MinTimes(1) 1975 } 1976 1977 // Expect a call to get the manifest secret - return that it does not exist 1978 mock.EXPECT(). 1979 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: GetManifestSecretName(name)}, gomock.Not(gomock.Nil()), gomock.Any()). 1980 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoMultiClusterNamespace, Resource: "Secret"}, GetManifestSecretName(name))) 1981 1982 // Expect to get existing VMC for status update 1983 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 1984 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 1985 return nil 1986 }) 1987 1988 mock.EXPECT(). 1989 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1990 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 1991 return nil 1992 }) 1993 1994 // Expect a call to create the manifest secret 1995 mock.EXPECT(). 1996 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1997 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 1998 data := secret.Data[mcconstants.YamlKey] 1999 asserts.NotZero(len(data), "Expected yaml data in manifest secret") 2000 2001 // YAML should contain the Rancher manifest things 2002 yamlString := string(data) 2003 asserts.True(strings.Contains(yamlString, expectedRancherYAML), "Manifest YAML does not contain the correct Rancher resources") 2004 2005 return nil 2006 }) 2007 2008 // Expect to get existing VMC to update manifest secret entry 2009 mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2010 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 2011 return nil 2012 }) 2013 2014 // Expect a call to update the vmc status 2015 mock.EXPECT().Status().Return(mockStatus) 2016 mockStatus.EXPECT(). 2017 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2018 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 2019 return nil 2020 }) 2021 } 2022 2023 func expectPushManifestRequests(t *testing.T, mockRequestSender *mocks.MockRequestSender, mock *mocks.MockClient, rancherClusterState string, vzNSExists bool) { 2024 // expect a login request for the vz-cluster-reg user 2025 expectRancherGetAuthTokenHTTPCall(t, mockRequestSender) 2026 2027 mockRequestSender.EXPECT(). 2028 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clustersPath+"/"+unitTestRancherClusterID)). 2029 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2030 var resp *http.Response 2031 r := io.NopCloser(bytes.NewReader([]byte(`{"state": "` + rancherClusterState + `", "agentImage":"test"}`))) 2032 resp = &http.Response{ 2033 StatusCode: http.StatusOK, 2034 Body: r, 2035 } 2036 return resp, nil 2037 }).MinTimes(2) 2038 expectRancherConfigK8sCalls(t, mock, true) 2039 2040 if rancherClusterState == rancherClusterStateActive { 2041 // expect a check for existence of verrazzano-system namespace 2042 mockRequestSender.EXPECT(). 2043 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(k8sClustersPath+unitTestRancherClusterID+"/api/v1/namespaces/verrazzano-system")). 2044 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2045 var resp *http.Response 2046 if vzNSExists { 2047 r := io.NopCloser(bytes.NewReader([]byte(`{"kind":"table", "apiVersion":"meta.k8s.io/v1"}`))) 2048 resp = &http.Response{ 2049 StatusCode: http.StatusOK, 2050 Body: r, 2051 } 2052 } else { 2053 r := io.NopCloser(bytes.NewReader([]byte(""))) 2054 resp = &http.Response{ 2055 StatusCode: http.StatusNotFound, 2056 Body: r, 2057 } 2058 } 2059 return resp, nil 2060 }) 2061 } 2062 } 2063 2064 func expectVmcGetAndUpdate(t *testing.T, mock *mocks.MockClient, name string, caSecretExists bool, rancherClusterAlreadyRegistered bool) { 2065 asserts := assert.New(t) 2066 labels := map[string]string{"label1": "test"} 2067 2068 // Expect a call to get the VerrazzanoManagedCluster resource. 2069 mock.EXPECT(). 2070 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: name}, gomock.Not(gomock.Nil()), gomock.Any()). 2071 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 2072 vmc.TypeMeta = metav1.TypeMeta{ 2073 APIVersion: apiVersion, 2074 Kind: kind} 2075 vmc.ObjectMeta = metav1.ObjectMeta{ 2076 Namespace: name.Namespace, 2077 Name: name.Name, 2078 Labels: labels} 2079 if caSecretExists { 2080 vmc.Spec = v1alpha1.VerrazzanoManagedClusterSpec{ 2081 CASecret: getCASecretName(name.Name), 2082 } 2083 } 2084 vmc.Status = v1alpha1.VerrazzanoManagedClusterStatus{ 2085 PrometheusHost: getPrometheusHost(), 2086 } 2087 vmc.Status.RancherRegistration = v1alpha1.RancherRegistration{} 2088 if rancherClusterAlreadyRegistered { 2089 vmc.Status.RancherRegistration.Status = v1alpha1.RegistrationCompleted 2090 vmc.Status.RancherRegistration.ClusterID = unitTestRancherClusterID 2091 } 2092 return nil 2093 }) 2094 2095 // Expect a call to update the VerrazzanoManagedCluster finalizer 2096 mock.EXPECT(). 2097 Update(gomock.Any(), gomock.Any(), gomock.Any()). 2098 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 2099 asserts.True(len(vmc.ObjectMeta.Finalizers) == 1, "Wrong number of finalizers") 2100 asserts.Equal(finalizerName, vmc.ObjectMeta.Finalizers[0], "wrong finalizer") 2101 return nil 2102 }) 2103 2104 } 2105 2106 func expectSyncPrometheusScraper(t *testing.T, mock *mocks.MockClient, vmcName string, prometheusYaml string, jobs string, caSecretExists bool, cacrtSecretData string, f AssertFn, additionalScrapeConfigsAssertFunc secretAssertFn, managedClusterCertsAssertFunc secretAssertFn) { 2107 const internalSecretPassword = "nRXlxXgMwN" //nolint:gosec //#gosec G101 2108 2109 if caSecretExists { 2110 // Expect a call to get the prometheus secret - return it 2111 mock.EXPECT(). 2112 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: getCASecretName(vmcName)}, gomock.Not(gomock.Nil()), gomock.Any()). 2113 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2114 2115 secret.Data = map[string][]byte{ 2116 "cacrt": []byte(cacrtSecretData), 2117 } 2118 return nil 2119 }) 2120 } 2121 2122 // Expect a call to get the verrazzano-monitoring namespace 2123 mock.EXPECT(). 2124 Get(gomock.Any(), types.NamespacedName{Name: vpoconstants.VerrazzanoMonitoringNamespace}, gomock.Not(gomock.Nil()), gomock.Any()). 2125 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 2126 ns.SetName(vpoconstants.VerrazzanoMonitoringNamespace) 2127 return nil 2128 }) 2129 2130 // Expect a call to get the managed cluster TLS certs secret - return NotFound error 2131 mock.EXPECT(). 2132 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: vpoconstants.PromManagedClusterCACertsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2133 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, vpoconstants.PromManagedClusterCACertsSecretName)) 2134 2135 // Expect a call to create the managed cluster TLS certs secret 2136 mock.EXPECT(). 2137 Create(gomock.Any(), gomock.Any(), gomock.Any()). 2138 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.UpdateOption) error { 2139 return managedClusterCertsAssertFunc(secret) 2140 }) 2141 2142 expectThanosDelete(t, mock) 2143 2144 // Expect a call to get the additional scrape config secret - return it 2145 mock.EXPECT(). 2146 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: constants.PromAdditionalScrapeConfigsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2147 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2148 secret.Data = map[string][]byte{ 2149 constants.PromAdditionalScrapeConfigsSecretKey: []byte(jobs), 2150 } 2151 return nil 2152 }) 2153 2154 // Expect a call to get the Verrazzano Prometheus internal secret - return it 2155 mock.EXPECT(). 2156 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VerrazzanoPromInternal}, gomock.Not(gomock.Nil()), gomock.Any()). 2157 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2158 secret.Data = map[string][]byte{ 2159 mcconstants.VerrazzanoPasswordKey: []byte(internalSecretPassword), 2160 } 2161 return nil 2162 }) 2163 2164 // Expect a call to get the additional scrape config secret (we call controllerruntime.CreateOrUpdate so it fetches again) - return it 2165 mock.EXPECT(). 2166 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: constants.PromAdditionalScrapeConfigsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2167 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2168 secret.Data = map[string][]byte{ 2169 constants.PromAdditionalScrapeConfigsSecretKey: []byte(jobs), 2170 } 2171 return nil 2172 }) 2173 2174 // Expect a call to update the additional scrape config secret 2175 mock.EXPECT(). 2176 Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1.Secret{}), gomock.Any()). 2177 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.UpdateOption) error { 2178 return additionalScrapeConfigsAssertFunc(secret) 2179 }) 2180 2181 } 2182 2183 // expectRegisterClusterWithRancher asserts all of the expected calls on the Kubernetes client mock and the HTTP client mock 2184 func expectRegisterClusterWithRancher(t *testing.T, k8sMock *mocks.MockClient, requestSenderMock *mocks.MockRequestSender, mockStatus *mocks.MockStatusWriter, clusterName string, clusterAlreadyRegistered bool, expectedRancherYAML string) { 2185 2186 expectRancherConfigK8sCalls(t, k8sMock, true) 2187 expectRegisterClusterWithRancherHTTPCalls(t, requestSenderMock, clusterName, clusterAlreadyRegistered, expectedRancherYAML) 2188 // Expect to get existing VMC to update manifest secret entry 2189 k8sMock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2190 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 2191 return nil 2192 }) 2193 } 2194 2195 // expectRegisterClusterWithRancherHTTPCalls asserts all of the expected calls on the HTTP client mock 2196 func expectRegisterClusterWithRancherHTTPCalls(t *testing.T, requestSenderMock *mocks.MockRequestSender, clusterName string, clusterAlreadyRegistered bool, rancherManifestYAML string) { 2197 asserts := assert.New(t) 2198 2199 expectRancherGetAuthTokenHTTPCall(t, requestSenderMock) 2200 2201 if !clusterAlreadyRegistered { 2202 // Cluster is not already registered - we now only expect import to happen if the cluster is NOT already registered 2203 // Expect an HTTP request to import the cluster to Rancher 2204 requestSenderMock.EXPECT(). 2205 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterPath)). 2206 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2207 asserts.Equal(clusterPath, req.URL.Path) 2208 2209 var resp *http.Response 2210 r := io.NopCloser(bytes.NewReader([]byte(`{"id":"` + unitTestRancherClusterID + `"}`))) 2211 resp = &http.Response{ 2212 StatusCode: http.StatusCreated, 2213 Body: r, 2214 } 2215 return resp, nil 2216 }) 2217 } 2218 2219 manifestToken := "unit-test-manifest-token" 2220 2221 // Expect an HTTP request to get the registration token in Rancher 2222 requestSenderMock.EXPECT(). 2223 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterRegTokenPath)). 2224 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2225 asserts.Contains(clusterRegTokenPath, req.URL.Path) 2226 2227 // assert that the cluster ID in the request body is what we expect 2228 _, err := io.ReadAll(req.Body) 2229 asserts.NoError(err) 2230 2231 // return a response with the empty data, no CRT exists for this cluster 2232 r := io.NopCloser(bytes.NewReader([]byte(`{"data":[]}`))) 2233 resp := &http.Response{ 2234 StatusCode: http.StatusOK, 2235 Body: r, 2236 Request: &http.Request{Method: http.MethodGet}, 2237 } 2238 return resp, nil 2239 }) 2240 2241 // Expect an HTTP request to create the registration token in Rancher 2242 requestSenderMock.EXPECT(). 2243 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clusterRegTokenPath)). 2244 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2245 asserts.Equal(clusterRegTokenPath, req.URL.Path) 2246 2247 // assert that the cluster ID in the request body is what we expect 2248 body, err := io.ReadAll(req.Body) 2249 asserts.NoError(err) 2250 jsonString, err := gabs.ParseJSON(body) 2251 asserts.NoError(err) 2252 clusterID, ok := jsonString.Path("clusterId").Data().(string) 2253 asserts.True(ok) 2254 asserts.Equal(unitTestRancherClusterID, clusterID) 2255 2256 // return a response with the manifest token 2257 r := io.NopCloser(bytes.NewReader([]byte(`{"token":"` + manifestToken + `"}`))) 2258 resp := &http.Response{ 2259 StatusCode: http.StatusCreated, 2260 Body: r, 2261 Request: &http.Request{Method: http.MethodPost}, 2262 } 2263 return resp, nil 2264 }) 2265 2266 // Expect an HTTP request to fetch the manifest YAML from Rancher 2267 requestSenderMock.EXPECT(). 2268 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(manifestPath+manifestToken+"_"+unitTestRancherClusterID+".yaml")). 2269 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2270 // asserts.Equal(manifestPath+manifestToken+"_"+unitTestRancherClusterID+".yaml", req.URL.Path) 2271 2272 r := io.NopCloser(bytes.NewReader([]byte(rancherManifestYAML))) 2273 resp := &http.Response{ 2274 StatusCode: http.StatusOK, 2275 Body: r, 2276 Request: &http.Request{Method: http.MethodGet}, 2277 } 2278 return resp, nil 2279 }).MinTimes(1) 2280 } 2281 2282 func expectRancherGetAuthTokenHTTPCall(t *testing.T, requestSenderMock *mocks.MockRequestSender) { 2283 asserts := assert.New(t) 2284 2285 // Expect an HTTP request to fetch the admin token from Rancher 2286 requestSenderMock.EXPECT(). 2287 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)). 2288 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2289 asserts.Equal(loginQueryString, req.URL.RawQuery) 2290 2291 r := io.NopCloser(bytes.NewReader([]byte(`{"token":"unit-test-token"}`))) 2292 resp := &http.Response{ 2293 StatusCode: http.StatusCreated, 2294 Body: r, 2295 Request: &http.Request{Method: http.MethodPost}, 2296 } 2297 return resp, nil 2298 }).AnyTimes() 2299 2300 } 2301 2302 func expectThanosDelete(t *testing.T, mock *mocks.MockClient) { 2303 // Expect a call to get the Istio CRDs 2304 mock.EXPECT(). 2305 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Name: destinationRuleCRDName}), gomock.AssignableToTypeOf(&apiv1.CustomResourceDefinition{}), gomock.Any()). 2306 DoAndReturn(func(ctx context.Context, key client.ObjectKey, crd *apiv1.CustomResourceDefinition, opts ...client.GetOption) error { 2307 return errors.NewNotFound(schema.GroupResource{}, "not found") 2308 }) 2309 mock.EXPECT(). 2310 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Name: serviceEntryCRDName}), gomock.AssignableToTypeOf(&apiv1.CustomResourceDefinition{}), gomock.Any()). 2311 DoAndReturn(func(ctx context.Context, key client.ObjectKey, crd *apiv1.CustomResourceDefinition, opts ...client.GetOption) error { 2312 return errors.NewNotFound(schema.GroupResource{}, "not found") 2313 }) 2314 } 2315 2316 // expectSyncCACertRancherHTTPCalls asserts all of the expected calls on the HTTP client mock when sync'ing the managed cluster 2317 // CA cert secret 2318 func expectSyncCACertRancherHTTPCalls(t *testing.T, requestSenderMock *mocks.MockRequestSender, caCertSecretData string, rancherClusterState string) { 2319 // Expect an HTTP request to fetch the managed cluster info from Rancher 2320 fetchClusterPath := fmt.Sprintf("/v3/clusters/%s", unitTestRancherClusterID) 2321 requestSenderMock.EXPECT(). 2322 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(fetchClusterPath)). 2323 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2324 2325 r := io.NopCloser(bytes.NewReader([]byte(`{"state": "` + rancherClusterState + `", "agentImage":"test-image:1.0.0"}`))) 2326 resp := &http.Response{ 2327 StatusCode: http.StatusOK, 2328 Body: r, 2329 Request: &http.Request{Method: http.MethodGet}, 2330 } 2331 return resp, nil 2332 }) 2333 2334 // Expect an HTTP request to fetch the Rancher TLS additional CA secret from the managed cluster and return an HTTP 404 2335 managedClusterAdditionalTLSCAPath := fmt.Sprintf("/k8s/clusters/%s/api/v1/namespaces/cattle-system/secrets/tls-ca", unitTestRancherClusterID) 2336 requestSenderMock.EXPECT(). 2337 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(managedClusterAdditionalTLSCAPath)). 2338 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2339 r := io.NopCloser(bytes.NewReader([]byte{})) 2340 resp := &http.Response{ 2341 StatusCode: http.StatusNotFound, 2342 Body: r, 2343 Request: &http.Request{Method: http.MethodGet}, 2344 } 2345 return resp, nil 2346 }) 2347 2348 // Expect an HTTP request to fetch the Verrazzano system TLS CA secret from the managed cluster and return the secret 2349 managedClusterSystemCAPath := fmt.Sprintf("/k8s/clusters/%s/api/v1/namespaces/verrazzano-system/secrets/verrazzano-tls", unitTestRancherClusterID) 2350 requestSenderMock.EXPECT(). 2351 Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(managedClusterSystemCAPath)). 2352 DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) { 2353 statusCode := http.StatusOK 2354 if len(caCertSecretData) == 0 { 2355 statusCode = http.StatusNotFound 2356 } 2357 r := io.NopCloser(bytes.NewReader([]byte(caCertSecretData))) 2358 resp := &http.Response{ 2359 StatusCode: statusCode, 2360 Body: r, 2361 Request: &http.Request{Method: http.MethodGet}, 2362 } 2363 return resp, nil 2364 }) 2365 } 2366 2367 // expectSyncCACertRancherK8sCalls asserts all of the expected calls on the Kubernetes client mock when sync'ing the managed cluster 2368 // CA cert secret 2369 func expectSyncCACertRancherK8sCalls(t *testing.T, k8sMock *mocks.MockClient, mockRequestSender *mocks.MockRequestSender, shouldSyncCACert bool) { 2370 asserts := assert.New(t) 2371 2372 if shouldSyncCACert { 2373 expectRancherConfigK8sCalls(t, k8sMock, true) 2374 2375 // Expect a call to get the CA cert secret for the managed cluster - return not found 2376 k8sMock.EXPECT(). 2377 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: "ca-secret-test"}), gomock.Not(gomock.Nil()), gomock.Any()). 2378 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoMultiClusterNamespace, Resource: "Secret"}, "ca-secret-test")) 2379 2380 // Expect a call to create the CA cert secret for the managed cluster 2381 k8sMock.EXPECT(). 2382 Create(gomock.Any(), gomock.Any(), gomock.Any()). 2383 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.CreateOption) error { 2384 data := secret.Data[caCertSecretKey] 2385 asserts.NotZero(len(data), "Expected data in CA cert secret") 2386 return nil 2387 }) 2388 2389 // Expect a call to update the VMC with ca secret name 2390 k8sMock.EXPECT(). 2391 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2392 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 2393 asserts.Equal(vmc.Spec.CASecret, getCASecretName(vmc.Name), "CA secret name %s did not match", vmc.Spec.CASecret) 2394 return nil 2395 }) 2396 } 2397 } 2398 2399 // getScrapeConfig gets a representation of the vmc scrape configuration from the provided yaml 2400 func getScrapeConfig(prometheusYaml string, name string) (*gabs.Container, error) { 2401 cfg, err := parsePrometheusConfig(prometheusYaml) 2402 if err != nil { 2403 return nil, err 2404 } 2405 scrapeConfigs := cfg.Path(scrapeConfigsKey).Children() 2406 return getJob(scrapeConfigs, name), nil 2407 } 2408 2409 // getJob returns the scrape config job identified by the passed name from the slice of scrape configs 2410 func getJob(scrapeConfigs []*gabs.Container, name string) *gabs.Container { 2411 var job *gabs.Container 2412 for _, scrapeConfig := range scrapeConfigs { 2413 jobName := scrapeConfig.Search(constants.PrometheusJobNameKey).Data() 2414 if jobName == name { 2415 job = scrapeConfig 2416 break 2417 } 2418 } 2419 return job 2420 } 2421 2422 // getPrometheusHost returns the prometheus host for testManagedCluster 2423 func getPrometheusHost() string { 2424 return "prometheus.vmi.system.default.1.2.3.4.nip.io" 2425 } 2426 2427 // getPrometheusHost returns the prometheus host for testManagedCluster 2428 func getCaCrt() string { 2429 // this is fake data 2430 return " -----BEGIN CERTIFICATE-----\n" + 2431 " MIIBiDCCAS6gAwIBAgIBADAKBggqhkjOPQQDAjA7MRwwGgYDVQQKExNkeW5hbWlj\n" + 2432 " -----END CERTIFICATE-----" 2433 } 2434 2435 func expectStatusUpdateReadyCondition(asserts *assert.Assertions, mock *mocks.MockClient, mockStatus *mocks.MockStatusWriter, expectReady corev1.ConditionStatus, msg string, assertManagedCACondition bool) { 2436 // mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2437 // DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 2438 // return nil 2439 // }) 2440 2441 mock.EXPECT().Status().Return(mockStatus) 2442 mockStatus.EXPECT(). 2443 Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()). 2444 DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error { 2445 readyFound := false 2446 managedCaFound := false 2447 readyConditionCount := 0 2448 managedCaConditionCount := 0 2449 for _, condition := range vmc.Status.Conditions { 2450 if condition.Type == v1alpha1.ConditionReady { 2451 readyConditionCount++ 2452 if condition.Status == expectReady { 2453 readyFound = true 2454 asserts.Contains(condition.Message, msg) 2455 } 2456 } 2457 if condition.Type == v1alpha1.ConditionManagedCARetrieved { 2458 managedCaConditionCount++ 2459 if condition.Status == expectReady { 2460 managedCaFound = true 2461 // asserts.Contains(condition.Message, msg) 2462 } 2463 } 2464 } 2465 asserts.True(readyFound, "Expected Ready condition on VMC not found") 2466 asserts.Equal(1, readyConditionCount, "Found more than one Ready condition") 2467 if assertManagedCACondition { 2468 asserts.True(managedCaFound, "Expected ManagedCARetrieved condition on VMC not found") 2469 asserts.Equal(1, managedCaConditionCount, "Found more than one ManagedCARetrieved condition") 2470 } 2471 2472 return nil 2473 }) 2474 } 2475 2476 // validateScrapeConfig validates that a scrape config job has the expected field names and values 2477 func validateScrapeConfig(t *testing.T, scrapeConfig *gabs.Container, caBasePath string, expectTLSConfig bool) { 2478 asserts := assert.New(t) 2479 asserts.NotNil(scrapeConfig) 2480 asserts.Equal(getPrometheusHost(), 2481 scrapeConfig.Search("static_configs", "0", "targets", "0").Data(), "No host entry found") 2482 asserts.NotEmpty(scrapeConfig.Search("basic_auth", "password").Data(), "No password") 2483 2484 // assert that the verrazzano_cluster label is added in the static config 2485 asserts.Equal(testManagedCluster, scrapeConfig.Search( 2486 "static_configs", "0", "labels", "verrazzano_cluster").Data(), 2487 "Label verrazzano_cluster not set correctly in static_configs") 2488 2489 // assert that the VMC job relabels verrazzano_cluster label to the right value 2490 asserts.Equal("verrazzano_cluster", scrapeConfig.Search("metric_relabel_configs", "0", 2491 "target_label").Data(), 2492 "metric_relabel_configs entry to post-process verrazzano_cluster label does not have expected target_label value") 2493 asserts.Equal(testManagedCluster, scrapeConfig.Search("metric_relabel_configs", "0", 2494 "replacement").Data(), 2495 "metric_relabel_configs entry to post-process verrazzano_cluster label does not have right replacement value") 2496 schemePath := scrapeConfig.Path("scheme") 2497 tlsScrapeConfig := scrapeConfig.Search("tls_config", "ca_file") 2498 if expectTLSConfig { 2499 asserts.Equal("https", schemePath.Data(), "wrong scheme") 2500 assert.NotNil(t, tlsScrapeConfig) 2501 asserts.Equal(caBasePath+"ca-test", tlsScrapeConfig.Data(), "Wrong cert path") 2502 } else { 2503 assert.Nil(t, tlsScrapeConfig) 2504 asserts.Equal("https", schemePath.Data(), "wrong scheme") 2505 } 2506 } 2507 2508 // expectRancherConfigK8sCalls asserts all of the expected calls on the Kubernetes client mock 2509 // when creating a new Rancher config for the purpose of making http calls 2510 func expectRancherConfigK8sCalls(t *testing.T, k8sMock *mocks.MockClient, admin bool) { 2511 // Expect a call to get the ingress host name 2512 k8sMock.EXPECT(). 2513 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherIngressName}), gomock.Not(gomock.Nil()), gomock.Any()). 2514 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error { 2515 rule := networkingv1.IngressRule{Host: "rancher.unit-test.com"} 2516 ingress.Spec.Rules = append(ingress.Spec.Rules, rule) 2517 return nil 2518 }) 2519 2520 // Expect a call to get the secret with the Rancher root CA cert 2521 k8sMock.EXPECT(). 2522 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: rancherNamespace, Name: rancherTLSSecret}), gomock.Not(gomock.Nil()), gomock.Any()). 2523 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2524 secret.Data = map[string][]byte{ 2525 "ca.crt": {}, 2526 } 2527 return nil 2528 }) 2529 2530 secNS := constants.VerrazzanoMultiClusterNamespace 2531 secName := constants.VerrazzanoClusterRancherName 2532 if admin { 2533 secNS = constants.RancherSystemNamespace 2534 secName = rancherAdminSecret 2535 } 2536 // Expect a call to get the admin secret 2537 k8sMock.EXPECT(). 2538 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: secNS, Name: secName}), gomock.Not(gomock.Nil()), gomock.Any()). 2539 DoAndReturn(func(ctx context.Context, nsName types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2540 secret.Data = map[string][]byte{ 2541 "password": []byte("super-secret"), 2542 } 2543 return nil 2544 }) 2545 2546 // Expect a call to get the Rancher additional CA secret 2547 k8sMock.EXPECT(). 2548 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.PrivateCABundle}), gomock.Not(gomock.Nil()), gomock.Any()). 2549 Return(errors.NewNotFound(schema.GroupResource{Group: constants.VerrazzanoSystemNamespace, Resource: "Secret"}, constants.PrivateCABundle)) 2550 } 2551 2552 // expectMockCallsForDelete mocks expectations for the VMC deletion scenario. These are the common mock expectations across 2553 // tests that exercise delete functionality. Individual tests add mock expectations after these to test various scenarios. 2554 func expectMockCallsForDelete(t *testing.T, mock *mocks.MockClient, namespace string) { 2555 asserts := assert.New(t) 2556 2557 // Expect a call to get the VerrazzanoManagedCluster resource. 2558 mock.EXPECT(). 2559 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: testManagedCluster}, gomock.Not(gomock.Nil()), gomock.Any()). 2560 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error { 2561 vmc.TypeMeta = metav1.TypeMeta{ 2562 APIVersion: apiVersion, 2563 Kind: kind} 2564 vmc.ObjectMeta = metav1.ObjectMeta{ 2565 Namespace: name.Namespace, 2566 Name: name.Name, 2567 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 2568 Finalizers: []string{finalizerName}} 2569 vmc.Status = v1alpha1.VerrazzanoManagedClusterStatus{ 2570 PrometheusHost: getPrometheusHost(), 2571 RancherRegistration: v1alpha1.RancherRegistration{ 2572 ClusterID: unitTestRancherClusterID, 2573 }, 2574 } 2575 2576 return nil 2577 }) 2578 2579 jobs := ` - ` + constants.PrometheusJobNameKey + `: test 2580 scrape_interval: 20s 2581 scrape_timeout: 15s 2582 scheme: http 2583 - ` + constants.PrometheusJobNameKey + `: test2 2584 scrape_interval: 20s 2585 scrape_timeout: 15s 2586 scheme: http` 2587 2588 // Expect a call to get the additional scrape config secret - return it configured with the two scrape jobs 2589 mock.EXPECT(). 2590 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: constants.PromAdditionalScrapeConfigsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2591 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2592 secret.Data = map[string][]byte{ 2593 constants.PromAdditionalScrapeConfigsSecretKey: []byte(jobs), 2594 } 2595 return nil 2596 }) 2597 2598 // Expect a call to get the additional scrape config secret (we call controllerruntime.CreateOrUpdate so it fetches again) - return it 2599 mock.EXPECT(). 2600 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: constants.PromAdditionalScrapeConfigsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2601 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2602 secret.Data = map[string][]byte{ 2603 constants.PromAdditionalScrapeConfigsSecretKey: []byte(jobs), 2604 } 2605 return nil 2606 }) 2607 2608 // Expect a call to update the additional scrape config secret 2609 mock.EXPECT(). 2610 Update(gomock.Any(), gomock.Any(), gomock.Any()). 2611 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.UpdateOption) error { 2612 // validate that the scrape config for the managed cluster is no longer present 2613 scrapeConfigs, err := metricsutils.ParseScrapeConfig(string(secret.Data[constants.PromAdditionalScrapeConfigsSecretKey])) 2614 if err != nil { 2615 return err 2616 } 2617 asserts.Len(scrapeConfigs.Children(), 1, "Expected only one scrape config") 2618 scrapeJobName := scrapeConfigs.Children()[0].Search(constants.PrometheusJobNameKey).Data() 2619 asserts.Equal("test2", scrapeJobName) 2620 return nil 2621 }) 2622 2623 // Expect a call to get the verrazzano-monitoring namespace 2624 mock.EXPECT(). 2625 Get(gomock.Any(), types.NamespacedName{Name: vpoconstants.VerrazzanoMonitoringNamespace}, gomock.Not(gomock.Nil()), gomock.Any()). 2626 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 2627 ns.SetName(vpoconstants.VerrazzanoMonitoringNamespace) 2628 return nil 2629 }) 2630 2631 // Expect a call to get the managed cluster TLS certs secret - return it configured with two managed cluster certs 2632 mock.EXPECT(). 2633 Get(gomock.Any(), types.NamespacedName{Namespace: vpoconstants.VerrazzanoMonitoringNamespace, Name: vpoconstants.PromManagedClusterCACertsSecretName}, gomock.Not(gomock.Nil()), gomock.Any()). 2634 DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error { 2635 secret.Data = map[string][]byte{ 2636 "ca-test": []byte("ca-cert-1"), 2637 "ca-test2": []byte("ca-cert-1"), 2638 } 2639 return nil 2640 }) 2641 2642 // Expect a call to update the managed cluster TLS certs secret 2643 mock.EXPECT(). 2644 Update(gomock.Any(), gomock.Any(), gomock.Any()). 2645 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.UpdateOption) error { 2646 // validate that the cert for the cluster being deleted is no longer present 2647 asserts.Len(secret.Data, 1, "Expected only one managed cluster cert") 2648 asserts.Contains(secret.Data, "ca-test2", "Expected to find cert for managed cluster not being deleted") 2649 return nil 2650 }) 2651 2652 // Expect a call to delete the argocd cluster secret 2653 mock.EXPECT(). 2654 Delete(gomock.Any(), gomock.Any(), gomock.Any()). 2655 DoAndReturn(func(ctx context.Context, secret *corev1.Secret, opts ...client.UpdateOption) error { 2656 return nil 2657 }) 2658 2659 // Expect Rancher k8s calls to configure the API client 2660 expectRancherConfigK8sCalls(t, mock, true) 2661 } 2662 2663 func expectMockCallsForCreateClusterRoleBindingTemplate(mock *mocks.MockClient, clusterID string) { 2664 name := fmt.Sprintf("crtb-verrazzano-cluster-%s", clusterID) 2665 mock.EXPECT(). 2666 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Name: name, Namespace: clusterID}), gomock.Not(gomock.Nil()), gomock.Any()). 2667 DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, resource *unstructured.Unstructured, opts ...client.GetOption) error { 2668 data := resource.UnstructuredContent() 2669 data[ClusterRoleTemplateBindingAttributeClusterName] = clusterID 2670 data[ClusterRoleTemplateBindingAttributeUserName] = constants.VerrazzanoClusterRancherUsername 2671 data[ClusterRoleTemplateBindingAttributeRoleTemplateName] = constants.VerrazzanoClusterRancherName 2672 return nil 2673 }) 2674 } 2675 2676 func expectMockCallsForListingRancherUsers(mock *mocks.MockClient) { 2677 usersList := unstructured.UnstructuredList{} 2678 usersList.SetGroupVersionKind(schema.GroupVersionKind{ 2679 Group: APIGroupRancherManagement, 2680 Version: APIGroupVersionRancherManagement, 2681 Kind: UserListKind, 2682 }) 2683 mock.EXPECT(). 2684 List(gomock.Any(), &usersList, gomock.Not(gomock.Nil())). 2685 DoAndReturn(func(ctx context.Context, userList *unstructured.UnstructuredList, options *client.ListOptions) error { 2686 user := unstructured.Unstructured{} 2687 user.SetGroupVersionKind(schema.GroupVersionKind{ 2688 Group: APIGroupRancherManagement, 2689 Version: APIGroupVersionRancherManagement, 2690 Kind: UserKind, 2691 }) 2692 user.SetName(constants.VerrazzanoClusterRancherUsername) 2693 data := user.UnstructuredContent() 2694 data[UserUsernameAttribute] = constants.VerrazzanoClusterRancherUsername 2695 userList.Items = []unstructured.Unstructured{user} 2696 return nil 2697 }) 2698 }