github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/clusters/verrazzanoproject/verrazzanoproject_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 verrazzanoproject 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/go-logr/logr" 11 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 12 13 "testing" 14 "time" 15 16 "github.com/golang/mock/gomock" 17 asserts "github.com/stretchr/testify/assert" 18 clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 19 "github.com/verrazzano/verrazzano/application-operator/constants" 20 "github.com/verrazzano/verrazzano/application-operator/controllers/clusters" 21 clusterstest "github.com/verrazzano/verrazzano/application-operator/controllers/clusters/test" 22 "github.com/verrazzano/verrazzano/application-operator/mocks" 23 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 24 vmcclient "github.com/verrazzano/verrazzano/platform-operator/clientset/versioned/scheme" 25 "go.uber.org/zap" 26 corev1 "k8s.io/api/core/v1" 27 netv1 "k8s.io/api/networking/v1" 28 rbacv1 "k8s.io/api/rbac/v1" 29 "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 ) 36 37 const finalizer = "project.verrazzano.io" 38 39 var testLabels = map[string]string{"label1": "test1", "label2": "test2"} 40 41 var existingNS = clustersv1alpha1.NamespaceTemplate{ 42 Metadata: metav1.ObjectMeta{ 43 Name: "existingNS", 44 Labels: testLabels, 45 }, 46 } 47 48 // Omit labels on this namespace to handle test case of starting with an empty label 49 var newNS = clustersv1alpha1.NamespaceTemplate{ 50 Metadata: metav1.ObjectMeta{ 51 Name: "newNS", 52 }, 53 } 54 55 var ns1 = clustersv1alpha1.NamespaceTemplate{ 56 Metadata: metav1.ObjectMeta{ 57 Name: "ns1", 58 }, 59 } 60 61 var ns1Netpol = clustersv1alpha1.NetworkPolicyTemplate{ 62 Metadata: metav1.ObjectMeta{ 63 Name: "ns1Netpol", 64 Namespace: "ns1", 65 }, 66 Spec: netv1.NetworkPolicySpec{}, 67 } 68 69 // roleBindingMatcher is a gomock Matcher that matches a rbacv1.RoleBinding based on roleref name 70 type roleBindingMatcher struct{ roleRefName string } 71 72 func RoleBindingMatcher(roleName string) gomock.Matcher { 73 return &roleBindingMatcher{roleName} 74 } 75 76 func (r *roleBindingMatcher) Matches(x interface{}) bool { 77 if rb, ok := x.(*rbacv1.RoleBinding); ok { 78 if r.roleRefName == rb.RoleRef.Name { 79 return true 80 } 81 } 82 return false 83 } 84 85 func (r *roleBindingMatcher) String() string { 86 return "rolebinding roleref name does not match expected name: " + r.roleRefName 87 } 88 89 // TestReconcilerSetupWithManager test the creation of the Reconciler. 90 // GIVEN a controller implementation 91 // WHEN the controller is created 92 // THEN verify no error is returned 93 func TestReconcilerSetupWithManager(t *testing.T) { 94 assert := asserts.New(t) 95 96 var mocker *gomock.Controller 97 var mgr *mocks.MockManager 98 var cli *mocks.MockClient 99 var scheme *runtime.Scheme 100 var reconciler Reconciler 101 var err error 102 103 mocker = gomock.NewController(t) 104 mgr = mocks.NewMockManager(mocker) 105 cli = mocks.NewMockClient(mocker) 106 scheme = runtime.NewScheme() 107 _ = clustersv1alpha1.AddToScheme(scheme) 108 reconciler = Reconciler{Client: cli, Scheme: scheme} 109 mgr.EXPECT().GetControllerOptions().AnyTimes() 110 mgr.EXPECT().GetScheme().Return(scheme) 111 mgr.EXPECT().GetLogger().Return(logr.Discard()) 112 mgr.EXPECT().SetFields(gomock.Any()).Return(nil).AnyTimes() 113 mgr.EXPECT().Add(gomock.Any()).Return(nil).AnyTimes() 114 err = reconciler.SetupWithManager(mgr) 115 mocker.Finish() 116 assert.NoError(err) 117 } 118 119 // TestReconcileVerrazzanoProject tests reconciling a VerrazzanoProject. 120 // GIVEN a VerrazzanoProject resource is created 121 // WHEN the controller Reconcile function is called 122 // THEN namespaces are created 123 func TestReconcileVerrazzanoProject(t *testing.T) { 124 const existingVP = "existingVP" 125 126 adminSubjects := []rbacv1.Subject{ 127 {Kind: "Group", Name: "project-admin-test-group"}, 128 {Kind: "User", Name: "project-admin-test-user"}, 129 } 130 131 monitorSubjects := []rbacv1.Subject{ 132 {Kind: "Group", Name: "project-monitor-test-group"}, 133 {Kind: "User", Name: "project-monitor-test-user"}, 134 } 135 136 defaultAdminSubjects := []rbacv1.Subject{ 137 {Kind: "Group", Name: fmt.Sprintf("verrazzano-project-%s-admins", existingVP)}, 138 } 139 140 defaultMonitorSubjects := []rbacv1.Subject{ 141 {Kind: "Group", Name: fmt.Sprintf("verrazzano-project-%s-monitors", existingVP)}, 142 } 143 144 clusterList := []clustersv1alpha1.Cluster{ 145 {Name: clusterstest.UnitTestClusterName}, 146 } 147 148 type fields struct { 149 vpNamespace string 150 vpName string 151 nsList []clustersv1alpha1.NamespaceTemplate 152 adminSubjects []rbacv1.Subject 153 monitorSubjects []rbacv1.Subject 154 placementList []clustersv1alpha1.Cluster 155 } 156 tests := []struct { 157 name string 158 fields fields 159 wantErr bool 160 }{ 161 { 162 "Update namespace", 163 fields{ 164 constants.VerrazzanoMultiClusterNamespace, 165 existingVP, 166 []clustersv1alpha1.NamespaceTemplate{existingNS}, 167 adminSubjects, 168 monitorSubjects, 169 clusterList, 170 }, 171 false, 172 }, 173 { 174 "Create namespace", 175 fields{ 176 constants.VerrazzanoMultiClusterNamespace, 177 existingVP, 178 []clustersv1alpha1.NamespaceTemplate{newNS}, 179 nil, 180 nil, 181 clusterList, 182 }, 183 false, 184 }, 185 { 186 "Create project admin rolebindings", 187 fields{ 188 constants.VerrazzanoMultiClusterNamespace, 189 existingVP, 190 []clustersv1alpha1.NamespaceTemplate{newNS}, 191 adminSubjects, 192 nil, 193 clusterList, 194 }, 195 false, 196 }, 197 { 198 "Create project monitor rolebindings", 199 fields{ 200 constants.VerrazzanoMultiClusterNamespace, 201 existingVP, 202 []clustersv1alpha1.NamespaceTemplate{newNS}, 203 nil, 204 monitorSubjects, 205 clusterList, 206 }, 207 false, 208 }, 209 { 210 fmt.Sprintf("VP not in %s namespace", constants.VerrazzanoMultiClusterNamespace), 211 fields{ 212 "random-namespace", 213 existingVP, 214 []clustersv1alpha1.NamespaceTemplate{newNS}, 215 nil, 216 nil, 217 clusterList, 218 }, 219 false, 220 }, 221 { 222 "VP not found", 223 fields{ 224 constants.VerrazzanoMultiClusterNamespace, 225 "not-found-vp", 226 []clustersv1alpha1.NamespaceTemplate{existingNS}, 227 nil, 228 nil, 229 clusterList, 230 }, 231 false, 232 }, 233 } 234 for _, tt := range tests { 235 t.Run(tt.name, func(t *testing.T) { 236 assert := asserts.New(t) 237 238 mocker := gomock.NewController(t) 239 mockClient := mocks.NewMockClient(mocker) 240 mockStatusWriter := mocks.NewMockStatusWriter(mocker) 241 242 expectedAdminSubjects := defaultAdminSubjects 243 if len(tt.fields.adminSubjects) > 0 { 244 expectedAdminSubjects = tt.fields.adminSubjects 245 } 246 expectedMonitorSubjects := defaultMonitorSubjects 247 if len(tt.fields.monitorSubjects) > 0 { 248 expectedMonitorSubjects = tt.fields.monitorSubjects 249 } 250 251 // expect call to get a verrazzanoproject 252 if tt.fields.vpName == existingVP { 253 mockClient.EXPECT(). 254 Get(gomock.Any(), types.NamespacedName{Namespace: tt.fields.vpNamespace, Name: tt.fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()). 255 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error { 256 vp.Namespace = tt.fields.vpNamespace 257 vp.Name = tt.fields.vpName 258 vp.ObjectMeta.Finalizers = []string{finalizer} 259 vp.Spec.Template.Namespaces = tt.fields.nsList 260 vp.Spec.Template.Security.ProjectAdminSubjects = expectedAdminSubjects 261 vp.Spec.Template.Security.ProjectMonitorSubjects = expectedMonitorSubjects 262 vp.Spec.Placement.Clusters = tt.fields.placementList 263 return nil 264 }) 265 266 if tt.fields.vpNamespace == constants.VerrazzanoMultiClusterNamespace { 267 if tt.fields.nsList[0].Metadata.Name == existingNS.Metadata.Name { 268 // expect call to get vz system namespace 269 mockClient.EXPECT(). 270 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: constants.VerrazzanoSystemNamespace}, gomock.Not(gomock.Nil()), gomock.Any()). 271 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 272 ns.Labels = make(map[string]string) 273 ns.Labels["istio-injection"] = "enabled" 274 275 return nil 276 }) 277 // expect call to get a namespace 278 mockClient.EXPECT(). 279 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: tt.fields.nsList[0].Metadata.Name}, gomock.Not(gomock.Nil()), gomock.Any()). 280 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 281 return nil 282 }) 283 284 // expect call to update the namespace 285 mockClient.EXPECT(). 286 Update(gomock.Any(), gomock.Any(), gomock.Any()). 287 DoAndReturn(func(ctx context.Context, namespace *corev1.Namespace, opts ...client.UpdateOption) error { 288 assert.Equal(tt.fields.nsList[0].Metadata.Name, namespace.Name, "namespace name did not match") 289 _, labelExists := namespace.Labels[vzconst.VerrazzanoManagedLabelKey] 290 assert.True(labelExists, fmt.Sprintf("the label %s does not exist", vzconst.VerrazzanoManagedLabelKey)) 291 _, labelExists = namespace.Labels[constants.LabelIstioInjection] 292 assert.True(labelExists, fmt.Sprintf("the label %s does not exist", constants.LabelIstioInjection)) 293 return nil 294 }) 295 296 if len(expectedAdminSubjects) > 0 { 297 mockUpdatedRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name, projectAdminRole, projectAdminK8sRole, expectedAdminSubjects) 298 } 299 if len(expectedMonitorSubjects) > 0 { 300 mockUpdatedRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name, projectMonitorRole, projectMonitorK8sRole, expectedMonitorSubjects) 301 } 302 303 mockNewManagedClusterRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name) 304 305 mockClusterRoleBindingNoDelete(assert, mockClient, tt.fields.nsList[0].Metadata.Name) 306 } else { // not an existing namespace 307 // expect call to get vz system namespace 308 mockClient.EXPECT(). 309 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: constants.VerrazzanoSystemNamespace}, gomock.Not(gomock.Nil()), gomock.Any()). 310 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 311 ns.Labels = make(map[string]string) 312 ns.Labels["istio-injection"] = "enabled" 313 314 return nil 315 }) 316 // expect call to get a namespace that returns namespace not found 317 mockClient.EXPECT(). 318 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: tt.fields.nsList[0].Metadata.Name}, gomock.Not(gomock.Nil()), gomock.Any()). 319 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Namespace"}, tt.fields.nsList[0].Metadata.Name)) 320 321 // expect call to create a namespace 322 mockClient.EXPECT(). 323 Create(gomock.Any(), gomock.Any(), gomock.Any()). 324 DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.CreateOption) error { 325 assert.Equal(tt.fields.nsList[0].Metadata.Name, ns.Name, "namespace name did not match") 326 return nil 327 }) 328 329 if len(expectedAdminSubjects) > 0 { 330 mockNewRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name, projectAdminRole, projectAdminK8sRole, expectedAdminSubjects) 331 } 332 if len(expectedMonitorSubjects) > 0 { 333 mockNewRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name, projectMonitorRole, projectMonitorK8sRole, expectedMonitorSubjects) 334 } 335 336 mockNewManagedClusterRoleBindingExpectations(assert, mockClient, tt.fields.nsList[0].Metadata.Name) 337 338 mockClusterRoleBindingNoDelete(assert, mockClient, tt.fields.nsList[0].Metadata.Name) 339 } 340 } // END VerrazzanoProject is in the expected Multi cluster namespace 341 342 // expect call to list network policies 343 mockClient.EXPECT(). 344 List(gomock.Any(), gomock.Any(), gomock.Any()). 345 DoAndReturn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error { 346 // return no resources 347 return nil 348 }) 349 350 // status update should be to "succeeded" in both existing and new namespace 351 doExpectStatusUpdateSucceeded(mockClient, mockStatusWriter, assert) 352 353 } else { // The VerrazzanoProject is not an existing one i.e. not existingVP 354 mockClient.EXPECT(). 355 Get(gomock.Any(), types.NamespacedName{Namespace: tt.fields.vpNamespace, Name: tt.fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()). 356 Return(errors.NewNotFound(schema.GroupResource{Group: clustersv1alpha1.SchemeGroupVersion.Group, Resource: clustersv1alpha1.VerrazzanoProjectResource}, tt.fields.vpName)) 357 } 358 359 // Make the request 360 request := clusterstest.NewRequest(tt.fields.vpNamespace, tt.fields.vpName) 361 reconciler := newVerrazzanoProjectReconciler(mockClient) 362 _ = vmcclient.AddToScheme(reconciler.Scheme) 363 _, err := reconciler.Reconcile(context.TODO(), request) 364 365 mocker.Finish() 366 367 if (err != nil) != tt.wantErr { 368 t.Errorf("syncVerrazzanoProjects() error = %v, wantErr %v", err, tt.wantErr) 369 } 370 }) 371 } 372 } 373 374 // TestNetworkPolicies tests the creation of network policies 375 // GIVEN a VerrazzanoProject resource is create with specified network policies 376 // WHEN the controller Reconcile function is called 377 // THEN the network policies are created 378 func TestNetworkPolicies(t *testing.T) { 379 const vpName = "testNetwokPolicies" 380 381 assert := asserts.New(t) 382 383 mocker := gomock.NewController(t) 384 mockClient := mocks.NewMockClient(mocker) 385 mockStatusWriter := mocks.NewMockStatusWriter(mocker) 386 387 // Expect call to get the project 388 mockClient.EXPECT(). 389 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: vpName}, gomock.Not(gomock.Nil()), gomock.Any()). 390 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error { 391 vp.Namespace = constants.VerrazzanoMultiClusterNamespace 392 vp.Name = vpName 393 vp.ObjectMeta.Finalizers = []string{finalizer} 394 vp.Spec.Template.Namespaces = []clustersv1alpha1.NamespaceTemplate{ns1} 395 vp.Spec.Template.NetworkPolicies = []clustersv1alpha1.NetworkPolicyTemplate{ns1Netpol} 396 vp.Spec.Placement.Clusters = []clustersv1alpha1.Cluster{{Name: clusterstest.UnitTestClusterName}} 397 return nil 398 }) 399 400 // expect call to get vz system namespace 401 mockClient.EXPECT(). 402 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: constants.VerrazzanoSystemNamespace}, gomock.Not(gomock.Nil()), gomock.Any()). 403 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 404 ns.Labels = make(map[string]string) 405 ns.Labels["istio-injection"] = "enabled" 406 407 return nil 408 }) 409 410 // expect call to get a namespace 411 mockClient.EXPECT(). 412 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: ns1.Metadata.Name}, gomock.Not(gomock.Nil()), gomock.Any()). 413 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error { 414 return nil 415 }) 416 417 // expect call to update the namespace 418 mockClient.EXPECT(). 419 Update(gomock.Any(), gomock.Any(), gomock.Any()). 420 DoAndReturn(func(ctx context.Context, namespace *corev1.Namespace, opts ...client.UpdateOption) error { 421 return nil 422 }) 423 424 defaultAdminSubjects := []rbacv1.Subject{ 425 {Kind: "Group", Name: fmt.Sprintf("verrazzano-project-%s-admins", vpName)}, 426 } 427 defaultMonitorSubjects := []rbacv1.Subject{ 428 {Kind: "Group", Name: fmt.Sprintf("verrazzano-project-%s-monitors", vpName)}, 429 } 430 431 mockUpdatedRoleBindingExpectations(assert, mockClient, ns1.Metadata.Name, projectAdminRole, projectAdminK8sRole, defaultAdminSubjects) 432 mockUpdatedRoleBindingExpectations(assert, mockClient, ns1.Metadata.Name, projectMonitorRole, projectMonitorK8sRole, defaultMonitorSubjects) 433 434 mockNewManagedClusterRoleBindingExpectations(assert, mockClient, ns1.Metadata.Name) 435 436 mockClusterRoleBindingNoDelete(assert, mockClient, ns1.Metadata.Name) 437 438 // expect call to get a network policy 439 mockClient.EXPECT(). 440 Get(gomock.Any(), types.NamespacedName{Namespace: ns1Netpol.Metadata.Namespace, Name: ns1Netpol.Metadata.Name}, gomock.Not(gomock.Nil()), gomock.Any()). 441 Return(errors.NewNotFound(schema.GroupResource{Group: ns1.Metadata.Namespace, Resource: "NetworkPolicy"}, ns1.Metadata.Name)) 442 443 // Expect call to create the network policies in the namespace 444 mockClient.EXPECT(). 445 Create(gomock.Any(), gomock.Any(), gomock.Any()). 446 DoAndReturn(func(ctx context.Context, policy *netv1.NetworkPolicy, opts ...client.CreateOption) error { 447 return nil 448 }) 449 450 // Expect call to get the network policies in the namespace 451 mockClient.EXPECT(). 452 List(gomock.Any(), gomock.Any(), gomock.Any()). 453 DoAndReturn(func(ctx context.Context, list *netv1.NetworkPolicyList, opts ...client.ListOption) error { 454 list.Items = []netv1.NetworkPolicy{{ 455 ObjectMeta: metav1.ObjectMeta{Namespace: "ns1", Name: "ns1Netpol"}, 456 Spec: netv1.NetworkPolicySpec{}, 457 }, 458 { 459 ObjectMeta: metav1.ObjectMeta{Namespace: "ns2", Name: "ns2Netpol"}, 460 Spec: netv1.NetworkPolicySpec{}, 461 }} 462 return nil 463 }) 464 465 // Expect call to delete network policy ns2 since it is not defined in the project 466 mockClient.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()). 467 DoAndReturn(func(ctx context.Context, policy *netv1.NetworkPolicy, opts ...client.DeleteOption) error { 468 assert.Equal("ns2Netpol", policy.Name, "Incorrect NetworkPolicy being deleted") 469 return nil 470 }) 471 472 // the status update should be to success status/conditions on the VerrazzanoProject 473 // status update should be to "succeeded" in both existing and new namespace 474 doExpectStatusUpdateSucceeded(mockClient, mockStatusWriter, assert) 475 476 // Make the request 477 request := clusterstest.NewRequest(constants.VerrazzanoMultiClusterNamespace, vpName) 478 reconciler := newVerrazzanoProjectReconciler(mockClient) 479 _ = vmcclient.AddToScheme(reconciler.Scheme) 480 _, err := reconciler.Reconcile(context.TODO(), request) 481 assert.NoError(err) 482 483 mocker.Finish() 484 } 485 486 // TestDeleteVerrazzanoProject tests deleting a VerrazzanoProject 487 // GIVEN a VerrazzanoProject resource is deleted 488 // WHEN the controller Reconcile function is called 489 // THEN the resource is successfully cleaned up 490 func TestDeleteVerrazzanoProject(t *testing.T) { 491 vpName := "testDelete" 492 assert := asserts.New(t) 493 494 mocker := gomock.NewController(t) 495 mockClient := mocks.NewMockClient(mocker) 496 497 mockClient.EXPECT(). 498 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: vpName}, gomock.Not(gomock.Nil()), gomock.Any()). 499 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error { 500 vp.Namespace = constants.VerrazzanoMultiClusterNamespace 501 vp.Name = vpName 502 vp.Spec.Template.Namespaces = []clustersv1alpha1.NamespaceTemplate{existingNS} 503 vp.Spec.Placement.Clusters = []clustersv1alpha1.Cluster{{Name: clusterstest.UnitTestClusterName}} 504 vp.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} 505 return nil 506 }) 507 508 // Make the request 509 request := clusterstest.NewRequest(constants.VerrazzanoMultiClusterNamespace, vpName) 510 reconciler := newVerrazzanoProjectReconciler(mockClient) 511 _, err := reconciler.Reconcile(context.TODO(), request) 512 assert.NoError(err) 513 514 mocker.Finish() 515 } 516 517 // TestDeleteVerrazzanoProjectFinalizer tests deleting a VerrazzanoProject with finalizer 518 // GIVEN a VerrazzanoProject resource is deleted 519 // WHEN the controller Reconcile function is called 520 // THEN the resource is successfully cleaned up, including network policies 521 func TestDeleteVerrazzanoProjectFinalizer(t *testing.T) { 522 vpName := "testDelete" 523 assert := asserts.New(t) 524 525 mocker := gomock.NewController(t) 526 mockClient := mocks.NewMockClient(mocker) 527 528 // Expect call to get the project 529 mockClient.EXPECT(). 530 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: vpName}, gomock.Not(gomock.Nil()), gomock.Any()). 531 DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error { 532 vp.Namespace = constants.VerrazzanoMultiClusterNamespace 533 vp.Name = vpName 534 vp.ObjectMeta.Finalizers = []string{finalizer} 535 vp.Spec.Template.Namespaces = []clustersv1alpha1.NamespaceTemplate{existingNS} 536 vp.Spec.Placement.Clusters = []clustersv1alpha1.Cluster{{Name: clusterstest.UnitTestClusterName}} 537 vp.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} 538 return nil 539 }) 540 541 // Expect call to get the network policies in the namespace 542 mockClient.EXPECT(). 543 List(gomock.Any(), gomock.Any(), gomock.Any()). 544 DoAndReturn(func(ctx context.Context, list *netv1.NetworkPolicyList, opts ...client.ListOption) error { 545 list.Items = []netv1.NetworkPolicy{{ 546 ObjectMeta: metav1.ObjectMeta{Namespace: "existingNS", Name: "ns1"}, 547 Spec: netv1.NetworkPolicySpec{}, 548 }} 549 return nil 550 }) 551 552 // Expect call to delete network policies in the namespace 553 mockClient.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 554 555 // Expect call to get list of VerrazzanoProjects 556 mockClient.EXPECT(). 557 List(gomock.Any(), gomock.Any(), gomock.Any()). 558 DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...client.ListOption) error { 559 list.Items = []clustersv1alpha1.VerrazzanoProject{{ 560 ObjectMeta: metav1.ObjectMeta{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: vpName}, 561 Spec: clustersv1alpha1.VerrazzanoProjectSpec{ 562 Template: clustersv1alpha1.ProjectTemplate{ 563 Namespaces: []clustersv1alpha1.NamespaceTemplate{ 564 { 565 Metadata: metav1.ObjectMeta{ 566 Name: "existingNS", 567 }, 568 }, 569 }, 570 }, 571 Placement: clustersv1alpha1.Placement{ 572 Clusters: []clustersv1alpha1.Cluster{ 573 { 574 Name: clusterstest.UnitTestClusterName, 575 }, 576 }, 577 }, 578 }, 579 }} 580 return nil 581 }) 582 583 // Expect call to get list of VerrazzanoManagedCluster 584 mockClient.EXPECT(). 585 List(gomock.Any(), gomock.Any(), gomock.Any()). 586 DoAndReturn(func(ctx context.Context, list *v1alpha1.VerrazzanoManagedClusterList, opts ...client.ListOption) error { 587 list.Items = []v1alpha1.VerrazzanoManagedCluster{{ 588 ObjectMeta: metav1.ObjectMeta{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: clusterstest.UnitTestClusterName}, 589 }} 590 return nil 591 }) 592 593 // expect a call to fetch a rolebinding for the cluster1 role and return rolebinding 594 clusterNameRef := generateRoleBindingManagedClusterRef(clusterstest.UnitTestClusterName) 595 mockClient.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: "existingNS", Name: clusterNameRef}, gomock.AssignableToTypeOf(&rbacv1.RoleBinding{}), gomock.Any()). 596 DoAndReturn(func(ctx context.Context, objectKey types.NamespacedName, rb *rbacv1.RoleBinding, opts ...client.GetOption) error { 597 rb.Name = clusterNameRef 598 rb.Namespace = "existingNS" 599 return nil 600 }) 601 602 // Expect call to delete rolebinding in the namespace 603 mockClient.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 604 605 // the status update should be to success status/conditions on the VerrazzanoProject 606 mockClient.EXPECT(). 607 Update(gomock.Any(), gomock.AssignableToTypeOf(&clustersv1alpha1.VerrazzanoProject{}), gomock.Any()). 608 DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.UpdateOption) error { 609 assert.NotContainsf(vp.Finalizers, finalizer, "finalizer should be cleared") 610 return nil 611 }) 612 613 // Make the request 614 request := clusterstest.NewRequest(constants.VerrazzanoMultiClusterNamespace, vpName) 615 reconciler := newVerrazzanoProjectReconciler(mockClient) 616 _ = vmcclient.AddToScheme(reconciler.Scheme) 617 _, err := reconciler.Reconcile(context.TODO(), request) 618 assert.NoError(err) 619 620 mocker.Finish() 621 } 622 623 // newVerrazzanoProjectReconciler creates a new reconciler for testing 624 // c - The K8s client to inject into the reconciler 625 func newVerrazzanoProjectReconciler(c client.Client) Reconciler { 626 return Reconciler{ 627 Client: c, 628 Log: zap.S().With("test"), 629 Scheme: clusters.NewScheme(), 630 } 631 } 632 633 // mockClusterRoleBindingNoDelete mocks the expectations for deleting the managed cluster rolebinding 634 func mockClusterRoleBindingNoDelete(assert *asserts.Assertions, mockClient *mocks.MockClient, name string) { 635 // Expect call to get list of VerrazzanoProjects 636 mockClient.EXPECT(). 637 List(gomock.Any(), gomock.Any(), gomock.Any()). 638 DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...client.ListOption) error { 639 list.Items = []clustersv1alpha1.VerrazzanoProject{{ 640 ObjectMeta: metav1.ObjectMeta{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: name}, 641 Spec: clustersv1alpha1.VerrazzanoProjectSpec{ 642 Template: clustersv1alpha1.ProjectTemplate{ 643 Namespaces: []clustersv1alpha1.NamespaceTemplate{ 644 { 645 Metadata: metav1.ObjectMeta{ 646 Name: "existingNS", 647 }, 648 }, 649 }, 650 }, 651 Placement: clustersv1alpha1.Placement{ 652 Clusters: []clustersv1alpha1.Cluster{ 653 { 654 Name: clusterstest.UnitTestClusterName, 655 }, 656 }, 657 }, 658 }, 659 }} 660 return nil 661 }) 662 663 // Expect call to get list of VerrazzanoManagedCluster 664 mockClient.EXPECT(). 665 List(gomock.Any(), gomock.Any(), gomock.Any()). 666 DoAndReturn(func(ctx context.Context, list *v1alpha1.VerrazzanoManagedClusterList, opts ...client.ListOption) error { 667 list.Items = []v1alpha1.VerrazzanoManagedCluster{{ 668 ObjectMeta: metav1.ObjectMeta{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: clusterstest.UnitTestClusterName}, 669 }} 670 return nil 671 }) 672 } 673 674 // mockNewManagedClusterRoleBindingExpectations mocks the expectations for a managed cluster role binding 675 func mockNewManagedClusterRoleBindingExpectations(assert *asserts.Assertions, mockClient *mocks.MockClient, namespace string) { 676 clusterNameRef := generateRoleBindingManagedClusterRef(clusterstest.UnitTestClusterName) 677 678 // expect a call to fetch a rolebinding for the specified role and return not found 679 mockClient.EXPECT(). 680 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: clusterNameRef}, gomock.Not(gomock.Nil()), gomock.Any()). 681 Return(errors.NewNotFound(schema.GroupResource{Group: "", Resource: "RoleBinding"}, existingNS.Metadata.Name)) 682 683 managedClusterSubjects := []rbacv1.Subject{ 684 { 685 Kind: "ServiceAccount", 686 Name: clusterNameRef, 687 Namespace: constants.VerrazzanoMultiClusterNamespace, 688 }, 689 } 690 691 // expect a call to create the managed cluster rolebinding 692 mockClient.EXPECT(). 693 Create(gomock.Any(), gomock.Any(), gomock.Any()). 694 DoAndReturn(func(ctx context.Context, rb *rbacv1.RoleBinding, opts ...client.CreateOption) error { 695 assert.Equal(managedClusterRole, rb.RoleRef.Name) 696 assert.Equal("ClusterRole", rb.RoleRef.Kind) 697 assert.Equal(managedClusterSubjects, rb.Subjects) 698 return nil 699 }) 700 } 701 702 // mockNewRoleBindingExpectations mocks the expectations for project rolebindings when the rolebindings do not already exist 703 func mockNewRoleBindingExpectations(assert *asserts.Assertions, mockClient *mocks.MockClient, namespace, role, k8sRole string, subjects []rbacv1.Subject) { 704 // expect a call to fetch a rolebinding for the specified role and return NotFound 705 mockClient.EXPECT(). 706 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: role}, gomock.AssignableToTypeOf(&rbacv1.RoleBinding{}), gomock.Any()). 707 Return(errors.NewNotFound(schema.GroupResource{}, "")) 708 709 // expect a call to create a rolebinding for the subjects to the specified role 710 mockClient.EXPECT(). 711 Create(gomock.Any(), RoleBindingMatcher(role), gomock.Any()). 712 DoAndReturn(func(ctx context.Context, rb *rbacv1.RoleBinding, opts ...client.CreateOption) error { 713 assert.Equal(subjects, rb.Subjects) 714 return nil 715 }) 716 717 // expect a call to fetch a rolebinding for the specified k8s role and return NotFound 718 mockClient.EXPECT(). 719 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: k8sRole}, gomock.AssignableToTypeOf(&rbacv1.RoleBinding{}), gomock.Any()). 720 Return(errors.NewNotFound(schema.GroupResource{}, "")) 721 722 // expect a call to create a rolebinding for the subjects to the specified k8s role 723 mockClient.EXPECT(). 724 Create(gomock.Any(), RoleBindingMatcher(k8sRole), gomock.Any()). 725 DoAndReturn(func(ctx context.Context, rb *rbacv1.RoleBinding, opts ...client.CreateOption) error { 726 assert.Equal(subjects, rb.Subjects) 727 return nil 728 }) 729 } 730 731 // mockUpdatedRoleBindingExpectations mocks the expectations for project rolebindings when the rolebindings already exist 732 func mockUpdatedRoleBindingExpectations(assert *asserts.Assertions, mockClient *mocks.MockClient, namespace, role, k8sRole string, subjects []rbacv1.Subject) { 733 // expect a call to fetch a rolebinding for the specified role and return an existing rolebinding 734 mockClient.EXPECT(). 735 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: role}, gomock.AssignableToTypeOf(&rbacv1.RoleBinding{}), gomock.Any()). 736 DoAndReturn(func(ctx context.Context, ns types.NamespacedName, roleBinding *rbacv1.RoleBinding, opts ...client.GetOption) error { 737 // simulate subjects and roleref being set our of band, we should reset them 738 roleBinding.RoleRef.Name = "changed" 739 roleBinding.Subjects = []rbacv1.Subject{ 740 {Kind: "Group", Name: "I-manually-set-this"}, 741 } 742 return nil 743 }) 744 745 // expect a call to update the rolebinding 746 mockClient.EXPECT(). 747 Update(gomock.Any(), RoleBindingMatcher(role), gomock.Any()). 748 DoAndReturn(func(ctx context.Context, rb *rbacv1.RoleBinding, opts ...client.UpdateOption) error { 749 assert.Equal(role, rb.RoleRef.Name) 750 assert.Equal(subjects, rb.Subjects) 751 return nil 752 }) 753 754 // expect a call to fetch a rolebinding for the specified k8s role and return an existing rolebinding 755 mockClient.EXPECT(). 756 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: k8sRole}, gomock.AssignableToTypeOf(&rbacv1.RoleBinding{}), gomock.Any()). 757 DoAndReturn(func(ctx context.Context, ns types.NamespacedName, roleBinding *rbacv1.RoleBinding, opts ...client.GetOption) error { 758 // simulate subjects and roleref being set our of band, we should reset them 759 roleBinding.RoleRef.Name = "changed" 760 roleBinding.Subjects = []rbacv1.Subject{ 761 {Kind: "Group", Name: "I-manually-set-this"}, 762 } 763 return nil 764 }) 765 766 // expect a call to update the rolebinding 767 mockClient.EXPECT(). 768 Update(gomock.Any(), RoleBindingMatcher(k8sRole), gomock.Any()). 769 DoAndReturn(func(ctx context.Context, rb *rbacv1.RoleBinding, opts ...client.UpdateOption) error { 770 assert.Equal(k8sRole, rb.RoleRef.Name) 771 assert.Equal(subjects, rb.Subjects) 772 return nil 773 }) 774 } 775 776 // doExpectStatusUpdateSucceeded expects a call to update status of 777 // VerrazzanoProject to success 778 func doExpectStatusUpdateSucceeded(cli *mocks.MockClient, mockStatusWriter *mocks.MockStatusWriter, assert *asserts.Assertions) { 779 // expect a call to fetch the MCRegistration secret to get the cluster name for status update 780 clusterstest.DoExpectGetMCRegistrationSecret(cli) 781 782 // expect a call to update the status of the VerrazzanoProject 783 cli.EXPECT().Status().Return(mockStatusWriter) 784 785 // the status update should be to success status/conditions on the VerrazzanoProject 786 mockStatusWriter.EXPECT(). 787 Update(gomock.Any(), gomock.AssignableToTypeOf(&clustersv1alpha1.VerrazzanoProject{}), gomock.Any()). 788 DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.UpdateOption) error { 789 clusterstest.AssertMultiClusterResourceStatus(assert, vp.Status, clustersv1alpha1.Succeeded, clustersv1alpha1.DeployComplete, corev1.ConditionTrue) 790 return nil 791 }) 792 } 793 794 // TestReconcileKubeSystem tests to make sure we do not reconcile 795 // Any resource that belong to the kube-system namespace 796 func TestReconcileKubeSystem(t *testing.T) { 797 assert := asserts.New(t) 798 799 var mocker = gomock.NewController(t) 800 var cli = mocks.NewMockClient(mocker) 801 802 // create a request and reconcile it 803 request := clusterstest.NewRequest(vzconst.KubeSystem, "unit-test-verrazzano-helidon-workload") 804 reconciler := newVerrazzanoProjectReconciler(cli) 805 result, err := reconciler.Reconcile(context.TODO(), request) 806 807 mocker.Finish() 808 assert.Nil(err) 809 assert.True(result.IsZero()) 810 }