github.com/verrazzano/verrazzano@v1.7.0/application-operator/mcagent/mcagent_component_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  	"encoding/json"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    13  	"github.com/golang/mock/gomock"
    14  	asserts "github.com/stretchr/testify/assert"
    15  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    16  	"github.com/verrazzano/verrazzano/application-operator/constants"
    17  	clusterstest "github.com/verrazzano/verrazzano/application-operator/controllers/clusters/test"
    18  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    19  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    20  	"go.uber.org/zap"
    21  	"k8s.io/apimachinery/pkg/api/errors"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	"sigs.k8s.io/controller-runtime/pkg/client"
    25  )
    26  
    27  const testMCComponentName = "unit-mccomp"
    28  const testMCComponentNamespace = "unit-mccomp-namespace"
    29  
    30  var mcComponentTestExpectedLabels = map[string]string{"label1": "test1",
    31  	vzconst.VerrazzanoManagedLabelKey: constants.LabelVerrazzanoManagedDefault}
    32  var mcComponentTestExpectedLabelsOnUpdate = map[string]string{"label1": "test1updated",
    33  	vzconst.VerrazzanoManagedLabelKey: constants.LabelVerrazzanoManagedDefault}
    34  
    35  // TestCreateMCComponent tests the synchronization method for the following use case.
    36  // GIVEN a request to sync MultiClusterComponent objects
    37  // WHEN the a new object exists
    38  // THEN ensure that the MultiClusterComponent is created.
    39  func TestCreateMCComponent(t *testing.T) {
    40  	assert := asserts.New(t)
    41  	log := zap.S().With("test")
    42  
    43  	// Managed cluster mocks
    44  	mcMocker := gomock.NewController(t)
    45  	mcMock := mocks.NewMockClient(mcMocker)
    46  
    47  	// Admin cluster mocks
    48  	adminMocker := gomock.NewController(t)
    49  	adminMock := mocks.NewMockClient(adminMocker)
    50  
    51  	// Test data
    52  	testMCComponent, err := getSampleMCComponent("testdata/multicluster-component.yaml")
    53  	if err != nil {
    54  		assert.NoError(err, "failed to read sample data for MultiClusterComponent")
    55  	}
    56  
    57  	// Admin Cluster - expect call to list MultiClusterComponent objects - return list with one object
    58  	adminMock.EXPECT().
    59  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
    60  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
    61  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
    62  			mcComponentList.Items = append(mcComponentList.Items, testMCComponent)
    63  			return nil
    64  		})
    65  
    66  	// Managed Cluster - expect call to get a MultiClusterComponent from the list returned by the admin cluster
    67  	//                   Return the resource does not exist
    68  	mcMock.EXPECT().
    69  		Get(gomock.Any(), types.NamespacedName{Namespace: testMCComponentNamespace, Name: testMCComponentName}, gomock.Not(gomock.Nil()), gomock.Any()).
    70  		Return(errors.NewNotFound(schema.GroupResource{Group: "clusters.verrazzano.io", Resource: "MultiClusterComponent"}, testMCComponentName))
    71  
    72  	// Managed Cluster - expect call to create a MultiClusterComponent
    73  	mcMock.EXPECT().
    74  		Create(gomock.Any(), gomock.Any()).
    75  		DoAndReturn(func(ctx context.Context, mcComponent *clustersv1alpha1.MultiClusterComponent, opts ...client.CreateOption) error {
    76  			assert.Equal(testMCComponentNamespace, mcComponent.Namespace, "mccomponent namespace did not match")
    77  			assert.Equal(testMCComponentName, mcComponent.Name, "mccomponent name did not match")
    78  			assert.Equal(mcComponentTestExpectedLabels, mcComponent.Labels, "mccomponent labels did not match")
    79  			assert.Equal(testClusterName, mcComponent.Spec.Placement.Clusters[0].Name, "mccomponent does not contain expected placement")
    80  			return nil
    81  		})
    82  
    83  	// Managed Cluster - expect call to list MultiClusterComponent objects - return same list as admin
    84  	mcMock.EXPECT().
    85  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
    86  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
    87  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
    88  			mcComponentList.Items = append(mcComponentList.Items, testMCComponent)
    89  			return nil
    90  		})
    91  
    92  	// Make the request
    93  	s := &Syncer{
    94  		AdminClient:        adminMock,
    95  		LocalClient:        mcMock,
    96  		Log:                log,
    97  		ManagedClusterName: testClusterName,
    98  		Context:            context.TODO(),
    99  	}
   100  	err = s.syncMCComponentObjects(testMCComponentNamespace)
   101  
   102  	// Validate the results
   103  	adminMocker.Finish()
   104  	mcMocker.Finish()
   105  	assert.NoError(err)
   106  }
   107  
   108  // TestUpdateMCComponent tests the synchronization method for the following use case.
   109  // GIVEN a request to sync MultiClusterComponent objects
   110  // WHEN the a object exists
   111  // THEN ensure that the MultiClusterComponent is updated.
   112  func TestUpdateMCComponent(t *testing.T) {
   113  	assert := asserts.New(t)
   114  	log := zap.S().With("test")
   115  
   116  	// Managed cluster mocks
   117  	mcMocker := gomock.NewController(t)
   118  	mcMock := mocks.NewMockClient(mcMocker)
   119  
   120  	// Admin cluster mocks
   121  	adminMocker := gomock.NewController(t)
   122  	adminMock := mocks.NewMockClient(adminMocker)
   123  
   124  	// Test data
   125  	testMCComponent, err := getSampleMCComponent("testdata/multicluster-component.yaml")
   126  	assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   127  
   128  	testMCComponentUpdate, err := getSampleMCComponent("testdata/multicluster-component-update.yaml")
   129  	assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   130  
   131  	// Admin Cluster - expect call to list MultiClusterComponent objects - return list with one object
   132  	adminMock.EXPECT().
   133  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   134  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   135  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   136  			mcComponentList.Items = append(mcComponentList.Items, testMCComponentUpdate)
   137  			return nil
   138  		})
   139  
   140  	// Managed Cluster - expect call to get a MultiClusterComponent from the list returned by the admin cluster
   141  	//                   Return the resource with some values different than what the admin cluster returned
   142  	mcMock.EXPECT().
   143  		Get(gomock.Any(), types.NamespacedName{Namespace: testMCComponentNamespace, Name: testMCComponentName}, gomock.Not(gomock.Nil()), gomock.Any()).
   144  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, mcComponent *clustersv1alpha1.MultiClusterComponent, opts ...client.GetOption) error {
   145  			testMCComponent.DeepCopyInto(mcComponent)
   146  			return nil
   147  		})
   148  
   149  	// Managed Cluster - expect call to update a MultiClusterComponent
   150  	//                   Verify request had the updated values
   151  	mcMock.EXPECT().
   152  		Update(gomock.Any(), gomock.Any()).
   153  		DoAndReturn(func(ctx context.Context, mcComponent *clustersv1alpha1.MultiClusterComponent, opts ...client.UpdateOption) error {
   154  			assert.Equal(testMCComponentNamespace, mcComponent.Namespace, "mccomponent namespace did not match")
   155  			assert.Equal(testMCComponentName, mcComponent.Name, "mccomponent name did not match")
   156  			assert.Equal(mcComponentTestExpectedLabelsOnUpdate, mcComponent.Labels, "mccomponent labels did not match")
   157  			workload := v1alpha2.ContainerizedWorkload{}
   158  			err := json.Unmarshal(mcComponent.Spec.Template.Spec.Workload.Raw, &workload)
   159  			assert.NoError(err, "failed to unmarshal the containerized workload")
   160  			assert.Equal("hello2", workload.Spec.Containers[0].Name)
   161  			assert.Equal("ghcr.io/oracle/oraclelinux:7-slim2", workload.Spec.Containers[0].Image)
   162  			return nil
   163  		})
   164  
   165  	// Managed Cluster - expect call to list MultiClusterComponent objects - return same list as admin
   166  	mcMock.EXPECT().
   167  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   168  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   169  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   170  			mcComponentList.Items = append(mcComponentList.Items, testMCComponent)
   171  			return nil
   172  		})
   173  
   174  	// Make the request
   175  	s := &Syncer{
   176  		AdminClient:        adminMock,
   177  		LocalClient:        mcMock,
   178  		Log:                log,
   179  		ManagedClusterName: testClusterName,
   180  		Context:            context.TODO(),
   181  	}
   182  	err = s.syncMCComponentObjects(testMCComponentNamespace)
   183  
   184  	// Validate the results
   185  	adminMocker.Finish()
   186  	mcMocker.Finish()
   187  	assert.NoError(err)
   188  }
   189  
   190  // TestDeleteMCComponent tests the synchronization method for the following use case.
   191  // GIVEN a request to sync MultiClusterComponent objects
   192  // WHEN the object exists on the local cluster but not on the admin cluster
   193  // THEN ensure that the MultiClusterComponent is deleted.
   194  func TestDeleteMCComponent(t *testing.T) {
   195  	assert := asserts.New(t)
   196  	log := zap.S().With("test")
   197  
   198  	// Managed cluster mocks
   199  	mcMocker := gomock.NewController(t)
   200  	mcMock := mocks.NewMockClient(mcMocker)
   201  
   202  	// Admin cluster mocks
   203  	adminMocker := gomock.NewController(t)
   204  	adminMock := mocks.NewMockClient(adminMocker)
   205  
   206  	// Test data
   207  	testMCComponent, err := getSampleMCComponent("testdata/multicluster-component.yaml")
   208  	if err != nil {
   209  		assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   210  	}
   211  	testMCComponentOrphan, err := getSampleMCComponent("testdata/multicluster-component.yaml")
   212  	if err != nil {
   213  		assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   214  	}
   215  	testMCComponentOrphan.Name = "orphaned-resource"
   216  
   217  	// Admin Cluster - expect call to list MultiClusterComponent objects - return list with one object
   218  	adminMock.EXPECT().
   219  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   220  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   221  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   222  			mcComponentList.Items = append(mcComponentList.Items, testMCComponent)
   223  			return nil
   224  		})
   225  
   226  	// Managed Cluster - expect call to get a MultiClusterComponent from the list returned by the admin cluster
   227  	//                   Return the resource
   228  	mcMock.EXPECT().
   229  		Get(gomock.Any(), types.NamespacedName{Namespace: testMCComponentNamespace, Name: testMCComponentName}, gomock.Not(gomock.Nil()), gomock.Any()).
   230  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, mcComponent *clustersv1alpha1.MultiClusterComponent, opts ...client.GetOption) error {
   231  			testMCComponent.DeepCopyInto(mcComponent)
   232  			return nil
   233  		})
   234  
   235  	// Managed Cluster - expect call to list MultiClusterComponent objects - return list including an orphaned object
   236  	mcMock.EXPECT().
   237  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   238  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   239  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   240  			mcComponentList.Items = append(mcComponentList.Items, testMCComponent)
   241  			mcComponentList.Items = append(mcComponentList.Items, testMCComponentOrphan)
   242  			return nil
   243  		})
   244  
   245  	// Managed Cluster - expect a call to delete a MultiClusterComponent object
   246  	mcMock.EXPECT().
   247  		Delete(gomock.Any(), gomock.Eq(&testMCComponentOrphan), gomock.Any()).
   248  		Return(nil)
   249  
   250  	// Make the request
   251  	s := &Syncer{
   252  		AdminClient:        adminMock,
   253  		LocalClient:        mcMock,
   254  		Log:                log,
   255  		ManagedClusterName: testClusterName,
   256  		Context:            context.TODO(),
   257  	}
   258  	err = s.syncMCComponentObjects(testMCComponentNamespace)
   259  
   260  	// Validate the results
   261  	adminMocker.Finish()
   262  	mcMocker.Finish()
   263  	assert.NoError(err)
   264  }
   265  
   266  // TestMCComponentPlacement tests the synchronization method for the following use case.
   267  // GIVEN a request to sync MultiClusterComponent objects
   268  // WHEN the a object exists that is not targeted for the cluster
   269  // THEN ensure that the MultiClusterComponent is not created or updated
   270  func TestMCComponentPlacement(t *testing.T) {
   271  	assert := asserts.New(t)
   272  	log := zap.S().With("test")
   273  
   274  	// Managed cluster mocks
   275  	mcMocker := gomock.NewController(t)
   276  	mcMock := mocks.NewMockClient(mcMocker)
   277  
   278  	// Admin cluster mocks
   279  	adminMocker := gomock.NewController(t)
   280  	adminMock := mocks.NewMockClient(adminMocker)
   281  
   282  	// Test data
   283  	adminMCComponent, err := getSampleMCComponent("testdata/multicluster-component.yaml")
   284  	assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   285  	adminMCComponent.Spec.Placement.Clusters[0].Name = "not-my-cluster"
   286  
   287  	localMCComponent, err := getSampleMCComponent("testdata/multicluster-component.yaml")
   288  	assert.NoError(err, "failed to read sample data for MultiClusterComponent")
   289  
   290  	// Admin Cluster - expect call to list MultiClusterComponent objects - return list with one object
   291  	adminMock.EXPECT().
   292  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   293  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   294  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   295  			mcComponentList.Items = append(mcComponentList.Items, adminMCComponent)
   296  			return nil
   297  		})
   298  
   299  	// Managed Cluster - expect call to list MultiClusterComponent objects - return same list as admin
   300  	mcMock.EXPECT().
   301  		List(gomock.Any(), &clustersv1alpha1.MultiClusterComponentList{}, gomock.Not(gomock.Nil())).
   302  		DoAndReturn(func(ctx context.Context, mcComponentList *clustersv1alpha1.MultiClusterComponentList, listOptions *client.ListOptions) error {
   303  			assert.Equal(testMCComponentNamespace, listOptions.Namespace, "list request did not have expected namespace")
   304  			mcComponentList.Items = append(mcComponentList.Items, localMCComponent)
   305  			return nil
   306  		})
   307  
   308  	// Managed Cluster - expect a call to delete a MultiClusterComponent object
   309  	mcMock.EXPECT().
   310  		Delete(gomock.Any(), gomock.Eq(&localMCComponent), gomock.Any()).
   311  		Return(nil)
   312  
   313  	// Make the request
   314  	s := &Syncer{
   315  		AdminClient:        adminMock,
   316  		LocalClient:        mcMock,
   317  		Log:                log,
   318  		ManagedClusterName: testClusterName,
   319  		Context:            context.TODO(),
   320  	}
   321  	err = s.syncMCComponentObjects(testMCComponentNamespace)
   322  
   323  	// Validate the results
   324  	adminMocker.Finish()
   325  	mcMocker.Finish()
   326  	assert.NoError(err)
   327  }
   328  
   329  // getSampleMCComponent creates and returns a sample MultiClusterComponent used in tests
   330  func getSampleMCComponent(filePath string) (clustersv1alpha1.MultiClusterComponent, error) {
   331  	mcComp := clustersv1alpha1.MultiClusterComponent{}
   332  	sampleComponentFile, err := filepath.Abs(filePath)
   333  	if err != nil {
   334  		return mcComp, err
   335  	}
   336  
   337  	rawMcComp, err := clusterstest.ReadYaml2Json(sampleComponentFile)
   338  	if err != nil {
   339  		return mcComp, err
   340  	}
   341  
   342  	err = json.Unmarshal(rawMcComp, &mcComp)
   343  	return mcComp, err
   344  }