github.com/verrazzano/verrazzano@v1.7.0/application-operator/mcagent/mcagent_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 mcagent
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
    13  	asserts "github.com/stretchr/testify/assert"
    14  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    15  	"github.com/verrazzano/verrazzano/application-operator/constants"
    16  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    17  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    18  	clustersapi "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    19  	vzconstants "github.com/verrazzano/verrazzano/pkg/constants"
    20  	"github.com/verrazzano/verrazzano/pkg/mcconstants"
    21  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    22  	"go.uber.org/zap"
    23  	corev1 "k8s.io/api/core/v1"
    24  	networkingv1 "k8s.io/api/networking/v1"
    25  	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	"k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/apimachinery/pkg/version"
    31  	"k8s.io/client-go/discovery"
    32  	fakediscovery "k8s.io/client-go/discovery/fake"
    33  	clientgotesting "k8s.io/client-go/testing"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    36  )
    37  
    38  var validSecret = corev1.Secret{
    39  	ObjectMeta: metav1.ObjectMeta{
    40  		Name:      constants.MCAgentSecret,
    41  		Namespace: constants.VerrazzanoSystemNamespace,
    42  	},
    43  	Data: map[string][]byte{constants.ClusterNameData: []byte("cluster1"), mcconstants.KubeconfigKey: []byte("kubeconfig")},
    44  }
    45  
    46  const testManagedPrometheusHost = "prometheus"
    47  const testManagedThanosQueryStoreAPIHost = "thanos-query-store.example.com"
    48  const testVZVersion = "dummy-verrazzano-version"
    49  
    50  var testK8sVersion = &version.Info{
    51  	GitVersion: "v1.26.3",
    52  }
    53  
    54  func fakeDiscoveryClientFunc() (discovery.DiscoveryInterface, error) {
    55  	discoveryClient := fakediscovery.FakeDiscovery{
    56  		Fake:               &clientgotesting.Fake{},
    57  		FakedServerVersion: testK8sVersion,
    58  	}
    59  	return &discoveryClient, nil
    60  }
    61  
    62  // TestReconcileAgentSecretDeleted tests agent thread when the registration secret is deleted
    63  // GIVEN a request to process the agent loop
    64  // WHEN the agent secret has been deleted
    65  // THEN ensure that there are no calls to get VerrazzanoProject resources
    66  func TestReconcileAgentSecretDeleted(t *testing.T) {
    67  	assert := asserts.New(t)
    68  	log := zap.S().With("test")
    69  
    70  	// Managed cluster mocks
    71  	mcMocker := gomock.NewController(t)
    72  	mcMock := mocks.NewMockClient(mcMocker)
    73  
    74  	// Admin cluster mocks
    75  	adminMocker := gomock.NewController(t)
    76  	adminMock := mocks.NewMockClient(adminMocker)
    77  
    78  	// Override createAdminClient to return the mock
    79  	originalAdminClientFunc := getAdminClientFunc
    80  	defer func() {
    81  		getAdminClientFunc = originalAdminClientFunc
    82  	}()
    83  	getAdminClientFunc = func(secret *corev1.Secret) (client.Client, error) {
    84  		return adminMock, nil
    85  	}
    86  
    87  	// Managed Cluster - expect call to get the agent secret.
    88  	expectAgentSecretNotFound(mcMock)
    89  
    90  	// Do not expect any further calls because the agent secret no longer exists
    91  
    92  	// Make the request
    93  	r := &Reconciler{
    94  		Client:       mcMock,
    95  		Log:          log,
    96  		Scheme:       newTestScheme(),
    97  		AgentChannel: nil,
    98  	}
    99  
   100  	_, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.MCAgentSecret}})
   101  
   102  	// Validate the results
   103  	// asserts.Equal(t, false, s.AgentSecretFound)
   104  	// asserts.Equal(t, false, s.AgentSecretValid)
   105  
   106  	adminMocker.Finish()
   107  	mcMocker.Finish()
   108  	assert.NoError(err)
   109  }
   110  
   111  func expectAgentSecretNotFound(mock *mocks.MockClient) {
   112  	mock.EXPECT().
   113  		Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.MCAgentSecret}, gomock.Not(gomock.Nil()), gomock.Any()).
   114  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   115  			return errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, name.Name)
   116  		})
   117  }
   118  
   119  // TestValidateSecret tests secret validation function
   120  func TestValidateSecret(t *testing.T) {
   121  	assert := asserts.New(t)
   122  
   123  	// Valid secret
   124  	err := validateAgentSecret(&validSecret)
   125  	assert.NoError(err)
   126  
   127  	// A secret without a cluster name
   128  	invalidSecret := validSecret
   129  	invalidSecret.Data = map[string][]byte{mcconstants.KubeconfigKey: []byte("kubeconfig")}
   130  	err = validateAgentSecret(&invalidSecret)
   131  	assert.Error(err)
   132  	assert.Contains(err.Error(), fmt.Sprintf("missing the required field %s", constants.ClusterNameData))
   133  
   134  	// A secret without a kubeconfig
   135  	invalidSecret.Data = map[string][]byte{constants.ClusterNameData: []byte("cluster1")}
   136  	err = validateAgentSecret(&invalidSecret)
   137  	assert.Error(err)
   138  	assert.Contains(err.Error(), fmt.Sprintf("missing the required field %s", mcconstants.KubeconfigKey))
   139  }
   140  
   141  // Test_getEnvValue tests getEnvValue
   142  // GIVEN a request for a specified ENV name
   143  // WHEN the env array contains such an env
   144  // THEN returns the env value, empty string if not found
   145  func Test_getEnvValue(t *testing.T) {
   146  	container := corev1.Container{}
   147  	container.Env = []corev1.EnvVar{}
   148  	asserts.Equal(t, "", getEnvValue(&[]corev1.Container{container}, registrationSecretVersion), "expected cluster name")
   149  	container.Env = []corev1.EnvVar{
   150  		{
   151  			Name:  registrationSecretVersion,
   152  			Value: "version1",
   153  		},
   154  	}
   155  	asserts.Equal(t, "version1", getEnvValue(&[]corev1.Container{container}, registrationSecretVersion), "expected cluster name")
   156  	container.Env = []corev1.EnvVar{
   157  		{
   158  			Name:  "env1",
   159  			Value: "value1",
   160  		},
   161  		{
   162  			Name:  registrationSecretVersion,
   163  			Value: "version1",
   164  		},
   165  	}
   166  	asserts.Equal(t, "version1", getEnvValue(&[]corev1.Container{container}, registrationSecretVersion), "expected cluster name")
   167  }
   168  
   169  // Test_getEnvValue tests updateEnvValue
   170  // GIVEN a request for a specified ENV name/value
   171  // WHEN the env array contains such an env
   172  // THEN updates its value, append the env name/value if not found
   173  func Test_updateEnvValue(t *testing.T) {
   174  	var testEnvs []corev1.EnvVar
   175  	newValue := "version2"
   176  	newEnvs := updateEnvValue(testEnvs, registrationSecretVersion, newValue)
   177  	asserts.Equal(t, registrationSecretVersion, newEnvs[0].Name, "expected env")
   178  	asserts.Equal(t, newValue, newEnvs[0].Value, "expected env value")
   179  	testEnvs = []corev1.EnvVar{
   180  		{
   181  			Name:  registrationSecretVersion,
   182  			Value: "version1",
   183  		},
   184  	}
   185  	newValue = "version2"
   186  	newEnvs = updateEnvValue(testEnvs, registrationSecretVersion, newValue)
   187  	asserts.Equal(t, registrationSecretVersion, newEnvs[0].Name, "expected env")
   188  	asserts.Equal(t, newValue, newEnvs[0].Value, "expected env value")
   189  	testEnvs = []corev1.EnvVar{
   190  		{
   191  			Name:  "env1",
   192  			Value: "value1",
   193  		},
   194  		{
   195  			Name:  registrationSecretVersion,
   196  			Value: "version1",
   197  		},
   198  	}
   199  	newEnvs = updateEnvValue(testEnvs, registrationSecretVersion, newValue)
   200  	asserts.Equal(t, registrationSecretVersion, newEnvs[1].Name, "expected env")
   201  	asserts.Equal(t, newValue, newEnvs[1].Value, "expected env value")
   202  }
   203  
   204  // TestReconcile_AgentSecretPresenceAndValidity
   205  // GIVEN a request to reconcile the agent
   206  // WHEN no new VerrazzanoProjects resources exists
   207  // THEN all the expected K8S calls are made and that there are no calls to sync any multi-cluster resources
   208  // for various cases of agent secret presence and validity, and agent state configmap presence
   209  func TestReconcile_AgentSecretPresenceAndValidity(t *testing.T) {
   210  	assert := asserts.New(t)
   211  	req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.MCAgentSecret}}
   212  	defer setDiscoveryClientFunc(getDiscoveryClientFunc)
   213  	setDiscoveryClientFunc(fakeDiscoveryClientFunc)
   214  	type fields struct {
   215  		AgentSecretFound         bool
   216  		AgentSecretValid         bool
   217  		AgentStateConfigMapFound bool
   218  	}
   219  	tests := []struct {
   220  		name    string
   221  		fields  fields
   222  		wantErr bool
   223  	}{
   224  		{"agent secret found not valid", fields{AgentSecretFound: true, AgentSecretValid: false, AgentStateConfigMapFound: false}, true},
   225  		{"agent secret not found - no op expected", fields{AgentSecretFound: false, AgentSecretValid: false, AgentStateConfigMapFound: false}, false},
   226  		{"agent secret found and valid but no configmap - should create configmap", fields{AgentSecretFound: true, AgentSecretValid: true, AgentStateConfigMapFound: false}, false},
   227  		{"agent secret found and valid and configmap exists", fields{AgentSecretFound: true, AgentSecretValid: true, AgentStateConfigMapFound: true}, false},
   228  	}
   229  	for _, tt := range tests {
   230  		t.Run(tt.name, func(t *testing.T) {
   231  
   232  			// Managed cluster mocks
   233  			mcMocker := gomock.NewController(t)
   234  			mcMock := mocks.NewMockClient(mcMocker)
   235  
   236  			// admin cluster mocks
   237  			adminMocker := gomock.NewController(t)
   238  			adminMock := mocks.NewMockClient(adminMocker)
   239  			adminStatusMock := mocks.NewMockStatusWriter(adminMocker)
   240  
   241  			// Override createAdminClient to return the mock
   242  			originalAdminClientFunc := getAdminClientFunc
   243  			defer func() {
   244  				getAdminClientFunc = originalAdminClientFunc
   245  			}()
   246  			getAdminClientFunc = func(secret *corev1.Secret) (client.Client, error) {
   247  				return adminMock, nil
   248  			}
   249  
   250  			secretToUse := validSecret
   251  			if !tt.fields.AgentSecretValid {
   252  				secretToUse.Data = map[string][]byte{}
   253  			}
   254  			if tt.fields.AgentSecretFound {
   255  				expectAgentSecretFound(mcMock, secretToUse)
   256  			} else {
   257  				expectAgentSecretNotFound(mcMock)
   258  			}
   259  			if tt.fields.AgentSecretFound && tt.fields.AgentSecretValid {
   260  				expectGetMCNamespace(mcMock)
   261  				clusterName := string(secretToUse.Data[constants.ClusterNameData])
   262  				if tt.fields.AgentStateConfigMapFound {
   263  					// Managed Cluster - expect call to get the agent state config map at the beginning of reconcile
   264  					expectAgentStateConfigMapFound(mcMock, clusterName)
   265  				} else {
   266  					expectAgentStateConfigMapNotFound(mcMock)
   267  					expectAgentStateConfigMapCreated(mcMock, clusterName)
   268  				}
   269  				expectAllCallsNoApps(adminMock, mcMock, adminStatusMock, clusterName, assert)
   270  			}
   271  			r := Reconciler{
   272  				Client: mcMock,
   273  				Scheme: newTestScheme(),
   274  				Log:    zap.S(),
   275  			}
   276  
   277  			_, err := r.Reconcile(context.TODO(), req)
   278  			if !tt.wantErr {
   279  				asserts.NoError(t, err)
   280  			} else {
   281  				asserts.NotNil(t, err)
   282  			}
   283  			adminMocker.Finish()
   284  			mcMocker.Finish()
   285  		})
   286  	}
   287  }
   288  
   289  // Test_discardStatusMessages tests the discardStatusMessages function
   290  func Test_discardStatusMessages(t *testing.T) {
   291  	statusUpdateChan := make(chan clusters.StatusUpdateMessage, 12)
   292  	for i := 0; i < 10; i++ {
   293  		statusUpdateChan <- clusters.StatusUpdateMessage{}
   294  	}
   295  	discardStatusMessages(statusUpdateChan)
   296  
   297  	asserts.Equal(t, 0, len(statusUpdateChan))
   298  }
   299  
   300  func expectAdminVMCStatusUpdateFailure(adminMock *mocks.MockClient, vmcName types.NamespacedName, adminStatusMock *mocks.MockStatusWriter, assert *asserts.Assertions) {
   301  	adminMock.EXPECT().
   302  		Get(gomock.Any(), vmcName, gomock.Not(gomock.Nil()), gomock.Any()).
   303  		Return(errors.NewNotFound(schema.GroupResource{Group: "clusters.verrazzano.io", Resource: "VerrazzanoManagedCluster"}, vmcName.Name))
   304  }
   305  
   306  func expectAdminVMCStatusUpdateSuccess(adminMock *mocks.MockClient, vmcName types.NamespacedName, adminStatusMock *mocks.MockStatusWriter, assert *asserts.Assertions) {
   307  	expectGetVMC(adminMock, vmcName, "")
   308  	adminMock.EXPECT().Status().Return(adminStatusMock)
   309  	adminStatusMock.EXPECT().
   310  		Update(gomock.Any(), gomock.AssignableToTypeOf(&clustersapi.VerrazzanoManagedCluster{}), gomock.Any()).
   311  		DoAndReturn(func(ctx context.Context, vmc *clustersapi.VerrazzanoManagedCluster, opts ...client.UpdateOption) error {
   312  			assert.Equal(vmcName.Namespace, vmc.Namespace)
   313  			assert.Equal(vmcName.Name, vmc.Name)
   314  			assert.NotNil(vmc.Status)
   315  			assert.NotNil(vmc.Status.LastAgentConnectTime)
   316  			assert.NotNil(vmc.Status.APIUrl)
   317  			assert.Equal(testManagedPrometheusHost, vmc.Status.PrometheusHost)
   318  			assert.Equal(testManagedThanosQueryStoreAPIHost, vmc.Status.ThanosQueryStore)
   319  			assert.Equal(testK8sVersion.String(), vmc.Status.Kubernetes.Version)
   320  			assert.Equal(testVZVersion, vmc.Status.Verrazzano.Version)
   321  			return nil
   322  		})
   323  }
   324  
   325  func expectCASyncSuccess(localMock, adminMock *mocks.MockClient, assert *asserts.Assertions, testClusterName string) {
   326  	localRegistrationSecret := types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.MCRegistrationSecret}
   327  	adminCASecret := types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: constants.VerrazzanoLocalCABundleSecret}
   328  	adminRegSecret := types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: getRegistrationSecretName(testClusterName)}
   329  	localIngressTLSSecret := types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: vzconstants.VerrazzanoIngressTLSSecret}
   330  
   331  	// Managed Cluster - expect call to get the verrazzano-tls-ca. Return not found since it
   332  	// is ok for it to be not present.
   333  	localMock.EXPECT().
   334  		Get(gomock.Any(), types.NamespacedName{Namespace: vzconstants.VerrazzanoSystemNamespace, Name: vzconstants.PrivateCABundle}, gomock.Not(gomock.Nil()), gomock.Any()).
   335  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   336  			return errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, name.Name)
   337  		})
   338  	adminMock.EXPECT().
   339  		Get(gomock.Any(), adminCASecret, gomock.Not(gomock.Nil()), gomock.Any()).
   340  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   341  			secret.Name = adminCASecret.Name
   342  			secret.Namespace = adminCASecret.Namespace
   343  			return nil
   344  		})
   345  	adminMock.EXPECT().
   346  		Get(gomock.Any(), adminRegSecret, gomock.Not(gomock.Nil()), gomock.Any()).
   347  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   348  			secret.Name = adminRegSecret.Name
   349  			secret.Namespace = adminRegSecret.Namespace
   350  			return nil
   351  		})
   352  	localMock.EXPECT().
   353  		Get(gomock.Any(), localRegistrationSecret, gomock.Not(gomock.Nil()), gomock.Any()).
   354  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   355  			secret.Name = localRegistrationSecret.Name
   356  			secret.Namespace = localRegistrationSecret.Namespace
   357  			return nil
   358  		})
   359  	localMock.EXPECT().
   360  		Get(gomock.Any(), localIngressTLSSecret, gomock.Not(gomock.Nil()), gomock.Any()).
   361  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   362  			secret.Name = localIngressTLSSecret.Name
   363  			secret.Namespace = localIngressTLSSecret.Namespace
   364  			secret.Data = map[string][]byte{mcconstants.CaCrtKey: []byte("somekey")}
   365  			return nil
   366  		})
   367  
   368  	vmcName := types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testClusterName}
   369  	clusterCASecret := "clusterCASecret"
   370  	expectGetVMC(adminMock, vmcName, clusterCASecret)
   371  	adminClusterCASecret := types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: clusterCASecret}
   372  	adminMock.EXPECT().
   373  		Get(gomock.Any(), adminClusterCASecret, gomock.Not(gomock.Nil()), gomock.Any()).
   374  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   375  			secret.Name = adminClusterCASecret.Name
   376  			secret.Namespace = adminClusterCASecret.Namespace
   377  			// make the value equal to the managed cluster CA - we are not looking for updates to this secret
   378  			secret.Data = map[string][]byte{keyCaCrtNoDot: []byte("somekey")}
   379  			return nil
   380  		})
   381  }
   382  
   383  // TestSyncer_updateVMCStatus tests updateVMCStatus method
   384  // GIVEN updateVMCStatus is called
   385  // WHEN the status update of VMC on admin cluster succeeds
   386  // THEN updateVMCStatus returns nil error
   387  // GIVEN updateVMCStatus is called
   388  // WHEN the status update of VMC on admin cluster fails
   389  // THEN updateVMCStatus returns a non-nil error
   390  func TestSyncer_updateVMCStatus(t *testing.T) {
   391  	assert := asserts.New(t)
   392  	log := zap.S().With("test")
   393  
   394  	// Admin cluster mocks
   395  	adminMocker := gomock.NewController(t)
   396  	adminMock := mocks.NewMockClient(adminMocker)
   397  	adminStatusMock := mocks.NewMockStatusWriter(adminMocker)
   398  	localClientMock := mocks.NewMockClient(adminMocker)
   399  
   400  	fakeDiscoveryClient, err := fakeDiscoveryClientFunc()
   401  	assert.Nil(err)
   402  
   403  	s := &Syncer{
   404  		AdminClient:          adminMock,
   405  		Log:                  log,
   406  		ManagedClusterName:   "my-test-cluster",
   407  		LocalClient:          localClientMock,
   408  		LocalDiscoveryClient: fakeDiscoveryClient,
   409  	}
   410  	vmcName := types.NamespacedName{Name: s.ManagedClusterName, Namespace: constants.VerrazzanoMultiClusterNamespace}
   411  
   412  	expectGetAPIServerURLCalled(localClientMock)
   413  	expectGetPrometheusHostCalled(localClientMock)
   414  	expectGetThanosQueryHostCalled(localClientMock)
   415  	expectGetWorkloadVZVersionCalled(localClientMock)
   416  	// Mock the success of status updates and assert that updateVMCStatus returns nil error
   417  	expectAdminVMCStatusUpdateSuccess(adminMock, vmcName, adminStatusMock, assert)
   418  	assert.Nil(s.updateVMCStatus())
   419  
   420  	// Mock the failure of status updates and assert that updateVMCStatus returns non-nil error
   421  	expectAdminVMCStatusUpdateFailure(adminMock, vmcName, adminStatusMock, assert)
   422  	assert.NotNil(s.updateVMCStatus())
   423  
   424  	adminMocker.Finish()
   425  }
   426  
   427  func expectGetWorkloadVZVersionCalled(mock *mocks.MockClient) {
   428  	// Expect a call to list the Verrazzanos.
   429  	mock.EXPECT().
   430  		List(gomock.Any(), &v1beta1.VerrazzanoList{}, gomock.Any()).
   431  		DoAndReturn(func(ctx context.Context, vzList *v1beta1.VerrazzanoList, opts ...client.ListOption) error {
   432  			vzList.Items = []v1beta1.Verrazzano{
   433  				{
   434  					Status: v1beta1.VerrazzanoStatus{
   435  						Version: testVZVersion,
   436  					},
   437  				},
   438  			}
   439  			return nil
   440  		})
   441  }
   442  
   443  func expectGetAPIServerURLCalled(mock *mocks.MockClient) {
   444  	// Expect a call to get the console ingress and return the ingress.
   445  	mock.EXPECT().
   446  		Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()).
   447  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error {
   448  			ingress.TypeMeta = metav1.TypeMeta{
   449  				APIVersion: "networking.k8s.io/v1",
   450  				Kind:       "ingress"}
   451  			ingress.ObjectMeta = metav1.ObjectMeta{
   452  				Namespace: name.Namespace,
   453  				Name:      name.Name}
   454  			ingress.Spec.Rules = []networkingv1.IngressRule{{
   455  				Host: "console",
   456  			}}
   457  			return nil
   458  		})
   459  }
   460  
   461  func expectGetPrometheusHostCalled(mock *mocks.MockClient) {
   462  	// Expect a call to get the prometheus ingress and return the host.
   463  	expectGetIngress(mock, constants.VerrazzanoSystemNamespace, constants.VzPrometheusIngress, testManagedPrometheusHost)
   464  }
   465  
   466  func expectGetThanosQueryHostCalled(mock *mocks.MockClient) {
   467  	// Expect a call to get the Thanos query ingress and return the host.
   468  	expectGetIngress(mock, constants.VerrazzanoSystemNamespace, vzconstants.ThanosQueryStoreIngress, testManagedThanosQueryStoreAPIHost)
   469  }
   470  
   471  // Expects a call to get an ingress with the given name and namespace, and returns an ingress with the specified
   472  // ingressHost
   473  func expectGetIngress(mock *mocks.MockClient, ingressNamespace string, ingressName string, ingressHost string) {
   474  	mock.EXPECT().
   475  		Get(gomock.Any(), types.NamespacedName{Namespace: ingressNamespace, Name: ingressName}, gomock.Not(gomock.Nil()), gomock.Any()).
   476  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *networkingv1.Ingress, opts ...client.GetOption) error {
   477  			ingress.TypeMeta = metav1.TypeMeta{
   478  				APIVersion: "networking.k8s.io/v1",
   479  				Kind:       "ingress"}
   480  			ingress.ObjectMeta = metav1.ObjectMeta{
   481  				Namespace: name.Namespace,
   482  				Name:      name.Name}
   483  			ingress.Spec.Rules = []networkingv1.IngressRule{{
   484  				Host: ingressHost,
   485  			}}
   486  			return nil
   487  		})
   488  }
   489  
   490  func expectGetManifestSecretNotFound(mock *mocks.MockClient, clusterName string) {
   491  	mock.EXPECT().
   492  		Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: getManifestSecretName(clusterName)}, gomock.Not(gomock.Nil()), gomock.Any()).
   493  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   494  			return errors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, name.Name)
   495  		})
   496  }
   497  
   498  func expectListNamespacesReturnNone(mock *mocks.MockClient) {
   499  	mock.EXPECT().
   500  		List(gomock.Any(), &corev1.NamespaceList{}, gomock.Any()).
   501  		DoAndReturn(func(ctx context.Context, list *corev1.NamespaceList, opts ...client.ListOption) error {
   502  			return nil
   503  		})
   504  
   505  }
   506  
   507  func expectListProjectsReturnNone(mock *mocks.MockClient) {
   508  	mock.EXPECT().
   509  		List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.Any()).
   510  		DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...client.ListOption) error {
   511  			return nil
   512  		})
   513  }
   514  
   515  func expectAgentStateConfigMapNotFound(mock *mocks.MockClient) {
   516  	// called once for reading the existing data, and another time during update
   517  	mock.EXPECT().
   518  		Get(gomock.Any(), mcAgentStateConfigMapName, gomock.Not(gomock.Nil()), gomock.Any()).
   519  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, cm *corev1.ConfigMap, opts ...client.GetOption) error {
   520  			return errors.NewNotFound(schema.GroupResource{Group: "", Resource: "ConfigMap"}, name.Name)
   521  		})
   522  }
   523  
   524  func expectAgentStateConfigMapFound(mock *mocks.MockClient, clusterName string) {
   525  	// called at the beginning for reading the existing data, and another get to determine if update is needed
   526  	mock.EXPECT().
   527  		Get(gomock.Any(), mcAgentStateConfigMapName, gomock.Not(gomock.Nil()), gomock.Any()).
   528  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, cm *corev1.ConfigMap, opts ...client.GetOption) error {
   529  			cm.Name = mcAgentStateConfigMapName.Name
   530  			cm.Namespace = mcAgentStateConfigMapName.Namespace
   531  			cm.Data = map[string]string{}
   532  			if clusterName != "" {
   533  				cm.Data[constants.ClusterNameData] = clusterName
   534  			}
   535  			return nil
   536  		}).Times(2)
   537  }
   538  
   539  func expectGetMCNamespace(mock *mocks.MockClient) {
   540  	// expect a get for the verrazzano-mc namespace
   541  	mock.EXPECT().
   542  		Get(gomock.Any(), types.NamespacedName{Name: mcAgentStateConfigMapName.Namespace}, gomock.Not(gomock.Nil()), gomock.Any()).
   543  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
   544  			ns.Name = name.Name
   545  			return nil
   546  		})
   547  }
   548  
   549  func expectAgentStateConfigMapCreated(mock *mocks.MockClient, clusterName string) {
   550  	// expect a get where the configmap is not found
   551  	expectAgentStateConfigMapNotFound(mock)
   552  
   553  	// expect a create of the config map
   554  	mock.EXPECT().
   555  		Create(gomock.Any(), gomock.AssignableToTypeOf(&corev1.ConfigMap{})).
   556  		DoAndReturn(func(ctx context.Context, cm *corev1.ConfigMap, opts ...client.GetOption) error {
   557  			cm.Name = mcAgentStateConfigMapName.Name
   558  			cm.Namespace = mcAgentStateConfigMapName.Namespace
   559  			if clusterName != "" {
   560  				cm.Data = map[string]string{constants.ClusterNameData: clusterName}
   561  			}
   562  			return nil
   563  		})
   564  }
   565  
   566  func expectGetVMC(mock *mocks.MockClient, vmcName types.NamespacedName, caSecretName string) {
   567  	mock.EXPECT().
   568  		Get(gomock.Any(), vmcName, gomock.Not(gomock.Nil()), gomock.Any()).
   569  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, vmc *clustersapi.VerrazzanoManagedCluster, opts ...client.GetOption) error {
   570  			vmc.DeletionTimestamp = nil
   571  			vmc.Name = vmcName.Name
   572  			vmc.Namespace = vmcName.Namespace
   573  			if caSecretName != "" {
   574  				vmc.Spec.CASecret = caSecretName
   575  			}
   576  			return nil
   577  		})
   578  }
   579  
   580  func expectAgentSecretFound(mock *mocks.MockClient, secretToUse corev1.Secret) {
   581  	mock.EXPECT().
   582  		Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.MCAgentSecret}, gomock.Not(gomock.Nil()), gomock.Any()).
   583  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *corev1.Secret, opts ...client.GetOption) error {
   584  			secret.ObjectMeta = secretToUse.ObjectMeta
   585  			secret.Data = secretToUse.Data
   586  			return nil
   587  		})
   588  }
   589  
   590  func expectServiceAndPodMonitorsList(mock *mocks.MockClient, assert *asserts.Assertions) {
   591  	// Managed Cluster, expect call to list service monitors, return an empty list
   592  	mock.EXPECT().
   593  		List(gomock.Any(), &v1.ServiceMonitorList{}, gomock.Any()).
   594  		DoAndReturn(func(ctx context.Context, list *v1.ServiceMonitorList, opts ...client.ListOption) error {
   595  			return nil
   596  		})
   597  	// Managed Cluster, expect call to list pod monitors, return an empty list
   598  	mock.EXPECT().
   599  		List(gomock.Any(), &v1.PodMonitorList{}, gomock.Any()).
   600  		DoAndReturn(func(ctx context.Context, list *v1.PodMonitorList, opts ...client.ListOption) error {
   601  			return nil
   602  		})
   603  
   604  }
   605  
   606  // expectAllCallsNoApps expects all the calls that a reconcile of the MC agent would result in, if
   607  // there are no applications or VerrazzanoProjects. It does not include the initial call to get the agent secret.
   608  func expectAllCallsNoApps(adminMock *mocks.MockClient, mcMock *mocks.MockClient, adminStatusMock *mocks.MockStatusWriter, clusterName string, assert *asserts.Assertions) {
   609  	// Admin Cluster - expect a get followed by status update on VMC to record last agent connect time
   610  	vmcName := types.NamespacedName{Name: clusterName, Namespace: constants.VerrazzanoMultiClusterNamespace}
   611  	expectGetAPIServerURLCalled(mcMock)
   612  	expectGetPrometheusHostCalled(mcMock)
   613  	expectGetThanosQueryHostCalled(mcMock)
   614  	expectGetWorkloadVZVersionCalled(mcMock)
   615  	expectAdminVMCStatusUpdateSuccess(adminMock, vmcName, adminStatusMock, assert)
   616  
   617  	// Managed Cluster - expect call to get MC app config CRD - return exists
   618  	expectGetMCAppConfigCRD(mcMock)
   619  
   620  	// Admin Cluster - expect call to list VerrazzanoProject objects - return an empty list
   621  	expectListProjectsReturnNone(adminMock)
   622  	// Managed Cluster - expect call to list VerrazzanoProject objects - return an empty list
   623  	expectListProjectsReturnNone(mcMock)
   624  
   625  	expectServiceAndPodMonitorsList(mcMock, assert)
   626  
   627  	// Managed Cluster - expect call to list Namespace objects - return an empty list
   628  	expectListNamespacesReturnNone(mcMock)
   629  
   630  	expectCASyncSuccess(mcMock, adminMock, assert, "cluster1")
   631  
   632  	// expect the VMC to be retrieved to check for deletion
   633  	clusterCASecret := "clusterCASecret"
   634  	expectGetVMC(adminMock, vmcName, clusterCASecret)
   635  	expectGetManifestSecretNotFound(adminMock, clusterName)
   636  }
   637  
   638  func expectGetMCAppConfigCRD(mock *mocks.MockClient) {
   639  	mock.EXPECT().
   640  		Get(gomock.Any(), types.NamespacedName{Name: mcAppConfCRDName}, gomock.Not(gomock.Nil()), gomock.Any()).
   641  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, crd *apiextv1.CustomResourceDefinition, opts ...client.GetOption) error {
   642  			crd.Name = mcAppConfCRDName
   643  			return nil
   644  		})
   645  }