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 }