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  }