github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/ingresstrait/ingresstrait_controller_test.go (about) 1 // Copyright (c) 2020, 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 ingresstrait 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "github.com/verrazzano/verrazzano/application-operator/controllers/reconcileresults" 12 vzstring "github.com/verrazzano/verrazzano/pkg/string" 13 "os" 14 "strings" 15 "testing" 16 "text/template" 17 "time" 18 19 "istio.io/api/meta/v1alpha1" 20 "istio.io/client-go/pkg/apis/security/v1beta1" 21 22 "github.com/prometheus/client_golang/prometheus/testutil" 23 "github.com/verrazzano/verrazzano/pkg/test/ip" 24 25 "github.com/go-logr/logr" 26 27 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 28 29 certapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" 30 oamrt "github.com/crossplane/crossplane-runtime/apis/common/v1" 31 "github.com/crossplane/oam-kubernetes-runtime/apis/core" 32 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 33 "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 34 "github.com/golang/mock/gomock" 35 asserts "github.com/stretchr/testify/assert" 36 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 37 "github.com/verrazzano/verrazzano/application-operator/constants" 38 "github.com/verrazzano/verrazzano/application-operator/metricsexporter" 39 "github.com/verrazzano/verrazzano/application-operator/mocks" 40 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 41 "go.uber.org/zap" 42 istionet "istio.io/api/networking/v1alpha3" 43 istioclient "istio.io/client-go/pkg/apis/networking/v1alpha3" 44 k8sapps "k8s.io/api/apps/v1" 45 k8score "k8s.io/api/core/v1" 46 k8net "k8s.io/api/networking/v1" 47 k8serrors "k8s.io/apimachinery/pkg/api/errors" 48 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 49 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 50 "k8s.io/apimachinery/pkg/runtime" 51 "k8s.io/apimachinery/pkg/runtime/schema" 52 "k8s.io/apimachinery/pkg/types" 53 "k8s.io/apimachinery/pkg/util/intstr" 54 k8scheme "k8s.io/client-go/kubernetes/scheme" 55 ctrl "sigs.k8s.io/controller-runtime" 56 "sigs.k8s.io/controller-runtime/pkg/client" 57 "sigs.k8s.io/controller-runtime/pkg/client/fake" 58 "sigs.k8s.io/yaml" 59 ) 60 61 type appFakeResources struct { 62 appConfig *v1alpha2.ApplicationConfiguration 63 workload *v1alpha2.ContainerizedWorkload 64 workloadDef *v1alpha2.WorkloadDefinition 65 workloadService *k8score.Service 66 } 67 68 const ( 69 testTraitName = "test-trait" 70 testTraitPortName = "https-test-trait" 71 apiVersion = "oam.verrazzano.io/v1alpha1" 72 traitKind = "IngressTrait" 73 testNamespace = "test-space" 74 expectedTraitVSName = "test-trait-rule-0-vs" 75 expectedAuthzPolicyName = "test-trait-rule-0-authz-test-path" 76 expectedAuthzPolicyNameRootPath = "test-trait-rule-0-authz" 77 expectedAppGWName = "test-space-myapp-gw" 78 testWorkloadName = "test-workload-name" 79 testWorkloadID = "test-workload-uid" 80 istioIngressGatewayName = "istio-ingressgateway" 81 istioSystemNamespace = "istio-system" 82 testName = "test-name" 83 testwWorkloadName = "test-contained-workload-name" 84 testRule = "%s-rule-0-dr" 85 ) 86 87 var ( 88 httpsLower = strings.ToLower(httpsProtocol) 89 testClusterIP = ip.RandomIP() 90 testLoadBalancerIP = ip.RandomIP() 91 testLoadBalancerDomainName = "myapp.myns." + testLoadBalancerIP + ".nip.io" 92 testLoadBalancerExternalDNSTarget = "verrazzano-ingress." + testLoadBalancerIP + ".nip.io" 93 testLoadBalancerAppGatewayServerHost = "test-appconf.test-namespace." + testLoadBalancerIP + ".nip.io" 94 testExternalIP = ip.RandomIP() 95 testExternalDomainName = "myapp.myns." + testExternalIP + ".nip.io" 96 namespace = k8score.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}} 97 ) 98 99 // GIVEN a controller implementation 100 // WHEN the controller is created 101 // THEN verify no error is returned 102 func TestReconcilerSetupWithManager(t *testing.T) { 103 assert := asserts.New(t) 104 105 var mocker *gomock.Controller 106 var mgr *mocks.MockManager 107 var cli *mocks.MockClient 108 var scheme *runtime.Scheme 109 var reconciler Reconciler 110 var err error 111 112 mocker = gomock.NewController(t) 113 mgr = mocks.NewMockManager(mocker) 114 cli = mocks.NewMockClient(mocker) 115 scheme = runtime.NewScheme() 116 _ = vzapi.AddToScheme(scheme) 117 reconciler = Reconciler{Client: cli, Scheme: scheme} 118 mgr.EXPECT().GetControllerOptions().AnyTimes() 119 mgr.EXPECT().GetScheme().Return(scheme) 120 mgr.EXPECT().GetLogger().Return(logr.Discard()) 121 mgr.EXPECT().SetFields(gomock.Any()).Return(nil).AnyTimes() 122 mgr.EXPECT().Add(gomock.Any()).Return(nil).AnyTimes() 123 err = reconciler.SetupWithManager(mgr) 124 mocker.Finish() 125 assert.NoError(err) 126 } 127 128 // TestSuccessfullyCreateNewIngress tests the Reconcile method for the following use case. 129 // GIVEN a request to reconcile an ingress trait resource 130 // WHEN the trait exists but the ingress does not 131 // THEN ensure that the trait is created. 132 func TestSuccessfullyCreateNewIngress(t *testing.T) { 133 assert := asserts.New(t) 134 mocker := gomock.NewController(t) 135 mock := mocks.NewMockClient(mocker) 136 mockStatus := mocks.NewMockStatusWriter(mocker) 137 getIngressTraitResourceExpectations(mock, assert) 138 139 workLoadResourceExpectations(mock) 140 workloadResourceDefinitionExpectations(mock) 141 listChildDeploymentExpectations(mock, assert) 142 deleteCertExpectations(mock, "test-space-myapp-cert") 143 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 144 createCertSuccessExpectations(mock) 145 appCertificateExpectations(mock) 146 getGatewayForTraitNotFoundExpectations(mock) 147 traitVSNotFoundExpectation(mock) 148 getMockStatusWriterExpectations(mock, mockStatus) 149 150 // Expect a call to list the child Service resources of the containerized workload definition 151 mock.EXPECT(). 152 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 153 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 154 assert.Equal("ServiceList", list.GetKind()) 155 return appendAsUnstructured(list, k8score.Service{ 156 TypeMeta: metav1.TypeMeta{ 157 APIVersion: "v1", 158 Kind: "Service", 159 }, 160 ObjectMeta: metav1.ObjectMeta{ 161 OwnerReferences: []metav1.OwnerReference{{ 162 APIVersion: "core.oam.dev/v1alpha2", 163 Kind: "ContainerizedWorkload", 164 Name: testWorkloadName, 165 UID: testWorkloadID, 166 }}}, 167 Spec: k8score.ServiceSpec{ 168 ClusterIP: testClusterIP, 169 Ports: []k8score.ServicePort{{Port: 42}}}}) 170 }) 171 // Expect a call to get the app config and return that it is not found. 172 mock.EXPECT(). 173 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 174 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 175 app.TypeMeta = metav1.TypeMeta{ 176 APIVersion: "core.oam.dev/v1alpha2", 177 Kind: "ApplicationConfiguration", 178 } 179 return nil 180 }) 181 // Expect a call to create the Gateway resource and return success 182 mock.EXPECT(). 183 Create(gomock.Any(), gomock.Any(), gomock.Any()). 184 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 185 return nil 186 }) 187 // Expect a call to create the VirtualService resource and return success 188 mock.EXPECT(). 189 Create(gomock.Any(), gomock.Any(), gomock.Any()). 190 DoAndReturn(func(ctx context.Context, virtualservice *istioclient.VirtualService, opts ...client.CreateOption) error { 191 return nil 192 }) 193 // Expect a call to update the status of the IngressTrait. 194 mockStatus.EXPECT(). 195 Update(gomock.Any(), gomock.Any(), gomock.Any()). 196 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 197 assert.Len(trait.Status.Conditions, 1) 198 assert.Len(trait.Status.Resources, 3) 199 return nil 200 }) 201 202 // Create and make the request 203 request := newRequest(testNamespace, testTraitName) 204 reconciler := newIngressTraitReconciler(mock) 205 result, err := reconciler.Reconcile(context.TODO(), request) 206 207 // Validate the results 208 mocker.Finish() 209 assert.NoError(err) 210 assert.Equal(true, result.Requeue) 211 assert.Equal(time.Duration(0), result.RequeueAfter) 212 } 213 214 // TestSuccessfullyCreateNewIngressWithCertSecret tests the Reconcile method for the following use case. 215 // GIVEN a request to reconcile an ingress trait resource that specifies a certificate secret to use for security 216 // WHEN the trait exists but the ingress does not 217 // THEN ensure that the trait is created. 218 func TestSuccessfullyCreateNewIngressWithCertSecret(t *testing.T) { 219 assert := asserts.New(t) 220 mocker := gomock.NewController(t) 221 mock := mocks.NewMockClient(mocker) 222 mockStatus := mocks.NewMockStatusWriter(mocker) 223 // Expect a call to get the ingress trait resource. 224 mock.EXPECT(). 225 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 226 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 227 trait.TypeMeta = metav1.TypeMeta{ 228 APIVersion: apiVersion, 229 Kind: traitKind} 230 trait.ObjectMeta = metav1.ObjectMeta{ 231 Namespace: name.Namespace, 232 Name: name.Name, 233 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 234 trait.Spec.Rules = []vzapi.IngressRule{{ 235 Hosts: []string{"test-host"}, 236 Paths: []vzapi.IngressPath{{Path: "test-path"}}}} 237 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 238 trait.Spec.WorkloadReference = oamrt.TypedReference{ 239 APIVersion: "core.oam.dev/v1alpha2", 240 Kind: "ContainerizedWorkload", 241 Name: testWorkloadName} 242 return nil 243 }) 244 // Expect a call to update the ingress trait resource with a finalizer. 245 mock.EXPECT(). 246 Update(gomock.Any(), gomock.Any(), gomock.Any()). 247 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 248 assert.Equal(testNamespace, trait.Namespace) 249 assert.Equal(testTraitName, trait.Name) 250 assert.Len(trait.Finalizers, 1) 251 assert.Equal(finalizerName, trait.Finalizers[0]) 252 return nil 253 }) 254 255 workLoadResourceExpectations(mock) 256 workloadResourceDefinitionExpectations(mock) 257 listChildDeploymentExpectations(mock, assert) 258 childServiceExpectations(mock, assert) 259 getGatewayForTraitNotFoundExpectations(mock) 260 261 // Expect a call to create the ingress/gateway resource and return success 262 mock.EXPECT(). 263 Create(gomock.Any(), gomock.Any(), gomock.Any()). 264 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 265 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 266 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 267 return nil 268 }) 269 // Expect a call to get the app config and return that it is not found. 270 mock.EXPECT(). 271 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 272 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 273 app.TypeMeta = metav1.TypeMeta{ 274 APIVersion: "core.oam.dev/v1alpha2", 275 Kind: "ApplicationConfiguration", 276 } 277 return nil 278 }) 279 280 traitVSNotFoundExpectation(mock) 281 createVSSuccessExpectations(mock) 282 getMockStatusWriterExpectations(mock, mockStatus) 283 updateMockStatusExpectations(mockStatus, assert) 284 285 // Create and make the request 286 request := newRequest(testNamespace, testTraitName) 287 reconciler := newIngressTraitReconciler(mock) 288 result, err := reconciler.Reconcile(context.TODO(), request) 289 290 // Validate the results 291 mocker.Finish() 292 assert.NoError(err) 293 assert.Equal(true, result.Requeue) 294 assert.Equal(time.Duration(0), result.RequeueAfter) 295 } 296 297 // TestSuccessfullyCreateNewIngressWithAuthzPolicy tests the Reconcile method for the following use case. 298 // GIVEN a request to reconcile an ingress trait resource that specifies an authorization policy for the test path 299 // WHEN the trait exists but the ingress does not 300 // THEN ensure that the trait and the authorization policy are created. 301 func TestSuccessfullyCreateNewIngressWithAuthorizationPolicy(t *testing.T) { 302 assert := asserts.New(t) 303 mocker := gomock.NewController(t) 304 mock := mocks.NewMockClient(mocker) 305 mockStatus := mocks.NewMockStatusWriter(mocker) 306 // Expect a call to get the ingress trait resource. 307 mock.EXPECT(). 308 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 309 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 310 trait.TypeMeta = metav1.TypeMeta{ 311 APIVersion: apiVersion, 312 Kind: traitKind} 313 trait.ObjectMeta = metav1.ObjectMeta{ 314 Namespace: name.Namespace, 315 Name: name.Name, 316 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 317 trait.Spec.Rules = []vzapi.IngressRule{{ 318 Hosts: []string{"test-host"}, 319 Paths: []vzapi.IngressPath{ 320 { 321 Path: "/test-path", 322 Policy: &vzapi.AuthorizationPolicy{ 323 Rules: []*vzapi.AuthorizationRule{ 324 { 325 From: &vzapi.AuthorizationRuleFrom{RequestPrincipals: []string{"*"}}, 326 When: []*vzapi.AuthorizationRuleCondition{ 327 { 328 Key: "testKey", 329 Values: []string{"testValue"}, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 }}} 337 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 338 trait.Spec.WorkloadReference = oamrt.TypedReference{ 339 APIVersion: "core.oam.dev/v1alpha2", 340 Kind: "ContainerizedWorkload", 341 Name: testWorkloadName} 342 return nil 343 }) 344 // Expect a call to update the ingress trait resource with a finalizer. 345 mock.EXPECT(). 346 Update(gomock.Any(), gomock.Any(), gomock.Any()). 347 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 348 assert.Equal(testNamespace, trait.Namespace) 349 assert.Equal(testTraitName, trait.Name) 350 assert.Len(trait.Finalizers, 1) 351 assert.Equal(finalizerName, trait.Finalizers[0]) 352 return nil 353 }) 354 355 workLoadResourceExpectations(mock) 356 workloadResourceDefinitionExpectations(mock) 357 listChildDeploymentExpectations(mock, assert) 358 childServiceExpectations(mock, assert) 359 getGatewayForTraitNotFoundExpectations(mock) 360 361 // Expect a call to create the ingress/gateway resource and return success 362 mock.EXPECT(). 363 Create(gomock.Any(), gomock.Any(), gomock.Any()). 364 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 365 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 366 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 367 return nil 368 }) 369 // Expect a call to get the app config and return that it is not found. 370 mock.EXPECT(). 371 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 372 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 373 app.TypeMeta = metav1.TypeMeta{ 374 APIVersion: "core.oam.dev/v1alpha2", 375 Kind: "ApplicationConfiguration", 376 } 377 return nil 378 }) 379 380 traitVSNotFoundExpectation(mock) 381 createVSSuccessExpectations(mock) 382 traitAuthzPolicyNotFoundExpectation(mock) 383 createAuthzPolicySuccessExpectations(mock, assert, 1, 1) 384 getMockStatusWriterExpectations(mock, mockStatus) 385 386 mockStatus.EXPECT(). 387 Update(gomock.Any(), gomock.Any()). 388 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 389 assert.Len(trait.Status.Conditions, 1) 390 assert.Len(trait.Status.Resources, 3) 391 return nil 392 }) 393 394 // Create and make the request 395 request := newRequest(testNamespace, testTraitName) 396 reconciler := newIngressTraitReconciler(mock) 397 result, err := reconciler.Reconcile(context.TODO(), request) 398 399 // Validate the results 400 mocker.Finish() 401 assert.NoError(err) 402 assert.Equal(true, result.Requeue) 403 assert.Equal(time.Duration(0), result.RequeueAfter) 404 } 405 406 // TestSuccessfullyCreateIngressWithAuthorizationPolicy2Paths tests the Reconcile method for the following use case. 407 // GIVEN a request to reconcile an ingress trait resource that specifies an authorization policy for the test path 408 // and a root path 409 // WHEN the trait exists but the ingress does not 410 // THEN ensure that the trait and the authorization policy are created. 411 func TestSuccessfullyCreateIngressWithAuthorizationPolicy2Paths(t *testing.T) { 412 assert := asserts.New(t) 413 mocker := gomock.NewController(t) 414 mock := mocks.NewMockClient(mocker) 415 mockStatus := mocks.NewMockStatusWriter(mocker) 416 // Expect a call to get the ingress trait resource. 417 mock.EXPECT(). 418 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 419 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 420 trait.TypeMeta = metav1.TypeMeta{ 421 APIVersion: apiVersion, 422 Kind: traitKind} 423 trait.ObjectMeta = metav1.ObjectMeta{ 424 Namespace: name.Namespace, 425 Name: name.Name, 426 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 427 trait.Spec.Rules = []vzapi.IngressRule{{ 428 Hosts: []string{"test-host"}, 429 Paths: []vzapi.IngressPath{ 430 {Path: "/"}, 431 { 432 Path: "/test-path", 433 Policy: &vzapi.AuthorizationPolicy{ 434 Rules: []*vzapi.AuthorizationRule{ 435 { 436 From: &vzapi.AuthorizationRuleFrom{RequestPrincipals: []string{"*"}}, 437 When: []*vzapi.AuthorizationRuleCondition{ 438 { 439 Key: "testKey", 440 Values: []string{"testValue"}, 441 }, 442 }, 443 }, 444 }, 445 }, 446 }, 447 }}} 448 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 449 trait.Spec.WorkloadReference = oamrt.TypedReference{ 450 APIVersion: "core.oam.dev/v1alpha2", 451 Kind: "ContainerizedWorkload", 452 Name: testWorkloadName} 453 return nil 454 }) 455 // Expect a call to update the ingress trait resource with a finalizer. 456 mock.EXPECT(). 457 Update(gomock.Any(), gomock.Any(), gomock.Any()). 458 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 459 assert.Equal(testNamespace, trait.Namespace) 460 assert.Equal(testTraitName, trait.Name) 461 assert.Len(trait.Finalizers, 1) 462 assert.Equal(finalizerName, trait.Finalizers[0]) 463 return nil 464 }) 465 466 workLoadResourceExpectations(mock) 467 workloadResourceDefinitionExpectations(mock) 468 listChildDeploymentExpectations(mock, assert) 469 childServiceExpectations(mock, assert) 470 getGatewayForTraitNotFoundExpectations(mock) 471 472 // Expect a call to create the ingress/gateway resource and return success 473 mock.EXPECT(). 474 Create(gomock.Any(), gomock.Any(), gomock.Any()). 475 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 476 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 477 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 478 return nil 479 }) 480 // Expect a call to get the app config and return that it is not found. 481 mock.EXPECT(). 482 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 483 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 484 app.TypeMeta = metav1.TypeMeta{ 485 APIVersion: "core.oam.dev/v1alpha2", 486 Kind: "ApplicationConfiguration", 487 } 488 return nil 489 }) 490 491 traitVSNotFoundExpectation(mock) 492 createVSSuccessExpectations(mock) 493 traitAuthzPolicyRootPathNotFoundExpectation(mock) 494 traitAuthzPolicyNotFoundExpectation(mock) 495 createAuthzPolicyRootPathSuccessExpectations(mock, assert, 1, 0) 496 createAuthzPolicySuccessExpectations(mock, assert, 1, 1) 497 getMockStatusWriterExpectations(mock, mockStatus) 498 499 mockStatus.EXPECT(). 500 Update(gomock.Any(), gomock.Any()). 501 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 502 assert.Len(trait.Status.Conditions, 1) 503 assert.Len(trait.Status.Resources, 4) 504 return nil 505 }) 506 507 // Create and make the request 508 request := newRequest(testNamespace, testTraitName) 509 reconciler := newIngressTraitReconciler(mock) 510 result, err := reconciler.Reconcile(context.TODO(), request) 511 512 // Validate the results 513 mocker.Finish() 514 assert.NoError(err) 515 assert.Equal(true, result.Requeue) 516 assert.Equal(time.Duration(0), result.RequeueAfter) 517 } 518 519 // TestSuccessfullyCreateIngressWithAuthorizationPolicyNoPaths tests the Reconcile method for the following use case. 520 // GIVEN a request to reconcile an ingress trait resource that specifies an authorization policy for no endpoints in the 521 // ingress path. 522 // WHEN the trait exists but the ingress does not 523 // THEN ensure that the trait and the authorization policy are created. 524 func TestSuccessfullyCreateIngressWithAuthorizationPolicyNoPaths(t *testing.T) { 525 assert := asserts.New(t) 526 mocker := gomock.NewController(t) 527 mock := mocks.NewMockClient(mocker) 528 mockStatus := mocks.NewMockStatusWriter(mocker) 529 // Expect a call to get the ingress trait resource. 530 mock.EXPECT(). 531 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 532 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 533 trait.TypeMeta = metav1.TypeMeta{ 534 APIVersion: apiVersion, 535 Kind: traitKind} 536 trait.ObjectMeta = metav1.ObjectMeta{ 537 Namespace: name.Namespace, 538 Name: name.Name, 539 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 540 trait.Spec.Rules = []vzapi.IngressRule{{ 541 Hosts: []string{"test-host"}, 542 Paths: []vzapi.IngressPath{ 543 { 544 Policy: &vzapi.AuthorizationPolicy{ 545 Rules: []*vzapi.AuthorizationRule{ 546 { 547 From: &vzapi.AuthorizationRuleFrom{RequestPrincipals: []string{"*"}}, 548 When: []*vzapi.AuthorizationRuleCondition{ 549 { 550 Key: "testKey", 551 Values: []string{"testValue"}, 552 }, 553 }, 554 }, 555 }, 556 }, 557 }, 558 }}} 559 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 560 trait.Spec.WorkloadReference = oamrt.TypedReference{ 561 APIVersion: "core.oam.dev/v1alpha2", 562 Kind: "ContainerizedWorkload", 563 Name: testWorkloadName} 564 return nil 565 }) 566 // Expect a call to update the ingress trait resource with a finalizer. 567 mock.EXPECT(). 568 Update(gomock.Any(), gomock.Any(), gomock.Any()). 569 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 570 assert.Equal(testNamespace, trait.Namespace) 571 assert.Equal(testTraitName, trait.Name) 572 assert.Len(trait.Finalizers, 1) 573 assert.Equal(finalizerName, trait.Finalizers[0]) 574 return nil 575 }) 576 577 workLoadResourceExpectations(mock) 578 workloadResourceDefinitionExpectations(mock) 579 listChildDeploymentExpectations(mock, assert) 580 childServiceExpectations(mock, assert) 581 getGatewayForTraitNotFoundExpectations(mock) 582 583 // Expect a call to create the ingress/gateway resource and return success 584 mock.EXPECT(). 585 Create(gomock.Any(), gomock.Any(), gomock.Any()). 586 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 587 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 588 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 589 return nil 590 }) 591 // Expect a call to get the app config and return that it is not found. 592 mock.EXPECT(). 593 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 594 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 595 app.TypeMeta = metav1.TypeMeta{ 596 APIVersion: "core.oam.dev/v1alpha2", 597 Kind: "ApplicationConfiguration", 598 } 599 return nil 600 }) 601 602 traitVSNotFoundExpectation(mock) 603 createVSSuccessExpectations(mock) 604 traitAuthzPolicyRootPathNotFoundExpectation(mock) 605 createAuthzPolicyRootPathSuccessExpectations(mock, assert, 1, 1) 606 getMockStatusWriterExpectations(mock, mockStatus) 607 608 mockStatus.EXPECT(). 609 Update(gomock.Any(), gomock.Any()). 610 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 611 assert.Len(trait.Status.Conditions, 1) 612 assert.Len(trait.Status.Resources, 3) 613 return nil 614 }) 615 616 // Create and make the request 617 request := newRequest(testNamespace, testTraitName) 618 reconciler := newIngressTraitReconciler(mock) 619 result, err := reconciler.Reconcile(context.TODO(), request) 620 621 // Validate the results 622 mocker.Finish() 623 assert.NoError(err) 624 assert.Equal(true, result.Requeue) 625 assert.Equal(time.Duration(0), result.RequeueAfter) 626 } 627 628 // TestSuccessfullyCreateNewIngressWithAuthzPolicyMultipleRules tests the Reconcile method for the following use case. 629 // GIVEN a request to reconcile an ingress trait resource that specifies an authorization policy with multiple rules for the test path 630 // WHEN the trait exists but the ingress does not 631 // THEN ensure that the trait and the authorization policy are created. 632 func TestSuccessfullyCreateNewIngressWithAuthorizationPolicyMultipleRules(t *testing.T) { 633 assert := asserts.New(t) 634 mocker := gomock.NewController(t) 635 mock := mocks.NewMockClient(mocker) 636 mockStatus := mocks.NewMockStatusWriter(mocker) 637 // Expect a call to get the ingress trait resource. 638 mock.EXPECT(). 639 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 640 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 641 trait.TypeMeta = metav1.TypeMeta{ 642 APIVersion: apiVersion, 643 Kind: traitKind} 644 trait.ObjectMeta = metav1.ObjectMeta{ 645 Namespace: name.Namespace, 646 Name: name.Name, 647 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 648 trait.Spec.Rules = []vzapi.IngressRule{{ 649 Hosts: []string{"test-host"}, 650 Paths: []vzapi.IngressPath{ 651 { 652 Path: "/test-path", 653 Policy: &vzapi.AuthorizationPolicy{ 654 Rules: []*vzapi.AuthorizationRule{ 655 { 656 From: &vzapi.AuthorizationRuleFrom{RequestPrincipals: []string{"*"}}, 657 When: []*vzapi.AuthorizationRuleCondition{ 658 { 659 Key: "testKey", 660 Values: []string{"testValue"}, 661 }, 662 }, 663 }, 664 { 665 From: &vzapi.AuthorizationRuleFrom{RequestPrincipals: []string{"*"}}, 666 When: []*vzapi.AuthorizationRuleCondition{ 667 { 668 Key: "testKey2", 669 Values: []string{"testValue2"}, 670 }, 671 }, 672 }, 673 }, 674 }, 675 }, 676 }}} 677 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 678 trait.Spec.WorkloadReference = oamrt.TypedReference{ 679 APIVersion: "core.oam.dev/v1alpha2", 680 Kind: "ContainerizedWorkload", 681 Name: testWorkloadName} 682 return nil 683 }) 684 // Expect a call to update the ingress trait resource with a finalizer. 685 mock.EXPECT(). 686 Update(gomock.Any(), gomock.Any(), gomock.Any()). 687 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 688 assert.Equal(testNamespace, trait.Namespace) 689 assert.Equal(testTraitName, trait.Name) 690 assert.Len(trait.Finalizers, 1) 691 assert.Equal(finalizerName, trait.Finalizers[0]) 692 return nil 693 }) 694 695 workLoadResourceExpectations(mock) 696 workloadResourceDefinitionExpectations(mock) 697 listChildDeploymentExpectations(mock, assert) 698 childServiceExpectations(mock, assert) 699 getGatewayForTraitNotFoundExpectations(mock) 700 701 // Expect a call to create the ingress/gateway resource and return success 702 mock.EXPECT(). 703 Create(gomock.Any(), gomock.Any(), gomock.Any()). 704 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 705 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 706 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 707 return nil 708 }) 709 // Expect a call to get the app config and return that it is not found. 710 mock.EXPECT(). 711 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 712 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 713 app.TypeMeta = metav1.TypeMeta{ 714 APIVersion: "core.oam.dev/v1alpha2", 715 Kind: "ApplicationConfiguration", 716 } 717 return nil 718 }) 719 720 traitVSNotFoundExpectation(mock) 721 createVSSuccessExpectations(mock) 722 traitAuthzPolicyNotFoundExpectation(mock) 723 createAuthzPolicySuccessExpectations(mock, assert, 2, 1) 724 getMockStatusWriterExpectations(mock, mockStatus) 725 726 mockStatus.EXPECT(). 727 Update(gomock.Any(), gomock.Any()). 728 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 729 assert.Len(trait.Status.Conditions, 1) 730 assert.Len(trait.Status.Resources, 3) 731 return nil 732 }) 733 734 // Create and make the request 735 request := newRequest(testNamespace, testTraitName) 736 reconciler := newIngressTraitReconciler(mock) 737 result, err := reconciler.Reconcile(context.TODO(), request) 738 739 // Validate the results 740 mocker.Finish() 741 assert.NoError(err) 742 assert.Equal(true, result.Requeue) 743 assert.Equal(time.Duration(0), result.RequeueAfter) 744 } 745 746 // TestFailureCreateNewIngressWithAuthorizationPolicyNoFromClause tests the Reconcile method for the following use case. 747 // GIVEN a request to reconcile an ingress trait resource that specifies an authorization policy with no 'from' clause 748 // WHEN the trait exists but the ingress does not 749 // THEN ensure that the trait and the authorization policy are not created. 750 func TestFailureCreateNewIngressWithAuthorizationPolicyNoFromClause(t *testing.T) { 751 assert := asserts.New(t) 752 mocker := gomock.NewController(t) 753 mock := mocks.NewMockClient(mocker) 754 mockStatus := mocks.NewMockStatusWriter(mocker) 755 // Expect a call to get the ingress trait resource. 756 mock.EXPECT(). 757 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 758 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 759 trait.TypeMeta = metav1.TypeMeta{ 760 APIVersion: apiVersion, 761 Kind: traitKind} 762 trait.ObjectMeta = metav1.ObjectMeta{ 763 Namespace: name.Namespace, 764 Name: name.Name, 765 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 766 trait.Spec.Rules = []vzapi.IngressRule{{ 767 Hosts: []string{"test-host"}, 768 Paths: []vzapi.IngressPath{ 769 { 770 Path: "/test-path", 771 Policy: &vzapi.AuthorizationPolicy{ 772 Rules: []*vzapi.AuthorizationRule{ 773 { 774 When: []*vzapi.AuthorizationRuleCondition{ 775 { 776 Key: "testKey", 777 Values: []string{"testValue"}, 778 }, 779 }, 780 }, 781 }, 782 }, 783 }, 784 }}} 785 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 786 trait.Spec.WorkloadReference = oamrt.TypedReference{ 787 APIVersion: "core.oam.dev/v1alpha2", 788 Kind: "ContainerizedWorkload", 789 Name: testWorkloadName} 790 return nil 791 }) 792 // Expect a call to update the ingress trait resource with a finalizer. 793 mock.EXPECT(). 794 Update(gomock.Any(), gomock.Any(), gomock.Any()). 795 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 796 assert.Equal(testNamespace, trait.Namespace) 797 assert.Equal(testTraitName, trait.Name) 798 assert.Len(trait.Finalizers, 1) 799 assert.Equal(finalizerName, trait.Finalizers[0]) 800 return nil 801 }) 802 803 workLoadResourceExpectations(mock) 804 workloadResourceDefinitionExpectations(mock) 805 listChildDeploymentExpectations(mock, assert) 806 childServiceExpectations(mock, assert) 807 getGatewayForTraitNotFoundExpectations(mock) 808 809 // Expect a call to create the ingress/gateway resource and return success 810 mock.EXPECT(). 811 Create(gomock.Any(), gomock.Any(), gomock.Any()). 812 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 813 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 814 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 815 return nil 816 }) 817 // Expect a call to get the app config and return that it is not found. 818 mock.EXPECT(). 819 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 820 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 821 app.TypeMeta = metav1.TypeMeta{ 822 APIVersion: "core.oam.dev/v1alpha2", 823 Kind: "ApplicationConfiguration", 824 } 825 return nil 826 }) 827 828 traitVSNotFoundExpectation(mock) 829 createVSSuccessExpectations(mock) 830 traitAuthzPolicyNotFoundExpectation(mock) 831 getMockStatusWriterExpectations(mock, mockStatus) 832 833 mockStatus.EXPECT(). 834 Update(gomock.Any(), gomock.Any(), gomock.Any()). 835 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 836 assert.Len(trait.Status.Conditions, 1) 837 assert.Equal("Authorization Policy requires 'From' clause", trait.Status.Conditions[0].Message, "Unexpected error message") 838 assert.Len(trait.Status.Resources, 3) 839 return nil 840 }) 841 842 // Create and make the request 843 request := newRequest(testNamespace, testTraitName) 844 reconciler := newIngressTraitReconciler(mock) 845 result, err := reconciler.Reconcile(context.TODO(), request) 846 847 // Validate the results 848 mocker.Finish() 849 assert.NoError(err) 850 assert.Equal(true, result.Requeue) 851 assert.Equal(time.Duration(0), result.RequeueAfter) 852 } 853 854 // TestSuccessfullyUpdateIngressWithCertSecret tests the Reconcile method for the following use case. 855 // GIVEN a request to reconcile an ingress trait resource that specifies a certificate secret to use for security 856 // WHEN the trait and ingress/gateway exist 857 // THEN ensure that the trait is updated with the expected hosts. 858 func TestSuccessfullyUpdateIngressWithCertSecret(t *testing.T) { 859 assert := asserts.New(t) 860 mocker := gomock.NewController(t) 861 mock := mocks.NewMockClient(mocker) 862 mockStatus := mocks.NewMockStatusWriter(mocker) 863 864 // As of 1.3, this represents an older configuration; since the IngressTrait only defines 1 host that 865 // is what will result in the end. 866 expectedHosts := []string{"test-host", "test2-host", "test3-host"} 867 868 appName := "myapp" 869 workloadRef := oamrt.TypedReference{ 870 APIVersion: "core.oam.dev/v1alpha2", 871 Kind: "ContainerizedWorkload", 872 Name: testWorkloadName} 873 rules := []vzapi.IngressRule{{ 874 Hosts: []string{"Test-host", "test2-host", "test3-host"}, 875 Paths: []vzapi.IngressPath{{Path: "test-path"}}}} 876 tls := vzapi.IngressSecurity{SecretName: "cert-secret"} 877 878 gatewayName := expectedAppGWName 879 880 // Expect a call to get the ingress trait resource. 881 mock.EXPECT(). 882 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 883 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 884 trait.TypeMeta = metav1.TypeMeta{ 885 APIVersion: apiVersion, 886 Kind: traitKind} 887 trait.ObjectMeta = metav1.ObjectMeta{ 888 Namespace: name.Namespace, 889 Name: name.Name, 890 Labels: map[string]string{oam.LabelAppName: appName, oam.LabelAppComponent: "mycomp"}} 891 trait.Spec.Rules = rules 892 trait.Spec.TLS = tls 893 trait.Spec.WorkloadReference = workloadRef 894 return nil 895 }) 896 // Expect a call to update the ingress trait resource with a finalizer. 897 mock.EXPECT(). 898 Update(gomock.Any(), gomock.Any(), gomock.Any()). 899 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 900 assert.Equal(testNamespace, trait.Namespace) 901 assert.Equal(testTraitName, trait.Name) 902 assert.Len(trait.Finalizers, 1) 903 assert.Equal(finalizerName, trait.Finalizers[0]) 904 return nil 905 }) 906 907 workLoadResourceExpectations(mock) 908 workloadResourceDefinitionExpectations(mock) 909 listChildDeploymentExpectations(mock, assert) 910 childServiceExpectations(mock, assert) 911 912 traitVSNotFoundExpectation(mock) 913 createVSSuccessExpectations(mock) 914 915 getMockStatusWriterExpectations(mock, mockStatus) 916 updateMockStatusExpectations(mockStatus, assert) 917 918 // Expect a call to get the gateway resource related to the ingress trait and return it. 919 mock.EXPECT(). 920 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: gatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 921 DoAndReturn(func(ctx context.Context, name types.NamespacedName, gateway *istioclient.Gateway, opts ...client.GetOption) error { 922 gateway.TypeMeta = metav1.TypeMeta{ 923 APIVersion: gatewayAPIVersion, 924 Kind: gatewayKind} 925 gateway.ObjectMeta = metav1.ObjectMeta{ 926 Namespace: testNamespace, 927 Name: gatewayName} 928 return nil 929 }) 930 // Expect a call to create the ingress/gateway resource and return success 931 mock.EXPECT(). 932 Update(gomock.Any(), gomock.Any(), gomock.Any()). 933 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.UpdateOption) error { 934 assert.Equal(istionet.ServerTLSSettings_SIMPLE, gateway.Spec.Servers[0].Tls.Mode, "Wrong Tls Mode") 935 assert.Equal("cert-secret", gateway.Spec.Servers[0].Tls.CredentialName, "Wrong secret name") 936 assert.Len(gateway.Spec.Servers, 1) 937 assert.Equal(testTraitName, gateway.Spec.Servers[0].Name) 938 assert.Equal(testTraitPortName, gateway.Spec.Servers[0].Port.Name) 939 assert.Equal(expectedHosts, gateway.Spec.Servers[0].Hosts) 940 return nil 941 }) 942 // Expect a call to get the app config and return that it is not found. 943 mock.EXPECT(). 944 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: appName}, gomock.Not(gomock.Nil()), gomock.Any()). 945 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 946 app.TypeMeta = metav1.TypeMeta{ 947 APIVersion: "core.oam.dev/v1alpha2", 948 Kind: "ApplicationConfiguration", 949 } 950 return nil 951 }) 952 953 // Create and make the request 954 request := newRequest(testNamespace, testTraitName) 955 reconciler := newIngressTraitReconciler(mock) 956 result, err := reconciler.Reconcile(context.TODO(), request) 957 958 // Validate the results 959 mocker.Finish() 960 assert.NoError(err) 961 assert.Equal(true, result.Requeue) 962 assert.Equal(time.Duration(0), result.RequeueAfter) 963 } 964 965 // TestFailureCreateNewIngressWithSecretNoHosts tests the Reconcile method for the following use case. 966 // GIVEN a request to reconcile an ingress trait resource that specifies a certificate secret to use for security 967 // WHEN the secret is specified but no associated hosts are configured 968 // THEN ensure that the trait creation fails 969 func TestFailureCreateNewIngressWithSecretNoHosts(t *testing.T) { 970 assert := asserts.New(t) 971 mocker := gomock.NewController(t) 972 mock := mocks.NewMockClient(mocker) 973 mockStatus := mocks.NewMockStatusWriter(mocker) 974 975 // Expect a call to get the Verrazzano ingress and return the ingress. 976 mock.EXPECT(). 977 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 978 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 979 ingress.TypeMeta = metav1.TypeMeta{ 980 APIVersion: "networking.k8s.io/v1", 981 Kind: "ingress"} 982 ingress.ObjectMeta = metav1.ObjectMeta{ 983 Namespace: name.Namespace, 984 Name: name.Name, 985 Annotations: map[string]string{"external-dns.alpha.kubernetes.io/target": "verrazzano-ingress.my.host.com"}} 986 return nil 987 }) 988 // Expect a call to get the ingress trait resource. 989 mock.EXPECT(). 990 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 991 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 992 trait.TypeMeta = metav1.TypeMeta{ 993 APIVersion: apiVersion, 994 Kind: traitKind} 995 trait.ObjectMeta = metav1.ObjectMeta{ 996 Namespace: name.Namespace, 997 Name: name.Name, 998 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 999 trait.Spec.Rules = []vzapi.IngressRule{{ 1000 Paths: []vzapi.IngressPath{{Path: "test-path"}}}} 1001 trait.Spec.TLS = vzapi.IngressSecurity{SecretName: "cert-secret"} 1002 trait.Spec.WorkloadReference = oamrt.TypedReference{ 1003 APIVersion: "core.oam.dev/v1alpha2", 1004 Kind: "ContainerizedWorkload", 1005 Name: testWorkloadName} 1006 return nil 1007 }) 1008 // Expect a call to update the ingress trait resource with a finalizer. 1009 mock.EXPECT(). 1010 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1011 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 1012 assert.Equal(testNamespace, trait.Namespace) 1013 assert.Equal(testTraitName, trait.Name) 1014 assert.Len(trait.Finalizers, 1) 1015 assert.Equal(finalizerName, trait.Finalizers[0]) 1016 return nil 1017 }) 1018 getMockStatusWriterExpectations(mock, mockStatus) 1019 // Expect a call to update the status of the ingress trait. The status is checked for the expected error condition. 1020 mockStatus.EXPECT(). 1021 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1022 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 1023 assert.Len(trait.Status.Conditions, 1) 1024 assert.Equal("all rules must specify at least one host when a secret is specified for TLS transport", trait.Status.Conditions[0].Message, "Unexpected error message") 1025 assert.Len(trait.Status.Resources, 1) 1026 return nil 1027 }) 1028 1029 // Create and make the request 1030 request := newRequest(testNamespace, testTraitName) 1031 reconciler := newIngressTraitReconciler(mock) 1032 result, err := reconciler.Reconcile(context.TODO(), request) 1033 1034 // Validate the results 1035 mocker.Finish() 1036 assert.NoError(err) 1037 assert.Equal(true, result.Requeue) 1038 assert.Equal(time.Duration(0), result.RequeueAfter) 1039 } 1040 1041 // TestFailureCreateGatewayCertNoAppName tests the Reconcile method for the following use case. 1042 // GIVEN a request to reconcile an ingress trait resource 1043 // WHEN the trait exists but doesn't specify an oam app label 1044 // THEN ensure that an error is generated 1045 func TestFailureCreateGatewayCertNoAppName(t *testing.T) { 1046 assert := asserts.New(t) 1047 mocker := gomock.NewController(t) 1048 mock := mocks.NewMockClient(mocker) 1049 mockStatus := mocks.NewMockStatusWriter(mocker) 1050 // Expect a call to get the ingress trait resource. 1051 mock.EXPECT(). 1052 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 1053 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 1054 trait.TypeMeta = metav1.TypeMeta{ 1055 APIVersion: apiVersion, 1056 Kind: traitKind} 1057 trait.ObjectMeta = metav1.ObjectMeta{ 1058 Namespace: name.Namespace, 1059 Name: name.Name} 1060 trait.Spec.Rules = []vzapi.IngressRule{{ 1061 Hosts: []string{"test-host"}, 1062 Paths: []vzapi.IngressPath{{Path: "test-path"}}}} 1063 trait.Spec.WorkloadReference = oamrt.TypedReference{ 1064 APIVersion: "core.oam.dev/v1alpha2", 1065 Kind: "ContainerizedWorkload", 1066 Name: testWorkloadName} 1067 return nil 1068 }) 1069 // Expect a call to update the ingress trait resource with a finalizer. 1070 mock.EXPECT(). 1071 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1072 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 1073 assert.Equal(testNamespace, trait.Namespace) 1074 assert.Equal(testTraitName, trait.Name) 1075 assert.Len(trait.Finalizers, 1) 1076 assert.Equal(finalizerName, trait.Finalizers[0]) 1077 return nil 1078 }) 1079 1080 deleteCertExpectations(mock, "") 1081 deleteCertSecretExpectations(mock, "") 1082 appCertificateExpectations(mock) 1083 createCertSuccessExpectations(mock) 1084 getMockStatusWriterExpectations(mock, mockStatus) 1085 // Expect a call to update the status of the ingress trait. The status is checked for the expected error condition. 1086 mockStatus.EXPECT(). 1087 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1088 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 1089 assert.Len(trait.Status.Conditions, 1) 1090 assert.Equal("OAM app name label missing from metadata, unable to generate gateway name", trait.Status.Conditions[0].Message, "Unexpected error message") 1091 assert.Len(trait.Status.Resources, 1) 1092 return nil 1093 }) 1094 1095 // Create and make the request 1096 request := newRequest(testNamespace, testTraitName) 1097 reconciler := newIngressTraitReconciler(mock) 1098 result, err := reconciler.Reconcile(context.TODO(), request) 1099 1100 // Validate the results 1101 mocker.Finish() 1102 assert.NoError(err) 1103 assert.Equal(true, result.Requeue) 1104 assert.Equal(time.Duration(0), result.RequeueAfter) 1105 } 1106 1107 // TestSuccessfullyCreateNewIngressForServiceComponent tests the Reconcile method for the following use case. 1108 // GIVEN a request to reconcile an ingress trait resource that applies to a service workload type 1109 // WHEN the trait exists but the ingress does not 1110 // THEN ensure that the service workload is unwrapped and the trait is created. 1111 func TestSuccessfullyCreateNewIngressForServiceComponent(t *testing.T) { 1112 assert := asserts.New(t) 1113 mocker := gomock.NewController(t) 1114 mock := mocks.NewMockClient(mocker) 1115 mockStatus := mocks.NewMockStatusWriter(mocker) 1116 // Expect a call to get the ingress trait resource. 1117 mock.EXPECT(). 1118 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 1119 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 1120 trait.TypeMeta = metav1.TypeMeta{ 1121 APIVersion: apiVersion, 1122 Kind: traitKind} 1123 trait.ObjectMeta = metav1.ObjectMeta{ 1124 Namespace: name.Namespace, 1125 Name: name.Name, 1126 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 1127 trait.Spec.Rules = []vzapi.IngressRule{{ 1128 Hosts: []string{"test-host"}, 1129 Paths: []vzapi.IngressPath{{Path: "test-path"}}, 1130 Destination: vzapi.IngressDestination{ 1131 Host: "test-service.test-space.svc.local", 1132 Port: 0, 1133 }}} 1134 trait.Spec.WorkloadReference = oamrt.TypedReference{ 1135 APIVersion: "v1", 1136 Kind: "Service", 1137 Name: testWorkloadName} 1138 return nil 1139 }) 1140 // Expect a call to update the ingress trait resource with a finalizer. 1141 mock.EXPECT(). 1142 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1143 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 1144 assert.Equal(testNamespace, trait.Namespace) 1145 assert.Equal(testTraitName, trait.Name) 1146 assert.Len(trait.Finalizers, 1) 1147 assert.Equal(finalizerName, trait.Finalizers[0]) 1148 return nil 1149 }) 1150 1151 containedName := testwWorkloadName 1152 containedResource := map[string]interface{}{ 1153 "metadata": map[string]interface{}{ 1154 "name": containedName, 1155 }, 1156 } 1157 1158 // Expect a call to get the service workload resource 1159 mock.EXPECT(). 1160 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 1161 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 1162 workload.SetAPIVersion("v1") 1163 workload.SetKind("Service") 1164 workload.SetNamespace(name.Namespace) 1165 workload.SetName(name.Name) 1166 _ = unstructured.SetNestedMap(workload.Object, containedResource, "spec", "template") 1167 return nil 1168 }) 1169 // Expect a call to get the service workload resource definition 1170 mock.EXPECT(). 1171 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "services."}, gomock.Not(gomock.Nil()), gomock.Any()). 1172 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 1173 return k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "Service"}, testWorkloadName) 1174 }) 1175 appCertificateExpectations(mock) 1176 // Expect a call to get the app config and return that it is not found. 1177 mock.EXPECT(). 1178 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 1179 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 1180 app.TypeMeta = metav1.TypeMeta{ 1181 APIVersion: "core.oam.dev/v1alpha2", 1182 Kind: "ApplicationConfiguration", 1183 } 1184 return nil 1185 }) 1186 1187 deleteCertExpectations(mock, "test-space-myapp-cert") 1188 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 1189 createCertSuccessExpectations(mock) 1190 getGatewayForTraitNotFoundExpectations(mock) 1191 createIngressResourceSuccessExpectations(mock) 1192 traitVSNotFoundExpectation(mock) 1193 1194 createIngressResSuccessExpectations(mock, assert) 1195 getMockStatusWriterExpectations(mock, mockStatus) 1196 // Expect a call to update the status of the ingress trait. 1197 mockStatus.EXPECT(). 1198 Update(gomock.Any(), gomock.Any()). 1199 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 1200 assert.Len(trait.Status.Conditions, 1) 1201 assert.Len(trait.Status.Resources, 3) 1202 return nil 1203 }) 1204 1205 // Create and make the request 1206 request := newRequest(testNamespace, testTraitName) 1207 reconciler := newIngressTraitReconciler(mock) 1208 result, err := reconciler.Reconcile(context.TODO(), request) 1209 1210 // Validate the results 1211 mocker.Finish() 1212 assert.NoError(err) 1213 assert.Equal(true, result.Requeue) 1214 assert.Equal(time.Duration(0), result.RequeueAfter) 1215 } 1216 1217 // TestSuccessfullyCreateNewIngressForVerrazzanoWorkload tests the Reconcile method for the following use case. 1218 // GIVEN a request to reconcile an ingress trait resource that applies to a Verrazzano workload type 1219 // WHEN the trait exists but the ingress does not 1220 // THEN ensure that the workload is unwrapped and the trait is created. 1221 func TestSuccessfullyCreateNewIngressForVerrazzanoWorkload(t *testing.T) { 1222 assert := asserts.New(t) 1223 mocker := gomock.NewController(t) 1224 mock := mocks.NewMockClient(mocker) 1225 mockStatus := mocks.NewMockStatusWriter(mocker) 1226 // Expect a call to get the ingress trait resource. 1227 mock.EXPECT(). 1228 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 1229 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 1230 trait.TypeMeta = metav1.TypeMeta{ 1231 APIVersion: apiVersion, 1232 Kind: traitKind} 1233 trait.ObjectMeta = metav1.ObjectMeta{ 1234 Namespace: name.Namespace, 1235 Name: name.Name, 1236 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 1237 trait.Spec.Rules = []vzapi.IngressRule{{ 1238 Hosts: []string{"test-host"}, 1239 Paths: []vzapi.IngressPath{{Path: "test-path"}}, 1240 Destination: vzapi.IngressDestination{ 1241 Host: "test-service.test-space.svc.local", 1242 Port: 0, 1243 }}} 1244 trait.Spec.WorkloadReference = oamrt.TypedReference{ 1245 APIVersion: apiVersion, 1246 Kind: "VerrazzanoCoherenceWorkload", 1247 Name: testWorkloadName} 1248 return nil 1249 }) 1250 // Expect a call to update the ingress trait resource with a finalizer. 1251 mock.EXPECT(). 1252 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1253 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 1254 assert.Equal(testNamespace, trait.Namespace) 1255 assert.Equal(testTraitName, trait.Name) 1256 assert.Len(trait.Finalizers, 1) 1257 assert.Equal(finalizerName, trait.Finalizers[0]) 1258 return nil 1259 }) 1260 1261 containedName := testwWorkloadName 1262 containedResource := map[string]interface{}{ 1263 "metadata": map[string]interface{}{ 1264 "name": containedName, 1265 }, 1266 } 1267 1268 // Expect a call to get the Verrazzano Coherence workload resource 1269 mock.EXPECT(). 1270 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 1271 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 1272 workload.SetAPIVersion(apiVersion) 1273 workload.SetKind("VerrazzanoCoherenceWorkload") 1274 workload.SetNamespace(name.Namespace) 1275 workload.SetName(name.Name) 1276 _ = unstructured.SetNestedMap(workload.Object, containedResource, "spec", "template") 1277 return nil 1278 }) 1279 // Expect a call to get the contained Coherence resource 1280 mock.EXPECT(). 1281 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: containedName}, gomock.Not(gomock.Nil()), gomock.Any()). 1282 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 1283 workload.SetUnstructuredContent(containedResource) 1284 workload.SetNamespace(name.Namespace) 1285 workload.SetAPIVersion("coherence.oracle.com/v1") 1286 workload.SetKind("Coherence") 1287 workload.SetUID(testWorkloadID) 1288 return nil 1289 }) 1290 // Expect a call to get the containerized workload resource definition 1291 mock.EXPECT(). 1292 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "coherences.coherence.oracle.com"}, gomock.Not(gomock.Nil()), gomock.Any()). 1293 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 1294 workloadDef.Namespace = name.Namespace 1295 workloadDef.Name = name.Name 1296 workloadDef.Spec.ChildResourceKinds = []v1alpha2.ChildResourceKind{ 1297 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 1298 {APIVersion: "v1", Kind: "Service", Selector: nil}, 1299 } 1300 return nil 1301 }) 1302 // Expect a call to list the child Deployment resources of the Coherence workload definition 1303 mock.EXPECT(). 1304 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 1305 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 1306 assert.Equal("DeploymentList", list.GetKind()) 1307 return nil 1308 }) 1309 appCertificateExpectations(mock) 1310 // Expect a call to list the child Service resources of the Coherence workload definition 1311 mock.EXPECT(). 1312 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 1313 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 1314 assert.Equal("ServiceList", list.GetKind()) 1315 return appendAsUnstructured(list, k8score.Service{ 1316 TypeMeta: metav1.TypeMeta{ 1317 APIVersion: "v1", 1318 Kind: "Service", 1319 }, 1320 ObjectMeta: metav1.ObjectMeta{ 1321 OwnerReferences: []metav1.OwnerReference{{ 1322 APIVersion: "core.oam.dev/v1alpha2", 1323 Kind: "ContainerizedWorkload", 1324 Name: testWorkloadName, 1325 UID: testWorkloadID, 1326 }}}, 1327 Spec: k8score.ServiceSpec{ 1328 ClusterIP: testClusterIP, 1329 Ports: []k8score.ServicePort{{Port: 42}}}, 1330 }) 1331 }) 1332 // Expect a call to get the app config and return that it is not found. 1333 mock.EXPECT(). 1334 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 1335 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 1336 app.TypeMeta = metav1.TypeMeta{ 1337 APIVersion: "core.oam.dev/v1alpha2", 1338 Kind: "ApplicationConfiguration", 1339 } 1340 return nil 1341 }) 1342 1343 deleteCertExpectations(mock, "test-space-myapp-cert") 1344 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 1345 createCertSuccessExpectations(mock) 1346 getGatewayForTraitNotFoundExpectations(mock) 1347 createIngressResourceSuccessExpectations(mock) 1348 traitVSNotFoundExpectation(mock) 1349 1350 createIngressResSuccessExpectations(mock, assert) 1351 getMockStatusWriterExpectations(mock, mockStatus) 1352 // Expect a call to update the status of the ingress trait. 1353 mockStatus.EXPECT(). 1354 Update(gomock.Any(), gomock.Any()). 1355 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 1356 assert.Len(trait.Status.Conditions, 1) 1357 assert.Len(trait.Status.Resources, 3) 1358 return nil 1359 }) 1360 1361 // Create and make the request 1362 request := newRequest(testNamespace, testTraitName) 1363 reconciler := newIngressTraitReconciler(mock) 1364 result, err := reconciler.Reconcile(context.TODO(), request) 1365 1366 // Validate the results 1367 mocker.Finish() 1368 assert.NoError(err) 1369 assert.Equal(true, result.Requeue) 1370 assert.Equal(time.Duration(0), result.RequeueAfter) 1371 } 1372 1373 // TestFailureToGetWorkload tests the Reconcile method for the following use case. 1374 // GIVEN a request to reconcile an ingress trait resource 1375 // WHEN the workload related to the trait cannot be found 1376 // THEN ensure that an error is returned 1377 func TestFailureToGetWorkload(t *testing.T) { 1378 assert := asserts.New(t) 1379 mocker := gomock.NewController(t) 1380 mock := mocks.NewMockClient(mocker) 1381 getIngressTraitResourceExpectations(mock, assert) 1382 deleteCertExpectations(mock, "test-space-myapp-cert") 1383 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 1384 createCertSuccessExpectations(mock) 1385 appCertificateExpectations(mock) 1386 getGatewayForTraitNotFoundExpectations(mock) 1387 // Expect a call to create the gateway and return success 1388 mock.EXPECT(). 1389 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1390 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 1391 return nil 1392 }) 1393 1394 // Expect a call to get the app config 1395 mock.EXPECT(). 1396 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 1397 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 1398 app.TypeMeta = metav1.TypeMeta{ 1399 APIVersion: "core.oam.dev/v1alpha2", 1400 Kind: "ApplicationConfiguration", 1401 } 1402 return nil 1403 }) 1404 // Expect a call to get the containerized workload resource and return an error 1405 mock.EXPECT(). 1406 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 1407 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 1408 return k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "ContainerizedWorkload"}, testWorkloadName) 1409 }) 1410 1411 // Create and make the request 1412 request := newRequest(testNamespace, testTraitName) 1413 reconciler := newIngressTraitReconciler(mock) 1414 result, err := reconciler.Reconcile(context.TODO(), request) 1415 1416 // Validate the results 1417 mocker.Finish() 1418 assert.Nil(err) 1419 assert.Equal(true, result.Requeue) 1420 assert.GreaterOrEqual(result.RequeueAfter.Milliseconds(), time.Duration(0).Milliseconds()) 1421 } 1422 1423 // TestFailureToGetWorkloadDefinition tests the Reconcile method for the following use case 1424 // GIVEN a request to reconcile an ingress trait resource 1425 // WHEN the workload definition of the workload related to the trait cannot be found 1426 // THEN ensure that an error is returned 1427 func TestFailureToGetWorkloadDefinition(t *testing.T) { 1428 assert := asserts.New(t) 1429 mocker := gomock.NewController(t) 1430 mock := mocks.NewMockClient(mocker) 1431 1432 getIngressTraitResourceExpectations(mock, assert) 1433 deleteCertExpectations(mock, "test-space-myapp-cert") 1434 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 1435 createCertSuccessExpectations(mock) 1436 appCertificateExpectations(mock) 1437 gatewayNotFoundExpectations(mock) 1438 1439 // Expect a call to create the gateway and return success 1440 mock.EXPECT(). 1441 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1442 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 1443 return nil 1444 }) 1445 1446 // Expect a call to get the app config 1447 mock.EXPECT(). 1448 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 1449 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 1450 app.TypeMeta = metav1.TypeMeta{ 1451 APIVersion: "core.oam.dev/v1alpha2", 1452 Kind: "ApplicationConfiguration", 1453 } 1454 return nil 1455 }) 1456 1457 workLoadResourceExpectations(mock) 1458 1459 // Expect a call to get the containerized workload resource definition and return an error 1460 mock.EXPECT(). 1461 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "containerizedworkloads.core.oam.dev"}, gomock.Not(gomock.Nil()), gomock.Any()). 1462 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 1463 return k8serrors.NewNotFound(schema.GroupResource{Group: "", Resource: "WorkloadDefinition"}, "containerizedworkloads.core.oam.dev") 1464 }) 1465 1466 // Create and make the request 1467 request := newRequest(testNamespace, testTraitName) 1468 reconciler := newIngressTraitReconciler(mock) 1469 result, err := reconciler.Reconcile(context.TODO(), request) 1470 1471 // Validate the results 1472 mocker.Finish() 1473 assert.Nil(err) 1474 assert.Equal(true, result.Requeue) 1475 assert.GreaterOrEqual(result.RequeueAfter.Milliseconds(), time.Duration(0).Milliseconds()) 1476 } 1477 1478 // TestFailureToUpdateStatus tests the Reconcile method for the following use case 1479 // GIVEN a request to reconcile an ingress trait resource 1480 // WHEN the request to update the trait status fails 1481 // THEN ensure an error is returned 1482 func TestFailureToUpdateStatus(t *testing.T) { 1483 assert := asserts.New(t) 1484 mocker := gomock.NewController(t) 1485 mock := mocks.NewMockClient(mocker) 1486 mockStatus := mocks.NewMockStatusWriter(mocker) 1487 1488 getIngressTraitResourceExpectations(mock, assert) 1489 workLoadResourceExpectations(mock) 1490 workloadResourceDefinitionExpectations(mock) 1491 listChildDeploymentExpectations(mock, assert) 1492 childServiceExpectations(mock, assert) 1493 appCertificateExpectations(mock) 1494 1495 // Expect a call to get the app config and return that it is not found. 1496 mock.EXPECT(). 1497 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 1498 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 1499 app.TypeMeta = metav1.TypeMeta{ 1500 APIVersion: "core.oam.dev/v1alpha2", 1501 Kind: "ApplicationConfiguration", 1502 } 1503 return nil 1504 }) 1505 1506 deleteCertExpectations(mock, "test-space-myapp-cert") 1507 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 1508 createCertSuccessExpectations(mock) 1509 getGatewayForTraitNotFoundExpectations(mock) 1510 createIngressResourceSuccessExpectations(mock) 1511 // Expect a call to get the gateway resource related to the ingress trait and return that it is not found. 1512 mock.EXPECT(). 1513 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: expectedTraitVSName}, gomock.Not(gomock.Nil()), gomock.Any()). 1514 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "Virtualservice"}, expectedTraitVSName)) 1515 createVSSuccessExpectations(mock) 1516 1517 // Expect a call to get the status writer and return a mock. 1518 mock.EXPECT().Status().Return(mockStatus) 1519 // Expect a call to update the status of the ingress trait. 1520 mockStatus.EXPECT(). 1521 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1522 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 1523 return k8serrors.NewApplyConflict([]metav1.StatusCause{{Type: "test-cause-type", Message: "test-cause-message", Field: "test-cause-field"}}, "test-error-message") 1524 }) 1525 1526 // Create and make the request 1527 request := newRequest(testNamespace, testTraitName) 1528 reconciler := newIngressTraitReconciler(mock) 1529 result, err := reconciler.Reconcile(context.TODO(), request) 1530 1531 // Validate the results 1532 mocker.Finish() 1533 assert.Nil(err) 1534 assert.Equal(true, result.Requeue) 1535 assert.GreaterOrEqual(result.RequeueAfter.Milliseconds(), time.Duration(0).Milliseconds()) 1536 } 1537 1538 // TestBuildAppHostNameForDNS tests building a DNS hostname for the application 1539 // GIVEN an appName and a trait 1540 // WHEN the ingress domain is not nip.io 1541 // THEN ensure that the correct DNS name is built 1542 func TestBuildAppHostNameForDNS(t *testing.T) { 1543 1544 assert := asserts.New(t) 1545 mocker := gomock.NewController(t) 1546 mock := mocks.NewMockClient(mocker) 1547 1548 ns := "myns" 1549 trait := vzapi.IngressTrait{ 1550 TypeMeta: metav1.TypeMeta{ 1551 APIVersion: apiVersion, 1552 Kind: traitKind, 1553 }, 1554 ObjectMeta: metav1.ObjectMeta{ 1555 Namespace: ns, 1556 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1557 }, 1558 } 1559 // Expect a call to get the Verrazzano ingress and return the ingress. 1560 mock.EXPECT(). 1561 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1562 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1563 ingress.TypeMeta = metav1.TypeMeta{ 1564 APIVersion: "networking.k8s.io/v1", 1565 Kind: "ingress"} 1566 ingress.ObjectMeta = metav1.ObjectMeta{ 1567 Namespace: name.Namespace, 1568 Name: name.Name, 1569 Annotations: map[string]string{"external-dns.alpha.kubernetes.io/target": "verrazzano-ingress.my.host.com"}} 1570 return nil 1571 }) 1572 1573 // Build the host name 1574 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 1575 1576 // Validate the results 1577 mocker.Finish() 1578 assert.NoError(err) 1579 assert.Equal("myapp.myns.my.host.com", domainName) 1580 } 1581 1582 // TestBuildAppHostNameIgnoreWildcardForDNS tests building a DNS hostname for the application 1583 // GIVEN an appName and a trait with wildcard hostnames and empty hostnames 1584 // WHEN the buildAppFullyQualifiedHostName function is called 1585 // THEN ensure that the correct DNS name is built and that the wildcard and empty names are ignored 1586 func TestBuildAppHostNameIgnoreWildcardForDNS(t *testing.T) { 1587 1588 assert := asserts.New(t) 1589 mocker := gomock.NewController(t) 1590 mock := mocks.NewMockClient(mocker) 1591 1592 ns := "myns" 1593 trait := vzapi.IngressTrait{ 1594 TypeMeta: metav1.TypeMeta{ 1595 APIVersion: apiVersion, 1596 Kind: traitKind, 1597 }, 1598 ObjectMeta: metav1.ObjectMeta{ 1599 Namespace: ns, 1600 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1601 }, 1602 Spec: vzapi.IngressTraitSpec{ 1603 Rules: []vzapi.IngressRule{{ 1604 Hosts: []string{"*name", "nam*e", "name*", "*", ""}, 1605 }}, 1606 }, 1607 } 1608 1609 // Expect a call to get the Verrazzano ingress and return the ingress. 1610 mock.EXPECT(). 1611 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1612 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1613 ingress.TypeMeta = metav1.TypeMeta{ 1614 APIVersion: "networking.k8s.io/v1", 1615 Kind: "ingress"} 1616 ingress.ObjectMeta = metav1.ObjectMeta{ 1617 Namespace: name.Namespace, 1618 Name: name.Name, 1619 Annotations: map[string]string{"external-dns.alpha.kubernetes.io/target": "verrazzano-ingress.my.host.com"}} 1620 return nil 1621 }) 1622 1623 // Build the host name 1624 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 1625 1626 // Validate the results 1627 mocker.Finish() 1628 assert.NoError(err) 1629 assert.Equal("myapp.myns.my.host.com", domainName) 1630 } 1631 1632 // TestFailureBuildAppHostNameForDNS tests failure of building a DNS hostname for the application 1633 // GIVEN an appName and a trait 1634 // WHEN the ingress domain is not nip.io and the Verrazzano annotation is missing 1635 // THEN ensure that an error is returned 1636 func TestFailureBuildAppHostNameForDNS(t *testing.T) { 1637 1638 assert := asserts.New(t) 1639 mocker := gomock.NewController(t) 1640 mock := mocks.NewMockClient(mocker) 1641 1642 ns := "myns" 1643 trait := vzapi.IngressTrait{ 1644 TypeMeta: metav1.TypeMeta{ 1645 APIVersion: apiVersion, 1646 Kind: traitKind, 1647 }, 1648 ObjectMeta: metav1.ObjectMeta{ 1649 Namespace: ns, 1650 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1651 }, 1652 } 1653 // Expect a call to get the Verrazzano ingress and return the ingress. 1654 mock.EXPECT(). 1655 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1656 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1657 ingress.TypeMeta = metav1.TypeMeta{ 1658 APIVersion: "networking.k8s.io/v1", 1659 Kind: "ingress"} 1660 ingress.ObjectMeta = metav1.ObjectMeta{ 1661 Namespace: name.Namespace, 1662 Name: name.Name} 1663 return nil 1664 }) 1665 1666 // Build the host name 1667 _, err := buildAppFullyQualifiedHostName(mock, &trait) 1668 1669 // Validate the results 1670 mocker.Finish() 1671 assert.Error(err) 1672 assert.Contains(err.Error(), "Annotation external-dns.alpha.kubernetes.io/target missing from Verrazzano ingress") 1673 } 1674 1675 // TestBuildAppHostNameLoadBalancerNIP tests building a hostname for the application 1676 // GIVEN an appName and a trait 1677 // WHEN the ingress domain is nip.io and LoadBalancer is used 1678 // THEN ensure that the correct DNS name is built 1679 func TestBuildAppHostNameLoadBalancerNIP(t *testing.T) { 1680 1681 assert := asserts.New(t) 1682 mocker := gomock.NewController(t) 1683 mock := mocks.NewMockClient(mocker) 1684 1685 ns := "myns" 1686 trait := vzapi.IngressTrait{ 1687 TypeMeta: metav1.TypeMeta{ 1688 APIVersion: apiVersion, 1689 }, 1690 ObjectMeta: metav1.ObjectMeta{ 1691 Namespace: ns, 1692 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1693 }, 1694 } 1695 // Expect a call to get the Verrazzano ingress and return the ingress. 1696 mock.EXPECT(). 1697 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1698 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1699 ingress.TypeMeta = metav1.TypeMeta{ 1700 APIVersion: "networking.k8s.io/v1", 1701 Kind: "ingress"} 1702 ingress.ObjectMeta = metav1.ObjectMeta{ 1703 Namespace: name.Namespace, 1704 Name: name.Name, 1705 Annotations: map[string]string{ 1706 "external-dns.alpha.kubernetes.io/target": testLoadBalancerExternalDNSTarget, 1707 "verrazzano.io/dns.wildcard.domain": "nip.io", 1708 }, 1709 } 1710 return nil 1711 }) 1712 1713 // Expect a call to get the Istio service 1714 mock.EXPECT(). 1715 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 1716 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 1717 service.TypeMeta = metav1.TypeMeta{ 1718 APIVersion: "networking.k8s.io/v1"} 1719 service.Spec.Type = "LoadBalancer" 1720 service.Status.LoadBalancer.Ingress = []k8score.LoadBalancerIngress{{ 1721 IP: testLoadBalancerIP, 1722 }} 1723 return nil 1724 }) 1725 1726 // Build the host name 1727 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 1728 1729 // Validate the results 1730 mocker.Finish() 1731 assert.NoError(err) 1732 assert.Equal(testLoadBalancerDomainName, domainName) 1733 } 1734 1735 // TestBuildAppHostNameExternalLoadBalancerNIP tests building a hostname for the application 1736 // GIVEN an appName and a trait 1737 // WHEN the ingress domain is nip.io and an external LoadBalancer is used 1738 // THEN ensure that the correct DNS name is built 1739 func TestBuildAppHostNameExternalLoadBalancerNIP(t *testing.T) { 1740 1741 assert := asserts.New(t) 1742 mocker := gomock.NewController(t) 1743 mock := mocks.NewMockClient(mocker) 1744 1745 ns := "myns" 1746 trait := vzapi.IngressTrait{ 1747 TypeMeta: metav1.TypeMeta{ 1748 APIVersion: apiVersion, 1749 }, 1750 ObjectMeta: metav1.ObjectMeta{ 1751 Namespace: ns, 1752 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1753 }, 1754 } 1755 // Expect a call to get the Verrazzano ingress and return the ingress. 1756 mock.EXPECT(). 1757 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1758 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1759 ingress.TypeMeta = metav1.TypeMeta{ 1760 APIVersion: "extensions/v1beta1", 1761 Kind: "ingress"} 1762 ingress.ObjectMeta = metav1.ObjectMeta{ 1763 Namespace: name.Namespace, 1764 Name: name.Name, 1765 Annotations: map[string]string{ 1766 "external-dns.alpha.kubernetes.io/target": testLoadBalancerExternalDNSTarget, 1767 "verrazzano.io/dns.wildcard.domain": "nip.io", 1768 }, 1769 } 1770 return nil 1771 }) 1772 1773 // Expect a call to get the Istio service 1774 mock.EXPECT(). 1775 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 1776 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 1777 service.TypeMeta = metav1.TypeMeta{ 1778 APIVersion: "extensions/v1beta1"} 1779 service.Spec.Type = "LoadBalancer" 1780 service.Spec.ExternalIPs = []string{testLoadBalancerIP} 1781 return nil 1782 }) 1783 1784 // Build the host name 1785 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 1786 1787 // Validate the results 1788 mocker.Finish() 1789 assert.NoError(err) 1790 assert.Equal(testLoadBalancerDomainName, domainName) 1791 } 1792 1793 // TestBuildAppHostNameBothInternalAndExternalLoadBalancerNIP tests building a hostname for the application 1794 // GIVEN an appName and a trait 1795 // WHEN the ingress domain is nip.io and an external LoadBalancer is used and LoadBalancer ise also used in Istio Ingress 1796 // THEN ensure that the correct DNS name is built 1797 func TestBuildAppHostNameBothInternalAndExternalLoadBalancerNIP(t *testing.T) { 1798 1799 assert := asserts.New(t) 1800 mocker := gomock.NewController(t) 1801 mock := mocks.NewMockClient(mocker) 1802 1803 ns := "myns" 1804 trait := vzapi.IngressTrait{ 1805 TypeMeta: metav1.TypeMeta{ 1806 APIVersion: apiVersion, 1807 }, 1808 ObjectMeta: metav1.ObjectMeta{ 1809 Namespace: ns, 1810 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1811 }, 1812 } 1813 // Expect a call to get the Verrazzano ingress and return the ingress. 1814 mock.EXPECT(). 1815 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1816 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1817 ingress.TypeMeta = metav1.TypeMeta{ 1818 APIVersion: "extensions/v1beta1", 1819 Kind: "ingress"} 1820 ingress.ObjectMeta = metav1.ObjectMeta{ 1821 Namespace: name.Namespace, 1822 Name: name.Name, 1823 Annotations: map[string]string{ 1824 "external-dns.alpha.kubernetes.io/target": testLoadBalancerExternalDNSTarget, 1825 "verrazzano.io/dns.wildcard.domain": "nip.io", 1826 }, 1827 } 1828 return nil 1829 }) 1830 1831 // Expect a call to get the Istio service 1832 mock.EXPECT(). 1833 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 1834 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 1835 service.TypeMeta = metav1.TypeMeta{ 1836 APIVersion: "extensions/v1beta1"} 1837 service.Spec.Type = "LoadBalancer" 1838 service.Spec.ExternalIPs = []string{testExternalIP} 1839 service.Status.LoadBalancer.Ingress = []k8score.LoadBalancerIngress{{ 1840 IP: testLoadBalancerIP, 1841 }} 1842 return nil 1843 }) 1844 1845 // Build the host name 1846 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 1847 1848 // Validate the results 1849 mocker.Finish() 1850 assert.NoError(err) 1851 assert.Equal(testExternalDomainName, domainName) 1852 } 1853 1854 // TestBuildAppHostNameExternalLoadBalancerNIPNotFound tests building a hostname for the application 1855 // GIVEN an appName and a trait 1856 // WHEN the ingress domain is nip.io and an external LoadBalancer is used, but no IP is found 1857 // THEN ensure that an error is returned 1858 func TestBuildAppHostNameExternalLoadBalancerNIPNotFound(t *testing.T) { 1859 1860 assert := asserts.New(t) 1861 mocker := gomock.NewController(t) 1862 mock := mocks.NewMockClient(mocker) 1863 1864 ns := "myns" 1865 trait := vzapi.IngressTrait{ 1866 TypeMeta: metav1.TypeMeta{ 1867 APIVersion: apiVersion, 1868 }, 1869 ObjectMeta: metav1.ObjectMeta{ 1870 Namespace: ns, 1871 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1872 }, 1873 } 1874 // Expect a call to get the Verrazzano ingress and return the ingress. 1875 mock.EXPECT(). 1876 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1877 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1878 ingress.TypeMeta = metav1.TypeMeta{ 1879 APIVersion: "extensions/v1beta1", 1880 Kind: "ingress"} 1881 ingress.ObjectMeta = metav1.ObjectMeta{ 1882 Namespace: name.Namespace, 1883 Name: name.Name, 1884 Annotations: map[string]string{ 1885 "external-dns.alpha.kubernetes.io/target": testLoadBalancerExternalDNSTarget, 1886 "verrazzano.io/dns.wildcard.domain": "nip.io", 1887 }, 1888 } 1889 return nil 1890 }) 1891 1892 // Expect a call to get the Istio service 1893 mock.EXPECT(). 1894 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 1895 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 1896 service.TypeMeta = metav1.TypeMeta{ 1897 APIVersion: "extensions/v1beta1"} 1898 service.Spec.Type = "LoadBalancer" 1899 return nil 1900 }) 1901 1902 // Build the host name 1903 _, err := buildAppFullyQualifiedHostName(mock, &trait) 1904 1905 // Validate the results 1906 mocker.Finish() 1907 assert.Error(err) 1908 } 1909 1910 // TestFailureBuildAppHostNameLoadBalancerNIP tests a failure when building a hostname for the application 1911 // GIVEN an appName and a trait 1912 // WHEN the ingress domain is nip.io and LoadBalancer is used, but an error occurs 1913 // THEN ensure that the correct error is returned 1914 func TestFailureBuildAppHostNameLoadBalancerNIP(t *testing.T) { 1915 1916 assert := asserts.New(t) 1917 mocker := gomock.NewController(t) 1918 mock := mocks.NewMockClient(mocker) 1919 1920 ns := "myns" 1921 trait := vzapi.IngressTrait{ 1922 TypeMeta: metav1.TypeMeta{ 1923 APIVersion: apiVersion, 1924 }, 1925 ObjectMeta: metav1.ObjectMeta{ 1926 Namespace: ns, 1927 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1928 }, 1929 } 1930 // Expect a call to get the Verrazzano ingress and return the ingress. 1931 mock.EXPECT(). 1932 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1933 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1934 ingress.TypeMeta = metav1.TypeMeta{ 1935 APIVersion: "networking.k8s.io/v1", 1936 Kind: "ingress"} 1937 ingress.ObjectMeta = metav1.ObjectMeta{ 1938 Namespace: name.Namespace, 1939 Name: name.Name, 1940 Annotations: map[string]string{ 1941 "external-dns.alpha.kubernetes.io/target": testLoadBalancerExternalDNSTarget, 1942 "verrazzano.io/dns.wildcard.domain": "nip.io", 1943 }, 1944 } 1945 return nil 1946 }) 1947 1948 // Expect a call to get the Istio service 1949 mock.EXPECT(). 1950 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 1951 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 1952 service.TypeMeta = metav1.TypeMeta{ 1953 APIVersion: "networking.k8s.io/v1"} 1954 service.Spec.Type = "LoadBalancer" 1955 return nil 1956 }) 1957 1958 // Build the host name 1959 _, err := buildAppFullyQualifiedHostName(mock, &trait) 1960 1961 // Validate the results 1962 mocker.Finish() 1963 assert.Error(err) 1964 assert.Equal("istio-ingressgateway is missing loadbalancer IP", err.Error()) 1965 } 1966 1967 // TestBuildAppHostNameNodePortExternalIP tests building a hostname for the application 1968 // GIVEN an appName and a trait 1969 // WHEN the ingress domain is nip.io and NodePort is used together with ExternalIPs 1970 // THEN ensure that the correct DNS name is built 1971 func TestBuildAppHostNameNodePortExternalIP(t *testing.T) { 1972 1973 assert := asserts.New(t) 1974 mocker := gomock.NewController(t) 1975 mock := mocks.NewMockClient(mocker) 1976 1977 ns := "myns" 1978 trait := vzapi.IngressTrait{ 1979 TypeMeta: metav1.TypeMeta{ 1980 APIVersion: apiVersion, 1981 }, 1982 ObjectMeta: metav1.ObjectMeta{ 1983 Namespace: ns, 1984 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 1985 }, 1986 } 1987 // Expect a call to get the Verrazzano ingress and return the ingress. 1988 mock.EXPECT(). 1989 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 1990 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 1991 ingress.TypeMeta = metav1.TypeMeta{ 1992 APIVersion: "networking.k8s.io/v1", 1993 Kind: "ingress"} 1994 ingress.ObjectMeta = metav1.ObjectMeta{ 1995 Namespace: name.Namespace, 1996 Name: name.Name, 1997 Annotations: map[string]string{ 1998 "external-dns.alpha.kubernetes.io/target": "verrazzano-ingress" + testExternalIP + ".nip.io", 1999 "verrazzano.io/dns.wildcard.domain": "nip.io", 2000 }, 2001 } 2002 return nil 2003 }) 2004 2005 // Expect a call to get the Istio service 2006 mock.EXPECT(). 2007 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: istioIngressGatewayName}, gomock.Not(gomock.Nil()), gomock.Any()). 2008 DoAndReturn(func(ctx context.Context, name types.NamespacedName, service *k8score.Service, opts ...client.GetOption) error { 2009 service.TypeMeta = metav1.TypeMeta{ 2010 APIVersion: "networking.k8s.io/v1"} 2011 service.Spec.Type = "NodePort" 2012 service.Spec.ExternalIPs = []string{testExternalIP} 2013 return nil 2014 }) 2015 2016 // Build the host name 2017 domainName, err := buildAppFullyQualifiedHostName(mock, &trait) 2018 2019 // Validate the results 2020 mocker.Finish() 2021 assert.NoError(err) 2022 assert.Equal(testExternalDomainName, domainName) 2023 } 2024 2025 // TestGetTraitFailurePropagated tests the Reconcile method for the following use case 2026 // GIVEN a request to reconcile an ingress trait resource 2027 // WHEN a failure occurs getting the ingress trait resource 2028 // THEN the error is propagated 2029 func TestGetTraitFailurePropagated(t *testing.T) { 2030 2031 assert := asserts.New(t) 2032 mocker := gomock.NewController(t) 2033 mock := mocks.NewMockClient(mocker) 2034 mock.EXPECT(). 2035 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testName}, gomock.Any(), gomock.Any()). 2036 Return(fmt.Errorf("test-error")). 2037 AnyTimes() 2038 reconciler := newIngressTraitReconciler(mock) 2039 request := newRequest(testNamespace, testName) 2040 result, err := reconciler.Reconcile(context.TODO(), request) 2041 2042 mocker.Finish() 2043 assert.Nil(err) 2044 assert.Equal(true, result.Requeue) 2045 assert.GreaterOrEqual(result.RequeueAfter.Milliseconds(), time.Duration(0).Milliseconds()) 2046 } 2047 2048 // TestGetNotFoundResource tests the Reconcile method for the following use case. 2049 // GIVEN a request to reconcile an ingress trait resource 2050 // WHEN a failure occurs indicating the resource is not found 2051 // THEN the error is propagated 2052 func TestGetNotFoundResource(t *testing.T) { 2053 2054 assert := asserts.New(t) 2055 mocker := gomock.NewController(t) 2056 mock := mocks.NewMockClient(mocker) 2057 mock.EXPECT(). 2058 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testName}, gomock.Any(), gomock.Any()). 2059 Return(k8serrors.NewNotFound(schema.GroupResource{Group: "oam.verrazzano.io", Resource: traitKind}, testName)) 2060 reconciler := newIngressTraitReconciler(mock) 2061 request := newRequest(testNamespace, testName) 2062 result, err := reconciler.Reconcile(context.TODO(), request) 2063 mocker.Finish() 2064 assert.NoError(err) 2065 assert.Equal(false, result.Requeue) 2066 assert.Equal(time.Duration(0), result.RequeueAfter) 2067 } 2068 2069 // GIVEN a CRs APIVersion and Kind 2070 // WHEN converting to the related CRD namespaced name 2071 // THEN ensure the conversion is correct 2072 func TestConvertCRAPIVersionAndKindToCRDNamespacedName(t *testing.T) { 2073 2074 assert := asserts.New(t) 2075 actual := convertAPIVersionAndKindToNamespacedName("core.oam.dev/v1alpha2", "ContainerizedWorkload") 2076 expect := types.NamespacedName{Namespace: "", Name: "containerizedworkloads.core.oam.dev"} 2077 assert.Equal(expect, actual) 2078 } 2079 2080 // TestCreateVirtualServiceMatchUriFromIngressTraitPath tests various use cases of createVirtualServiceMatchURIFromIngressTraitPath 2081 func TestCreateVirtualServiceMatchUriFromIngressTraitPath(t *testing.T) { 2082 2083 assert := asserts.New(t) 2084 var path vzapi.IngressPath 2085 var match *istionet.StringMatch 2086 2087 // GIVEN an ingress path with normal path and type 2088 // WHEN a virtual service match uri is created from the ingress path 2089 // THEN verify the path and type were used correctly 2090 path = vzapi.IngressPath{Path: "/path", PathType: "exact"} 2091 match = createVirtualServiceMatchURIFromIngressTraitPath(path) 2092 assert.IsType(&istionet.StringMatch_Exact{}, match.MatchType) 2093 assert.Equal("/path", match.MatchType.(*istionet.StringMatch_Exact).Exact) 2094 2095 // GIVEN an ingress path with path and type with whitespace and upper case 2096 // WHEN a virtual service match uri is created from the ingress path 2097 // THEN verify the path and type were updated correctly 2098 path = vzapi.IngressPath{Path: " /path ", PathType: " PREFIX "} 2099 match = createVirtualServiceMatchURIFromIngressTraitPath(path) 2100 assert.IsType(&istionet.StringMatch_Prefix{}, match.MatchType) 2101 assert.Equal("/path", match.MatchType.(*istionet.StringMatch_Prefix).Prefix) 2102 2103 // GIVEN an ingress path with no path or type 2104 // WHEN a virtual service match uri is created from the ingress path 2105 // THEN verify the path and type were defaulted correctly 2106 path = vzapi.IngressPath{} 2107 match = createVirtualServiceMatchURIFromIngressTraitPath(path) 2108 assert.IsType(&istionet.StringMatch_Prefix{}, match.MatchType) 2109 assert.Equal("/", match.MatchType.(*istionet.StringMatch_Prefix).Prefix) 2110 2111 // GIVEN an ingress path with only a path / and no type 2112 // WHEN a virtual service match uri is created from the ingress path 2113 // THEN verify the type were defaulted correctly to prefix 2114 path = vzapi.IngressPath{Path: "/"} 2115 match = createVirtualServiceMatchURIFromIngressTraitPath(path) 2116 assert.IsType(&istionet.StringMatch_Prefix{}, match.MatchType) 2117 assert.Equal("/", match.MatchType.(*istionet.StringMatch_Prefix).Prefix) 2118 2119 // GIVEN an ingress path with only a path and no type 2120 // WHEN a virtual service match uri is created from the ingress path 2121 // THEN verify the type were defaulted correctly to exact 2122 path = vzapi.IngressPath{Path: "/path"} 2123 match = createVirtualServiceMatchURIFromIngressTraitPath(path) 2124 assert.IsType(&istionet.StringMatch_Exact{}, match.MatchType) 2125 assert.Equal("/path", match.MatchType.(*istionet.StringMatch_Exact).Exact) 2126 } 2127 2128 // TestCreateHostsFromIngressTraitRule tests generation of a default host name 2129 // GIVEN a trait rule with only wildcard hosts and an empty host 2130 // WHEN a host slice DNS domain exists in the ingress 2131 // THEN verify that only the default host is used 2132 func TestCreateHostsFromIngressTraitRuleWildcards(t *testing.T) { 2133 2134 assert := asserts.New(t) 2135 mocker := gomock.NewController(t) 2136 mock := mocks.NewMockClient(mocker) 2137 2138 ns := "myns" 2139 trait := vzapi.IngressTrait{ 2140 TypeMeta: metav1.TypeMeta{ 2141 APIVersion: apiVersion, 2142 Kind: traitKind, 2143 }, 2144 ObjectMeta: metav1.ObjectMeta{ 2145 Namespace: ns, 2146 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}, 2147 }, 2148 Spec: vzapi.IngressTraitSpec{ 2149 Rules: []vzapi.IngressRule{{ 2150 Hosts: []string{"*name", "nam*e", "name*", "*", ""}, 2151 }}, 2152 }, 2153 } 2154 2155 // Expect a call to get the Verrazzano ingress and return the ingress. 2156 mock.EXPECT(). 2157 Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoSystemNamespace, Name: constants.VzConsoleIngress}, gomock.Not(gomock.Nil()), gomock.Any()). 2158 DoAndReturn(func(ctx context.Context, name types.NamespacedName, ingress *k8net.Ingress, opts ...client.GetOption) error { 2159 ingress.TypeMeta = metav1.TypeMeta{ 2160 APIVersion: "networking.k8s.io/v1", 2161 Kind: "ingress"} 2162 ingress.ObjectMeta = metav1.ObjectMeta{ 2163 Namespace: name.Namespace, 2164 Name: name.Name, 2165 Annotations: map[string]string{"external-dns.alpha.kubernetes.io/target": "verrazzano-ingress.my.host.com"}} 2166 return nil 2167 }) 2168 2169 rule := vzapi.IngressRule{Hosts: []string{"*", "", "*host", "host*", "ho*st"}} 2170 hosts, err := createHostsFromIngressTraitRule(mock, rule, &trait) 2171 2172 mocker.Finish() 2173 assert.NoError(err) 2174 assert.Len(hosts, 1) 2175 assert.Equal("myapp.myns.my.host.com", hosts[0]) 2176 } 2177 2178 // TestIngressTraitHostsForVirtualServiceAndGateway tests the host in a Gateways and VirtualServices 2179 // GIVEN a list of IngressTraits 2180 // WHEN createOrUpdateChildResources is called 2181 // THEN verify that the correct Gateways and VirtualServices are created 2182 // 2183 // AND that the Gateway and VirtualService hosts are correct 2184 func TestIngressTraitHostsForVirtualServiceAndGateway(t *testing.T) { 2185 assert := asserts.New(t) 2186 const appName = "hello" 2187 2188 tests := []struct { 2189 name string 2190 traits []*vzapi.IngressTrait 2191 }{ 2192 {name: "1trait-1rule-1host", 2193 traits: []*vzapi.IngressTrait{{ 2194 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: testNamespace, 2195 Labels: map[string]string{oam.LabelAppName: appName}, 2196 }, 2197 Spec: vzapi.IngressTraitSpec{ 2198 Rules: []vzapi.IngressRule{ 2199 {Hosts: []string{"host1"}}, 2200 }, 2201 }}, 2202 }}, 2203 {name: "1trait-1rule-2host", 2204 traits: []*vzapi.IngressTrait{{ 2205 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: testNamespace, 2206 Labels: map[string]string{oam.LabelAppName: appName}, 2207 }, 2208 Spec: vzapi.IngressTraitSpec{ 2209 Rules: []vzapi.IngressRule{ 2210 {Hosts: []string{"host1"}}, 2211 {Hosts: []string{"host2"}}, 2212 }, 2213 }}, 2214 }}, 2215 {name: "1trait-2rule-3host-same-names", 2216 traits: []*vzapi.IngressTrait{{ 2217 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: testNamespace, 2218 Labels: map[string]string{oam.LabelAppName: appName}, 2219 }, 2220 Spec: vzapi.IngressTraitSpec{ 2221 Rules: []vzapi.IngressRule{ 2222 {Hosts: []string{"host1", "host2", "host3"}}, 2223 {Hosts: []string{"host1", "host2", "host3"}}, 2224 }, 2225 }}, 2226 }}, 2227 {name: "2traits-1rule-each", 2228 traits: []*vzapi.IngressTrait{{ 2229 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: testNamespace, 2230 Labels: map[string]string{oam.LabelAppName: appName}, 2231 }, 2232 Spec: vzapi.IngressTraitSpec{ 2233 Rules: []vzapi.IngressRule{ 2234 {Hosts: []string{"host1", "host2", "host3"}}, 2235 }, 2236 }}, 2237 {ObjectMeta: metav1.ObjectMeta{Name: "hello-2", Namespace: testNamespace, 2238 Labels: map[string]string{oam.LabelAppName: appName}, 2239 }, 2240 Spec: vzapi.IngressTraitSpec{ 2241 Rules: []vzapi.IngressRule{ 2242 {Hosts: []string{"host1-a", "host2-a", "host3-a"}}, 2243 }, 2244 }}, 2245 }}, 2246 {name: "3traits-multiple-rules-and-hosts", 2247 traits: []*vzapi.IngressTrait{{ 2248 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: testNamespace, 2249 Labels: map[string]string{oam.LabelAppName: appName}, 2250 }, 2251 Spec: vzapi.IngressTraitSpec{ 2252 Rules: []vzapi.IngressRule{ 2253 {Hosts: []string{"host1", "host2", "host3"}}, 2254 }, 2255 }}, 2256 {ObjectMeta: metav1.ObjectMeta{Name: "hello-2", Namespace: testNamespace, 2257 Labels: map[string]string{oam.LabelAppName: appName}, 2258 }, 2259 Spec: vzapi.IngressTraitSpec{ 2260 Rules: []vzapi.IngressRule{ 2261 {Hosts: []string{"host1-a", "host2-a", "host3-a"}}, 2262 {Hosts: []string{"host4-a", "host5-a", "host6-a"}}, 2263 }, 2264 }}, 2265 {ObjectMeta: metav1.ObjectMeta{Name: "hello-3", Namespace: testNamespace, 2266 Labels: map[string]string{oam.LabelAppName: appName}, 2267 }, 2268 Spec: vzapi.IngressTraitSpec{ 2269 Rules: []vzapi.IngressRule{ 2270 {Hosts: []string{"host1-b"}}, 2271 {Hosts: []string{"host2-b", "host3-b"}}, 2272 {Hosts: []string{"host4-b", "host5-b", "host6-b"}}, 2273 }, 2274 }}, 2275 }}, 2276 } 2277 for _, test := range tests { 2278 t.Run(test.name, func(t *testing.T) { 2279 // Create the fake reconciler up front with all traits 2280 cli := fake.NewClientBuilder().WithScheme(newScheme()) 2281 cli = cli.WithObjects(getAppFakeResources(setupAppFakes(appName))...) 2282 2283 // Finish configuration traits 2284 for _, trait := range test.traits { 2285 trait.TypeMeta = metav1.TypeMeta{ 2286 APIVersion: apiVersion, 2287 Kind: "IngressTrait", 2288 } 2289 trait.Spec.WorkloadReference = oamrt.TypedReference{ 2290 APIVersion: "core.oam.dev/v1alpha2", 2291 Kind: "ContainerizedWorkload", 2292 Name: testWorkloadName} 2293 cli = cli.WithObjects(trait) 2294 } 2295 r := newIngressTraitReconciler(cli.Build()) 2296 2297 // Reconcile each trait 2298 for i, trait := range test.traits { 2299 _, _, err := r.createOrUpdateChildResources(context.TODO(), test.traits[i], vzlog.DefaultLogger()) 2300 assert.NoError(err) 2301 2302 // Every trait rule must have a VS with all the hosts. This test must use explicit hosts 2303 for i, rule := range trait.Spec.Rules { 2304 // The VS and the rule must have the same set of hosts 2305 vsName := fmt.Sprintf("%s-rule-%d-vs", trait.Name, i) 2306 vs := istioclient.VirtualService{} 2307 err = r.Get(context.Background(), client.ObjectKey{Namespace: trait.Namespace, Name: vsName}, &vs) 2308 assert.NoError(err) 2309 hostSet := vzstring.SliceToSet(rule.Hosts) 2310 for _, h := range vs.Spec.Hosts { 2311 _, ok := hostSet[h] 2312 if ok { 2313 delete(hostSet, h) 2314 } else { 2315 assert.Fail(fmt.Sprintf("unexpected host %s in VS %s", h, vs.Name)) 2316 } 2317 } 2318 assert.Len(hostSet, 0) 2319 } 2320 2321 // get the gateway 2322 gwName, _ := buildGatewayName(trait) 2323 gw := istioclient.Gateway{} 2324 err = r.Get(context.Background(), client.ObjectKey{Namespace: trait.Namespace, Name: gwName}, &gw) 2325 assert.NoError(err) 2326 2327 // There must be a gateway server for each trait. 2328 for _, server := range gw.Spec.Servers { 2329 if server.Name != trait.Name { 2330 continue 2331 } 2332 // The hosts in the gateway server must match the hosts in the trait 2333 traitHosts := getHostsInTrait(trait) 2334 hostSet := vzstring.SliceToSet(traitHosts) 2335 for _, h := range server.Hosts { 2336 _, ok := hostSet[h] 2337 if ok { 2338 delete(hostSet, h) 2339 } else { 2340 assert.Fail(fmt.Sprintf("unexpected host %s in server %s", h, server.Name)) 2341 } 2342 } 2343 assert.Len(hostSet, 0) 2344 } 2345 } 2346 }) 2347 } 2348 } 2349 2350 func getHostsInTrait(trait *vzapi.IngressTrait) []string { 2351 hosts := []string{} 2352 for _, rule := range trait.Spec.Rules { 2353 hosts = append(hosts, rule.Hosts...) 2354 } 2355 return hosts 2356 } 2357 2358 // TestHostRules tests host name rules 2359 // GIVEN a trait with a set of rules 2360 // WHEN coallateAllHostsForTrait is called 2361 // THEN verify that the correct set of host names are returned 2362 func TestHostRules(t *testing.T) { 2363 assert := asserts.New(t) 2364 const externalDNSKey = "external-dns.alpha.kubernetes.io/target" 2365 2366 tests := []struct { 2367 name string 2368 rules []vzapi.IngressRule 2369 expectedHosts []string 2370 }{ 2371 {name: "explicit-host-first", 2372 expectedHosts: []string{"host1", "hello.default.myhost.com"}, 2373 rules: []vzapi.IngressRule{ 2374 { 2375 Hosts: []string{"host1"}, 2376 Paths: nil, 2377 }, 2378 { 2379 Destination: vzapi.IngressDestination{}, 2380 Hosts: nil, 2381 Paths: nil, 2382 }, 2383 }}, 2384 {name: "default-host-first", 2385 expectedHosts: []string{"host1", "hello.default.myhost.com"}, 2386 rules: []vzapi.IngressRule{ 2387 { 2388 Destination: vzapi.IngressDestination{}, 2389 Hosts: nil, 2390 Paths: nil, 2391 }, 2392 { 2393 Hosts: []string{"host1"}, 2394 Paths: nil, 2395 }, 2396 }}, 2397 {name: "default-host-only", 2398 expectedHosts: []string{"hello.default.myhost.com"}, 2399 rules: []vzapi.IngressRule{ 2400 { 2401 Destination: vzapi.IngressDestination{}, 2402 Hosts: nil, 2403 Paths: nil, 2404 }, 2405 }}, 2406 {name: "two-default-hosts-only", 2407 expectedHosts: []string{"hello.default.myhost.com"}, 2408 rules: []vzapi.IngressRule{ 2409 { 2410 Destination: vzapi.IngressDestination{}, 2411 Hosts: nil, 2412 Paths: nil, 2413 }, 2414 { 2415 Destination: vzapi.IngressDestination{}, 2416 Hosts: nil, 2417 Paths: nil, 2418 }, 2419 }}, 2420 {name: "explicit-host-only", 2421 expectedHosts: []string{"host1"}, 2422 rules: []vzapi.IngressRule{ 2423 { 2424 Hosts: []string{"host1"}, 2425 Paths: nil, 2426 }, 2427 }}, 2428 {name: "two-explicit-hosts-only", 2429 expectedHosts: []string{"host1", "host2"}, 2430 rules: []vzapi.IngressRule{ 2431 { 2432 Hosts: []string{"host1"}, 2433 Paths: nil, 2434 }, 2435 { 2436 Hosts: []string{"host2"}, 2437 Paths: nil, 2438 }, 2439 }}, 2440 {name: "dup-explicit-host-only", 2441 expectedHosts: []string{"host1"}, 2442 rules: []vzapi.IngressRule{ 2443 { 2444 Hosts: []string{"host1"}, 2445 Paths: nil, 2446 }, 2447 { 2448 Hosts: []string{"host1"}, 2449 Paths: nil, 2450 }, 2451 }}, 2452 {name: "wildcard-use-default", 2453 expectedHosts: []string{"hello.default.myhost.com"}, 2454 rules: []vzapi.IngressRule{ 2455 { 2456 Hosts: []string{"*"}, 2457 Paths: nil, 2458 }, 2459 { 2460 Hosts: []string{"*/foo.com"}, 2461 Paths: nil, 2462 }, 2463 }}, 2464 {name: "wildcard-use-explicit", 2465 expectedHosts: []string{"host1", "hello.default.myhost.com"}, 2466 rules: []vzapi.IngressRule{ 2467 { 2468 Hosts: []string{"*"}, 2469 Paths: nil, 2470 }, 2471 { 2472 Hosts: []string{"*/foo.com"}, 2473 Paths: nil, 2474 }, 2475 { 2476 Hosts: []string{"host1"}, 2477 Paths: nil, 2478 }, 2479 }}, 2480 {name: "dup-explicit-and-default-hosts", 2481 expectedHosts: []string{"host1", "host2", "hello.default.myhost.com"}, 2482 rules: []vzapi.IngressRule{ 2483 { 2484 Hosts: []string{"host1"}, 2485 Paths: nil, 2486 }, 2487 { 2488 Destination: vzapi.IngressDestination{}, 2489 Hosts: nil, 2490 Paths: nil, 2491 }, 2492 { 2493 Hosts: []string{"host2"}, 2494 Paths: nil, 2495 }, 2496 { 2497 Hosts: []string{"host1"}, 2498 Paths: nil, 2499 }, 2500 { 2501 Destination: vzapi.IngressDestination{}, 2502 Hosts: nil, 2503 Paths: nil, 2504 }, 2505 }}, 2506 } 2507 for _, test := range tests { 2508 t.Run(test.name, func(t *testing.T) { 2509 trait := vzapi.IngressTrait{ 2510 ObjectMeta: metav1.ObjectMeta{ 2511 Name: "hello", 2512 Namespace: "default", 2513 Labels: map[string]string{oam.LabelAppName: "hello"}, 2514 }, 2515 Spec: vzapi.IngressTraitSpec{}, 2516 Status: vzapi.IngressTraitStatus{}, 2517 } 2518 trait.Spec.Rules = test.rules 2519 ingress := k8net.Ingress{ 2520 ObjectMeta: metav1.ObjectMeta{ 2521 Name: constants.VzConsoleIngress, 2522 Namespace: constants.VerrazzanoSystemNamespace, 2523 Annotations: map[string]string{ 2524 externalDNSKey: "verrazzano-ingress-myhost.com"}, 2525 }, 2526 } 2527 results := reconcileresults.ReconcileResults{} 2528 r := createReconcilerWithFake(&ingress) 2529 2530 hosts := r.coallateAllHostsForTrait(&trait, results) 2531 assert.Len(hosts, len(test.expectedHosts)) 2532 hostSet := vzstring.SliceToSet(hosts) 2533 for _, h := range hosts { 2534 _, ok := hostSet[h] 2535 if ok { 2536 delete(hostSet, h) 2537 } else { 2538 assert.Fail(fmt.Sprintf("unexpected host %s returned from coallateAllHostsForTrait", h)) 2539 } 2540 } 2541 assert.Len(hostSet, 0) 2542 }) 2543 } 2544 } 2545 2546 // TestCreateHostsFromIngressTraitRule tests various use cases of createHostsFromIngressTraitRule 2547 func TestCreateHostsFromIngressTraitRule(t *testing.T) { 2548 2549 assert := asserts.New(t) 2550 var rule vzapi.IngressRule 2551 var hosts []string 2552 2553 // GIVEN a trait rule with a valid hosts 2554 // WHEN a host slice is requested for use 2555 // THEN verify that valid hosts are used 2556 rule = vzapi.IngressRule{Hosts: []string{"host-1", "host-2"}} 2557 hosts, err := createHostsFromIngressTraitRule(nil, rule, nil) 2558 assert.NoError(err) 2559 assert.Len(hosts, 2) 2560 assert.Equal("host-1", hosts[0]) 2561 assert.Equal("host-2", hosts[1]) 2562 2563 // GIVEN a trait rule with a mix of hosts including an empty host and wildcard host 2564 // WHEN a host slice is requested for use 2565 // THEN verify that the empty host is ignored and the defaultHost is not used 2566 rule = vzapi.IngressRule{Hosts: []string{"host-1", "", "*", "host-2"}} 2567 hosts, err = createHostsFromIngressTraitRule(nil, rule, nil) 2568 assert.NoError(err) 2569 assert.Len(hosts, 2) 2570 assert.Equal("host-1", hosts[0]) 2571 assert.Equal("host-2", hosts[1]) 2572 } 2573 2574 // TestGetPathsFromTrait tests various use cases of getPathsFromRule 2575 func TestGetPathsFromTrait(t *testing.T) { 2576 assert := asserts.New(t) 2577 var rule vzapi.IngressRule 2578 var paths []vzapi.IngressPath 2579 2580 // GIVEN an ingress rule with no path or type 2581 // WHEN the paths are obtained from the rule 2582 // THEN verify that path and type are defaulted 2583 rule = vzapi.IngressRule{} 2584 paths = getPathsFromRule(rule) 2585 assert.Len(paths, 1) 2586 assert.Equal("/", paths[0].Path) 2587 assert.Equal("prefix", paths[0].PathType) 2588 2589 // GIVEN an ingress rule with valid path and type 2590 // WHEN the paths are obtained from the rule 2591 // THEN verify that path and type are the same. 2592 rule = vzapi.IngressRule{Paths: []vzapi.IngressPath{{ 2593 Path: "/test-path-name", 2594 PathType: "test-path-type", 2595 }}} 2596 paths = getPathsFromRule(rule) 2597 assert.Len(paths, 1) 2598 assert.Equal("/test-path-name", paths[0].Path) 2599 assert.Equal("test-path-type", paths[0].PathType) 2600 } 2601 2602 // TestCreateDestinationFromService test various use cases of createDestinationFromService 2603 func TestCreateDestinationFromService(t *testing.T) { 2604 2605 assert := asserts.New(t) 2606 var services []*k8score.Service 2607 var dest *istionet.HTTPRouteDestination 2608 2609 // GIVEN one service with no cluster-IP defined 2610 // WHEN a destination is created from the service 2611 // THEN verify that destination created successfully 2612 service1 := k8score.Service{ 2613 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}} 2614 services = append(services, &service1) 2615 dest, err := createDestinationFromService(services) 2616 assert.Equal("test-service-name", dest.Destination.Host) 2617 assert.Nil(dest.Destination.Port) 2618 assert.NoError(err) 2619 2620 // GIVEN a service with no ports defined 2621 // WHEN a destination is created from the service 2622 // THEN verify that the port is nil. 2623 service1 = k8score.Service{ 2624 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2625 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3"}} 2626 services[0] = &service1 2627 dest, err = createDestinationFromService(services) 2628 assert.Equal("test-service-name", dest.Destination.Host) 2629 assert.Nil(dest.Destination.Port) 2630 assert.NoError(err) 2631 2632 // GIVEN a service with a valid port defined 2633 // WHEN a destination is created from the service 2634 // THEN verify that the service's port is used. 2635 service1 = k8score.Service{ 2636 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2637 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", Ports: []k8score.ServicePort{{Port: 42}}}} 2638 services[0] = &service1 2639 dest, err = createDestinationFromService(services) 2640 assert.Equal(uint32(42), dest.Destination.Port.Number) 2641 assert.NoError(err) 2642 2643 // GIVEN a service with multiple valid ports defined 2644 // WHEN a destination is created from the service 2645 // THEN verify that the service's port with name having "http" prefix is used. 2646 service1 = k8score.Service{ 2647 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2648 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2649 Ports: []k8score.ServicePort{{Port: 42}, {Port: 777, Name: "http"}}}} 2650 services[0] = &service1 2651 dest, err = createDestinationFromService(services) 2652 assert.Equal("test-service-name", dest.Destination.Host) 2653 assert.Equal(uint32(777), dest.Destination.Port.Number) 2654 assert.NoError(err) 2655 2656 // GIVEN a service with multiple valid ports defined and none of them named with "http" prefix 2657 // WHEN a destination is created from the service 2658 // THEN verify that an error is returned. 2659 service1 = k8score.Service{ 2660 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2661 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2662 Ports: []k8score.ServicePort{{Port: 42}, {Port: 777}}}} 2663 services[0] = &service1 2664 dest, err = createDestinationFromService(services) 2665 assert.Nil(dest, "No destination should have been created") 2666 assert.Error(err, "An error should have been returned") 2667 2668 // GIVEN a service with multiple valid ports defined and many of them named with "http" prefix 2669 // WHEN a destination is created from the service 2670 // THEN verify that an error is returned. 2671 service1 = k8score.Service{ 2672 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2673 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2674 Ports: []k8score.ServicePort{{Port: 42, Name: "http-1"}, {Port: 777, Name: "http"}}}} 2675 services[0] = &service1 2676 dest, err = createDestinationFromService(services) 2677 assert.Nil(dest, "No destination should have been created") 2678 assert.Error(err, "An error should have been returned") 2679 2680 // GIVEN multiple services and one of them having a port name with the prefix "http" 2681 // WHEN a destination is created from the service 2682 // THEN verify that destination created successfully using the service with the prefix "http" 2683 service1 = k8score.Service{ 2684 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2685 Spec: k8score.ServiceSpec{Ports: []k8score.ServicePort{{Port: 42}}}} 2686 service2 := k8score.Service{ 2687 ObjectMeta: metav1.ObjectMeta{Name: "test1-service-name"}, 2688 Spec: k8score.ServiceSpec{Ports: []k8score.ServicePort{{Name: "http", Port: 777}}}} 2689 services = append(services, &service2) 2690 services[0] = &service1 2691 dest, err = createDestinationFromService(services) 2692 assert.Equal("test1-service-name", dest.Destination.Host) 2693 assert.Equal(uint32(777), dest.Destination.Port.Number) 2694 assert.NoError(err) 2695 2696 // GIVEN multiple services defined and many of them having the port names with the prefix "http" 2697 // WHEN a destination is created from the service 2698 // THEN verify that an error is returned 2699 service1 = k8score.Service{ 2700 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2701 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2702 Ports: []k8score.ServicePort{{Port: 42}, {Port: 777, Name: "metrics"}}}} 2703 service2 = k8score.Service{ 2704 ObjectMeta: metav1.ObjectMeta{Name: "test-service1-name"}, 2705 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2706 Ports: []k8score.ServicePort{{Port: 777}}}} 2707 services[0] = &service1 2708 services[1] = &service2 2709 dest, err = createDestinationFromService(services) 2710 assert.Nil(dest, "No destination should have been created") 2711 assert.Error(err, "An error should have been returned") 2712 2713 // GIVEN multiple services defined and more than one having ports named with the prefix "http" 2714 // WHEN a destination is created from the service 2715 // THEN verify that an error is returned 2716 service1 = k8score.Service{ 2717 ObjectMeta: metav1.ObjectMeta{Name: "http-service-name"}, 2718 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2719 Ports: []k8score.ServicePort{{Port: 42, Name: "http"}, {Port: 777, Name: "metrics"}}}} 2720 service2 = k8score.Service{ 2721 ObjectMeta: metav1.ObjectMeta{Name: "http-service1-name"}, 2722 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2723 Ports: []k8score.ServicePort{{Port: 777, Name: "http1"}}}} 2724 services[0] = &service1 2725 services[1] = &service2 2726 dest, err = createDestinationFromService(services) 2727 assert.Nil(dest, "No destination should have been created") 2728 assert.Error(err, "An error should have been returned") 2729 2730 // GIVEN no services 2731 // WHEN a destination is created from the service 2732 // THEN verify that function fails 2733 dest, err = createDestinationFromService(nil) 2734 assert.Nil(dest, "No destination should have been created") 2735 assert.Error(err, "An error should have been returned") 2736 } 2737 2738 // TestCreateDestinationForWeblogicWorkload test various use cases of createDestinationFromService for weblogic workload 2739 func TestCreateDestinationForWeblogicWorkload(t *testing.T) { 2740 2741 assert := asserts.New(t) 2742 var services []*k8score.Service 2743 var dest *istionet.HTTPRouteDestination 2744 2745 // GIVEN a weblogic workload service with one weblogic port defined 2746 // WHEN a destination is created from the service 2747 // THEN verify that the destination is created successfully 2748 service1 := k8score.Service{ 2749 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2750 Spec: k8score.ServiceSpec{Selector: map[string]string{"weblogic.createdByOperator": "true"}, 2751 ClusterIP: "10.10.10.3", 2752 Ports: []k8score.ServicePort{{Port: 42, Name: "tcp-1"}, {Port: 777, Name: "tcp-ldap"}}}} 2753 services = append(services, &service1) 2754 dest, err := createDestinationFromService(services) 2755 assert.Equal(uint32(777), dest.Destination.Port.Number) 2756 assert.NoError(err) 2757 2758 // GIVEN a weblogic workload service with one http port defined 2759 // WHEN a destination is created from the service 2760 // THEN verify that the destination is created successfully 2761 service1 = k8score.Service{ 2762 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2763 Spec: k8score.ServiceSpec{Selector: map[string]string{"weblogic.createdByOperator": "true"}, 2764 ClusterIP: "10.10.10.3", 2765 Ports: []k8score.ServicePort{{Port: 42, Name: "tcp-1"}, {Port: 777, Name: "http-default"}}}} 2766 services[0] = &service1 2767 dest, err = createDestinationFromService(services) 2768 assert.Equal(uint32(777), dest.Destination.Port.Number) 2769 assert.NoError(err) 2770 2771 // GIVEN a weblogic workload service with two known weblogic http ports defined 2772 // WHEN a destination is created from the service 2773 // THEN verify that the destination creation fails 2774 service1 = k8score.Service{ 2775 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2776 Spec: k8score.ServiceSpec{Selector: map[string]string{"weblogic.createdByOperator": "true"}, 2777 ClusterIP: "10.10.10.3", 2778 Ports: []k8score.ServicePort{{Port: 42, Name: "tcp-cbt"}, {Port: 777, Name: "tcp-ldap"}}}} 2779 services[0] = &service1 2780 dest, err = createDestinationFromService(services) 2781 assert.Nil(dest, "No destination should have been created") 2782 assert.Error(err, "An error should have been returned") 2783 2784 // GIVEN a weblogic workload service with one weblogic port defined but not created by operator 2785 // WHEN a destination is created from the service 2786 // THEN verify that the destination creation fails 2787 service1 = k8score.Service{ 2788 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2789 Spec: k8score.ServiceSpec{Selector: nil, 2790 ClusterIP: "10.10.10.3", 2791 Ports: []k8score.ServicePort{{Port: 42, Name: "tcp-test"}, {Port: 777, Name: "tcp-ldap"}}}} 2792 services[0] = &service1 2793 dest, err = createDestinationFromService(services) 2794 assert.Nil(dest, "No destination should have been created") 2795 assert.Error(err, "An error should have been returned") 2796 } 2797 2798 // TestCreateDestinationFromRuleOrService test various use cases of createDestinationFromRuleOrService 2799 func TestCreateDestinationFromRuleOrService(t *testing.T) { 2800 2801 assert := asserts.New(t) 2802 var rule vzapi.IngressRule 2803 var services []*k8score.Service 2804 var dest *istionet.HTTPRouteDestination 2805 2806 // GIVEN a rule and service with a valid port defined 2807 // WHEN a destination is created from the rule or service 2808 // THEN verify that the host and port used are that of the one defined in the rule. 2809 rule = vzapi.IngressRule{ 2810 Destination: vzapi.IngressDestination{Host: "test-host", Port: 77}} 2811 service1 := k8score.Service{ 2812 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2813 Spec: k8score.ServiceSpec{Ports: []k8score.ServicePort{{Port: 42, Name: "test-port"}}}} 2814 services = append(services, &service1) 2815 dest, err := createDestinationFromRuleOrService(rule, services) 2816 assert.Equal("test-host", dest.Destination.Host) 2817 assert.Equal(uint32(77), dest.Destination.Port.Number) 2818 assert.NoError(err) 2819 2820 // GIVEN a service and a rule with only valid port defined but not host 2821 // WHEN a destination is created from the rule or service 2822 // THEN verify that the host used is that of the one defined in the service for the corresponding port. 2823 rule = vzapi.IngressRule{ 2824 Destination: vzapi.IngressDestination{Port: 77}} 2825 service1 = k8score.Service{ 2826 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2827 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2828 Ports: []k8score.ServicePort{{Port: 88, Name: "test1-port"}, {Port: 77, Name: "test2-port"}}}} 2829 services[0] = &service1 2830 dest, err = createDestinationFromRuleOrService(rule, services) 2831 assert.Equal("test-service-name", dest.Destination.Host) 2832 assert.Equal(uint32(77), dest.Destination.Port.Number) 2833 assert.NoError(err) 2834 2835 // GIVEN a service and a rule with only valid port defined but not host 2836 // WHEN a destination is created from the rule or service 2837 // THEN an error is returned if there is no corresponding service exists with that rule port 2838 rule = vzapi.IngressRule{ 2839 Destination: vzapi.IngressDestination{Port: 77}} 2840 service1 = k8score.Service{ 2841 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2842 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2843 Ports: []k8score.ServicePort{{Port: 42, Name: "test-port"}}}} 2844 services[0] = &service1 2845 dest, err = createDestinationFromRuleOrService(rule, services) 2846 assert.Nil(dest, "No destination should have been created") 2847 assert.Error(err, "An error should have been returned") 2848 2849 // GIVEN a rule without destination defined and multiple ports defined for a service 2850 // WHEN a destination is created from the rule or service 2851 // THEN verify that the port with name having "http" prefix is used. 2852 rule = vzapi.IngressRule{} 2853 service1 = k8score.Service{ 2854 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2855 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2856 Ports: []k8score.ServicePort{{Port: 42, Name: "metrics"}, {Port: 77, Name: "http"}}}} 2857 services[0] = &service1 2858 dest, err = createDestinationFromRuleOrService(rule, services) 2859 assert.Equal("test-service-name", dest.Destination.Host) 2860 assert.Equal(uint32(77), dest.Destination.Port.Number) 2861 assert.NoError(err) 2862 2863 // GIVEN a rule without destination and multiple ports defined for a service and none of them have "http" prefix 2864 // WHEN a destination is created from the rule or service 2865 // THEN verify that an error is returned 2866 rule = vzapi.IngressRule{} 2867 service1 = k8score.Service{ 2868 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2869 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2870 Ports: []k8score.ServicePort{{Port: 42, Name: "metrics"}, {Port: 77, Name: "test"}}}} 2871 services[0] = &service1 2872 dest, err = createDestinationFromRuleOrService(rule, services) 2873 assert.Nil(dest, "No destination should have been created") 2874 assert.Error(err, "An error should have been returned") 2875 2876 // GIVEN multiple services with same port and rule with only port defined 2877 // WHEN a destination is created from the rule or service 2878 // THEN verify that the service having port name with the prefix "http" is used. 2879 rule = vzapi.IngressRule{ 2880 Destination: vzapi.IngressDestination{Port: 777}} 2881 service1 = k8score.Service{ 2882 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2883 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2884 Ports: []k8score.ServicePort{{Port: 777, Name: "test-port"}}}} 2885 service2 := k8score.Service{ 2886 ObjectMeta: metav1.ObjectMeta{Name: "http-service-name"}, 2887 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2888 Ports: []k8score.ServicePort{{Port: 42, Name: "metrics"}, {Port: 777, Name: "http"}}}} 2889 services[0] = &service1 2890 services = append(services, &service2) 2891 dest, err = createDestinationFromRuleOrService(rule, services) 2892 assert.Equal("http-service-name", dest.Destination.Host) 2893 assert.Equal(uint32(777), dest.Destination.Port.Number) 2894 assert.NoError(err) 2895 2896 // GIVEN multiple services and rule with only port defined 2897 // WHEN a destination is created from the rule or service 2898 // THEN verify that the service corresponding to rule port is used than the one having the port name with 2899 // the prefix "http". 2900 rule = vzapi.IngressRule{ 2901 Destination: vzapi.IngressDestination{Port: 77}} 2902 service1 = k8score.Service{ 2903 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2904 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2905 Ports: []k8score.ServicePort{{Port: 77, Name: "test-port"}}}} 2906 service2 = k8score.Service{ 2907 ObjectMeta: metav1.ObjectMeta{Name: "http-service-name"}, 2908 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2909 Ports: []k8score.ServicePort{{Port: 42, Name: "http"}, {Port: 777}}}} 2910 services[0] = &service1 2911 services[1] = &service2 2912 dest, err = createDestinationFromRuleOrService(rule, services) 2913 assert.Equal("test-service-name", dest.Destination.Host) 2914 assert.Equal(uint32(77), dest.Destination.Port.Number) 2915 assert.NoError(err) 2916 2917 // GIVEN a rule without destination defined and multiple services defined 2918 // WHEN a destination is created from the rule or service 2919 // THEN verify that the service with prefix "http" is used. 2920 rule = vzapi.IngressRule{} 2921 service1 = k8score.Service{ 2922 ObjectMeta: metav1.ObjectMeta{Name: "test-service-name"}, 2923 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.2", 2924 Ports: []k8score.ServicePort{{Port: 42, Name: "test-port"}}}} 2925 service2 = k8score.Service{ 2926 ObjectMeta: metav1.ObjectMeta{Name: "test1-service-name"}, 2927 Spec: k8score.ServiceSpec{ClusterIP: "10.10.10.3", 2928 Ports: []k8score.ServicePort{{Port: 777, Name: "http-port"}}}} 2929 services[0] = &service1 2930 services[1] = &service2 2931 dest, err = createDestinationFromRuleOrService(rule, services) 2932 assert.Equal("test1-service-name", dest.Destination.Host) 2933 assert.Equal(uint32(777), dest.Destination.Port.Number) 2934 assert.NoError(err) 2935 } 2936 2937 // GIVEN a single service in the unstructured children list 2938 // WHEN extracting the services 2939 // THEN ensure the returned service is the child from the list 2940 func TestExtractServicesOnlyOneService(t *testing.T) { 2941 2942 assert := asserts.New(t) 2943 2944 workload := &unstructured.Unstructured{} 2945 workload.SetAPIVersion("apps/v1") 2946 workload.SetKind("Deployment") 2947 workload.SetOwnerReferences([]metav1.OwnerReference{{APIVersion: apiVersion, Kind: "VerrazzanoHelidonWorkload"}}) 2948 2949 var serviceID types.UID = "test-service-1" 2950 u, err := newUnstructuredService(serviceID, "11.12.13.14", 777) 2951 assert.NoError(err) 2952 2953 children := []*unstructured.Unstructured{&u} 2954 var extractedServices []*k8score.Service 2955 reconciler := Reconciler{} 2956 l := vzlog.DefaultLogger() 2957 extractedServices, err = reconciler.extractServicesFromUnstructuredChildren(children, l) 2958 assert.NoError(err) 2959 assert.NotNil(extractedServices) 2960 assert.Equal(len(extractedServices), 1) 2961 assert.Equal(serviceID, extractedServices[0].GetObjectMeta().GetUID()) 2962 } 2963 2964 // GIVEN multiple services in the unstructured children list 2965 // WHEN extracting the services 2966 // THEN ensure the returned services has details of all the services 2967 func TestExtractServicesMultipleServices(t *testing.T) { 2968 2969 assert := asserts.New(t) 2970 2971 workload := &unstructured.Unstructured{} 2972 _ = updateUnstructuredFromYAMLTemplate(workload, "testdata/templates/wls_domain_instance.yaml", nil) 2973 2974 var service1ID types.UID = "test-service-1" 2975 u1, err := newUnstructuredService(service1ID, clusterIPNone, 8001) 2976 assert.NoError(err) 2977 2978 var service2ID types.UID = "test-service-2" 2979 u2, err := newUnstructuredService(service2ID, "10.0.0.1", 8002) 2980 assert.NoError(err) 2981 2982 var service3ID types.UID = "test-service-3" 2983 u3, err := newUnstructuredService(service3ID, "10.0.0.2", 8003) 2984 assert.NoError(err) 2985 2986 children := []*unstructured.Unstructured{&u1, &u2, &u3} 2987 var extractedServices []*k8score.Service 2988 reconciler := Reconciler{} 2989 l := vzlog.DefaultLogger() 2990 extractedServices, err = reconciler.extractServicesFromUnstructuredChildren(children, l) 2991 assert.NoError(err) 2992 assert.NotNil(extractedServices) 2993 assert.Equal(len(extractedServices), 3) 2994 assert.Equal(service1ID, extractedServices[0].GetObjectMeta().GetUID()) 2995 assert.Equal(service2ID, extractedServices[1].GetObjectMeta().GetUID()) 2996 assert.Equal(service3ID, extractedServices[2].GetObjectMeta().GetUID()) 2997 } 2998 2999 // Test a valid existing Service is discovered and used for the destination. 3000 // GIVEN a valid existing Service for a workload 3001 // WHEN an ingress trait is reconciled 3002 // THEN verify gateway and virtual service are created correctly. 3003 func TestSelectExistingServiceForVirtualServiceDestination(t *testing.T) { 3004 3005 assert := asserts.New(t) 3006 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 3007 params := map[string]string{ 3008 "NAMESPACE_NAME": "test-namespace", 3009 "APPCONF_NAME": "test-appconf", 3010 "APPCONF_NAMESPACE": "test-namespace", 3011 "COMPONENT_NAME": "test-comp", 3012 "COMPONENT_NAMESPACE": "test-namespace", 3013 "TRAIT_NAME": "test-trait", 3014 "TRAIT_NAMESPACE": "test-namespace", 3015 "WORKLOAD_NAME": "test-workload", 3016 "WORKLOAD_NAMESPACE": "test-namespace", 3017 "WORKLOAD_KIND": "VerrazzanoWebLogicWorkload", 3018 "DOMAIN_NAME": "test-domain", 3019 "DOMAIN_NAMESPACE": "test-namespace", 3020 "DOMAIN_UID": "test-domain-uid", 3021 } 3022 3023 // Create namespace 3024 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 3025 // Create Verrazzano ingress 3026 assert.NoError(cli.Create(context.Background(), newVerrazzanoIngress("verrazzano-ingress."+testLoadBalancerIP))) 3027 // Create Istio ingress service 3028 assert.NoError(cli.Create(context.Background(), newIstioLoadBalancerService(testClusterIP, testLoadBalancerIP))) 3029 // Create application configuration 3030 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/appconf_with_ingress.yaml", params)) 3031 // Create application component 3032 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_component.yaml", params)) 3033 // Create WebLogic workload definition 3034 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/workloaddefinition_wls.yaml", params)) 3035 // Create trait 3036 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance.yaml", params)) 3037 // Create workload 3038 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_workload_instance.yaml", params)) 3039 // Create domain 3040 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_domain_instance.yaml", params)) 3041 // Create a service 3042 service := k8score.Service{ 3043 ObjectMeta: metav1.ObjectMeta{ 3044 Name: "test-service", 3045 Namespace: params["NAMESPACE_NAME"], 3046 OwnerReferences: []metav1.OwnerReference{{ 3047 APIVersion: "weblogic.oracle/v8", 3048 Kind: "Domain", 3049 Name: params["DOMAIN_NAME"], 3050 UID: types.UID(params["DOMAIN_UID"]), 3051 }}, 3052 }, 3053 Spec: k8score.ServiceSpec{ 3054 Ports: []k8score.ServicePort{{ 3055 Name: "default", 3056 Protocol: "TCP", 3057 Port: 8001, 3058 TargetPort: intstr.FromInt(8001), 3059 }}, 3060 ClusterIP: testClusterIP, 3061 Type: "ClusterIP", 3062 }, 3063 } 3064 assert.NoError(cli.Create(context.Background(), &service)) 3065 3066 // Perform Reconcile 3067 request := newRequest(params["TRAIT_NAMESPACE"], params["TRAIT_NAME"]) 3068 reconciler := newIngressTraitReconciler(cli) 3069 result, err := reconciler.Reconcile(context.TODO(), request) 3070 assert.NoError(err) 3071 assert.Equal(true, result.Requeue, "Expected a requeue due to status update.") 3072 3073 gw := istioclient.Gateway{} 3074 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3075 assert.NoError(err) 3076 assert.Equal("ingressgateway", gw.Spec.Selector["istio"]) 3077 assert.Equal(testLoadBalancerAppGatewayServerHost, gw.Spec.Servers[0].Hosts[0]) 3078 assert.Equal(testTraitPortName, gw.Spec.Servers[0].Port.Name) 3079 assert.Equal(uint32(443), gw.Spec.Servers[0].Port.Number) 3080 assert.Equal("HTTPS", gw.Spec.Servers[0].Port.Protocol) 3081 assert.Equal("test-namespace-test-trait-cert-secret", gw.Spec.Servers[0].Tls.CredentialName) 3082 assert.Equal("SIMPLE", gw.Spec.Servers[0].Tls.Mode.String()) 3083 3084 vs := istioclient.VirtualService{} 3085 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-trait-rule-0-vs"}, &vs) 3086 assert.NoError(err) 3087 assert.Equal("test-namespace-test-appconf-gw", vs.Spec.Gateways[0]) 3088 assert.Len(vs.Spec.Gateways, 1) 3089 assert.Equal(testLoadBalancerAppGatewayServerHost, vs.Spec.Hosts[0]) 3090 assert.Len(vs.Spec.Hosts, 1) 3091 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "prefix:") 3092 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "/bobbys-front-end") 3093 assert.Len(vs.Spec.Http[0].Match, 1) 3094 assert.Equal("test-service", vs.Spec.Http[0].Route[0].Destination.Host) 3095 assert.Equal(uint32(8001), vs.Spec.Http[0].Route[0].Destination.Port.Number) 3096 assert.Len(vs.Spec.Http[0].Route, 1) 3097 assert.Len(vs.Spec.Http, 1) 3098 } 3099 3100 // Test an explicitly provided destination is used in preference to an existing Service. 3101 // GIVEN an ingress trait containing an explicit destination 3102 // WHEN the ingress trait is reconciled 3103 // THEN verify the correct gateway and virtual services are created. 3104 func TestExplicitServiceProvidedForVirtualServiceDestination(t *testing.T) { 3105 3106 assert := asserts.New(t) 3107 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 3108 params := map[string]string{ 3109 "NAMESPACE_NAME": "test-namespace", 3110 "APPCONF_NAME": "test-appconf", 3111 "APPCONF_NAMESPACE": "test-namespace", 3112 "COMPONENT_NAME": "test-comp", 3113 "COMPONENT_NAMESPACE": "test-namespace", 3114 "TRAIT_NAME": testTraitName, 3115 "TRAIT_NAMESPACE": "test-namespace", 3116 "WORKLOAD_NAME": "test-workload", 3117 "WORKLOAD_NAMESPACE": "test-namespace", 3118 "WORKLOAD_KIND": "VerrazzanoWebLogicWorkload", 3119 "DOMAIN_NAME": "test-domain", 3120 "DOMAIN_NAMESPACE": "test-namespace", 3121 "DOMAIN_UID": "test-domain-uid", 3122 } 3123 3124 // Create namespace 3125 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 3126 // Create Verrazzano ingress 3127 assert.NoError(cli.Create(context.Background(), newVerrazzanoIngress(testLoadBalancerIP))) 3128 // Create Istio ingress service 3129 assert.NoError(cli.Create(context.Background(), newIstioLoadBalancerService(testClusterIP, testLoadBalancerIP))) 3130 // Create application configuration 3131 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/appconf_with_ingress.yaml", params)) 3132 // Create application component 3133 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_component.yaml", params)) 3134 // Create WebLogic workload definition 3135 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/workloaddefinition_wls.yaml", params)) 3136 // Create trait 3137 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance_with_dest.yaml", params)) 3138 // Create workload 3139 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_workload_instance.yaml", params)) 3140 // Create domain 3141 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_domain_instance.yaml", params)) 3142 // Create a service. This service should be ignored as an explicit destination is provided. 3143 service := k8score.Service{ 3144 ObjectMeta: metav1.ObjectMeta{ 3145 Name: "test-service", 3146 Namespace: params["NAMESPACE_NAME"], 3147 OwnerReferences: []metav1.OwnerReference{{ 3148 APIVersion: "weblogic.oracle/v8", 3149 Kind: "Domain", 3150 Name: params["DOMAIN_NAME"], 3151 UID: types.UID(params["DOMAIN_UID"]), 3152 }}, 3153 }, 3154 Spec: k8score.ServiceSpec{ 3155 Ports: []k8score.ServicePort{{ 3156 Name: "default", 3157 Protocol: "TCP", 3158 Port: 8001, 3159 TargetPort: intstr.FromInt(8001), 3160 }}, 3161 ClusterIP: testClusterIP, 3162 Type: "ClusterIP", 3163 }, 3164 } 3165 assert.NoError(cli.Create(context.Background(), &service)) 3166 3167 // Perform Reconcile 3168 request := newRequest(params["TRAIT_NAMESPACE"], params["TRAIT_NAME"]) 3169 reconciler := newIngressTraitReconciler(cli) 3170 result, err := reconciler.Reconcile(context.TODO(), request) 3171 assert.NoError(err) 3172 assert.Equal(true, result.Requeue, "Expected a requeue due to status update.") 3173 3174 gw := istioclient.Gateway{} 3175 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3176 assert.NoError(err) 3177 assert.Equal("ingressgateway", gw.Spec.Selector["istio"]) 3178 assert.Equal(testLoadBalancerAppGatewayServerHost, gw.Spec.Servers[0].Hosts[0]) 3179 assert.Equal(testTraitPortName, gw.Spec.Servers[0].Port.Name) 3180 assert.Equal(uint32(443), gw.Spec.Servers[0].Port.Number) 3181 assert.Equal("HTTPS", gw.Spec.Servers[0].Port.Protocol) 3182 assert.Equal("test-namespace-test-trait-cert-secret", gw.Spec.Servers[0].Tls.CredentialName) 3183 assert.Equal("SIMPLE", gw.Spec.Servers[0].Tls.Mode.String()) 3184 3185 vs := istioclient.VirtualService{} 3186 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: expectedTraitVSName}, &vs) 3187 assert.NoError(err) 3188 assert.Equal("test-namespace-test-appconf-gw", vs.Spec.Gateways[0]) 3189 assert.Len(vs.Spec.Gateways, 1) 3190 assert.Equal(testLoadBalancerAppGatewayServerHost, vs.Spec.Hosts[0]) 3191 assert.Len(vs.Spec.Hosts, 1) 3192 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "prefix:") 3193 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "/test-path") 3194 assert.Len(vs.Spec.Http[0].Match, 1) 3195 assert.Equal("test-dest-host", vs.Spec.Http[0].Route[0].Destination.Host) 3196 assert.Equal(uint32(777), vs.Spec.Http[0].Route[0].Destination.Port.Number) 3197 assert.Len(vs.Spec.Http[0].Route, 1) 3198 assert.Len(vs.Spec.Http, 1) 3199 } 3200 3201 // Test failure for multiple service ports without an explicit destination. 3202 // GIVEN a service with multiple ports exists for a workload 3203 // AND no explicit ingress trait definitions are provided 3204 // WHEN the ingress trait is reconciled 3205 // THEN verify the correct gateway and virtual services are created. 3206 func TestMultiplePortsOnDiscoveredService(t *testing.T) { 3207 3208 assert := asserts.New(t) 3209 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 3210 params := map[string]string{ 3211 "NAMESPACE_NAME": "test-namespace", 3212 "APPCONF_NAME": "test-appconf", 3213 "APPCONF_NAMESPACE": "test-namespace", 3214 "COMPONENT_NAME": "test-comp", 3215 "COMPONENT_NAMESPACE": "test-namespace", 3216 "TRAIT_NAME": testTraitName, 3217 "TRAIT_NAMESPACE": "test-namespace", 3218 "WORKLOAD_NAME": "test-workload", 3219 "WORKLOAD_NAMESPACE": "test-namespace", 3220 "WORKLOAD_KIND": "VerrazzanoWebLogicWorkload", 3221 "DOMAIN_NAME": "test-domain", 3222 "DOMAIN_NAMESPACE": "test-namespace", 3223 "DOMAIN_UID": "test-domain-uid", 3224 } 3225 3226 // Create namespace 3227 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 3228 // Create Verrazzano ingress 3229 assert.NoError(cli.Create(context.Background(), newVerrazzanoIngress(testLoadBalancerIP))) 3230 // Create Istio ingress service 3231 assert.NoError(cli.Create(context.Background(), newIstioLoadBalancerService(testClusterIP, testLoadBalancerIP))) 3232 // Create application configuration 3233 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/appconf_with_ingress.yaml", params)) 3234 // Create application component 3235 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_component.yaml", params)) 3236 // Create WebLogic workload definition 3237 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/workloaddefinition_wls.yaml", params)) 3238 // Create trait. This trait has no destination. 3239 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance.yaml", params)) 3240 // Create workload 3241 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_workload_instance.yaml", params)) 3242 // Create domain 3243 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_domain_instance.yaml", params)) 3244 // Create a service. This service has two ports and one with "http" prefix. 3245 service := k8score.Service{ 3246 ObjectMeta: metav1.ObjectMeta{ 3247 Name: "test-service", 3248 Namespace: params["NAMESPACE_NAME"], 3249 OwnerReferences: []metav1.OwnerReference{{ 3250 APIVersion: "weblogic.oracle/v8", 3251 Kind: "Domain", 3252 Name: params["DOMAIN_NAME"], 3253 UID: types.UID(params["DOMAIN_UID"]), 3254 }}, 3255 }, 3256 Spec: k8score.ServiceSpec{ 3257 Ports: []k8score.ServicePort{{ 3258 Name: "default", 3259 Protocol: "TCP", 3260 Port: 8001, 3261 TargetPort: intstr.FromInt(8001)}, { 3262 Name: "http", 3263 Protocol: "TCP", 3264 Port: 8002, 3265 TargetPort: intstr.FromInt(8002)}, 3266 }, 3267 ClusterIP: testClusterIP, 3268 Type: "ClusterIP", 3269 }, 3270 } 3271 assert.NoError(cli.Create(context.Background(), &service)) 3272 3273 // Perform Reconcile 3274 request := newRequest(params["TRAIT_NAMESPACE"], params["TRAIT_NAME"]) 3275 reconciler := newIngressTraitReconciler(cli) 3276 result, err := reconciler.Reconcile(context.TODO(), request) 3277 assert.NoError(err, "No error because reconcile worked but needs to be retried.") 3278 assert.Equal(true, result.Requeue, "Expected a requeue because the discovered service has multiple ports.") 3279 3280 gw := istioclient.Gateway{} 3281 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3282 assert.NoError(err) 3283 assert.Equal("ingressgateway", gw.Spec.Selector["istio"]) 3284 assert.Equal(testLoadBalancerAppGatewayServerHost, gw.Spec.Servers[0].Hosts[0]) 3285 assert.Equal(testTraitPortName, gw.Spec.Servers[0].Port.Name) 3286 assert.Equal(uint32(443), gw.Spec.Servers[0].Port.Number) 3287 assert.Equal("HTTPS", gw.Spec.Servers[0].Port.Protocol) 3288 assert.Equal("test-namespace-test-trait-cert-secret", gw.Spec.Servers[0].Tls.CredentialName) 3289 assert.Equal("SIMPLE", gw.Spec.Servers[0].Tls.Mode.String()) 3290 3291 vs := istioclient.VirtualService{} 3292 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: expectedTraitVSName}, &vs) 3293 assert.NoError(err) 3294 assert.Equal("test-namespace-test-appconf-gw", vs.Spec.Gateways[0]) 3295 assert.Len(vs.Spec.Gateways, 1) 3296 assert.Equal(testLoadBalancerAppGatewayServerHost, vs.Spec.Hosts[0]) 3297 assert.Len(vs.Spec.Hosts, 1) 3298 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "prefix:") 3299 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "/bobbys-front-end") 3300 assert.Len(vs.Spec.Http[0].Match, 1) 3301 assert.Equal("test-service", vs.Spec.Http[0].Route[0].Destination.Host) 3302 assert.Equal(8002, int(vs.Spec.Http[0].Route[0].Destination.Port.Number)) 3303 assert.Len(vs.Spec.Http[0].Route, 1) 3304 assert.Len(vs.Spec.Http, 1) 3305 } 3306 3307 // Test failure for multiple services for non-WebLogic workload without explicit destination. 3308 // GIVEN multiple services created for a non-WebLogic workload 3309 // AND no explicit ingress trait definitions are provided 3310 // WHEN the ingress trait is reconciled 3311 // THEN verify the correct gateway and virtual services are created. 3312 func TestMultipleServicesForNonWebLogicWorkloadWithoutExplicitIngressDestination(t *testing.T) { 3313 3314 assert := asserts.New(t) 3315 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 3316 params := map[string]string{ 3317 "NAMESPACE_NAME": "test-namespace", 3318 "APPCONF_NAME": "test-appconf", 3319 "APPCONF_NAMESPACE": "test-namespace", 3320 "APPCONF_UID": "test-appconf-uid", 3321 "COMPONENT_NAME": "test-comp", 3322 "COMPONENT_NAMESPACE": "test-namespace", 3323 "TRAIT_NAME": testTraitName, 3324 "TRAIT_NAMESPACE": "test-namespace", 3325 "WORKLOAD_NAME": "test-workload", 3326 "WORKLOAD_NAMESPACE": "test-namespace", 3327 "WORKLOAD_UID": testWorkloadID, 3328 "WORKLOAD_KIND": "VerrazzanoHelidonWorkload", 3329 "DEPLOYMENT_NAME": "test-deployment", 3330 "DEPLOYMENT_NAMESPACE": "test-namespace", 3331 "DEPLOYMENT_UID": "test-domain-uid", 3332 "CONTAINER_NAME": "test-container-name", 3333 "CONTAINER_IMAGE": "test-container-image", 3334 "CONTAINER_PORT_NAME": "test-container-port-name", 3335 "CONTAINER_PORT_NUMBER": "777", 3336 } 3337 3338 // Create namespace 3339 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 3340 // Create Verrazzano ingress 3341 assert.NoError(cli.Create(context.Background(), newVerrazzanoIngress(testLoadBalancerIP))) 3342 // Create Istio ingress service 3343 assert.NoError(cli.Create(context.Background(), newIstioLoadBalancerService(testClusterIP, testLoadBalancerIP))) 3344 // Create application configuration 3345 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/appconf_with_ingress.yaml", params)) 3346 // Create application component 3347 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/helidon_component.yaml", params)) 3348 // Create WebLogic workload definition 3349 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/workloaddefinition_vzhelidon.yaml", params)) 3350 // Create workload 3351 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/helidon_workload_instance.yaml", params)) 3352 // Create trait. This trait has no destination. 3353 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance.yaml", params)) 3354 // Create a first service. 3355 service1 := k8score.Service{ 3356 ObjectMeta: metav1.ObjectMeta{ 3357 Name: "test-service-1", 3358 Namespace: params["APPCONF_NAMESPACE"], 3359 OwnerReferences: []metav1.OwnerReference{{ 3360 APIVersion: apiVersion, 3361 Kind: "VerrazzanoHelidonWorkload", 3362 Name: params["WORKLOAD_NAME"], 3363 UID: types.UID(params["WORKLOAD_UID"]), 3364 }}, 3365 }, 3366 Spec: k8score.ServiceSpec{ 3367 Ports: []k8score.ServicePort{{ 3368 Name: "test-service-1-port", 3369 Protocol: "TCP", 3370 Port: 8081, 3371 TargetPort: intstr.FromInt(8081)}, 3372 }, 3373 ClusterIP: testClusterIP, 3374 Type: "NodePort", 3375 }, 3376 } 3377 assert.NoError(cli.Create(context.Background(), &service1)) 3378 // Create a second service. 3379 service2 := k8score.Service{ 3380 ObjectMeta: metav1.ObjectMeta{ 3381 Name: "test-service-2", 3382 Namespace: params["APPCONF_NAMESPACE"], 3383 OwnerReferences: []metav1.OwnerReference{{ 3384 APIVersion: apiVersion, 3385 Kind: "VerrazzanoHelidonWorkload", 3386 Name: params["WORKLOAD_NAME"], 3387 UID: types.UID(params["WORKLOAD_UID"]), 3388 }}, 3389 }, 3390 Spec: k8score.ServiceSpec{ 3391 Ports: []k8score.ServicePort{{ 3392 Name: "http-service-2-port", 3393 Protocol: "TCP", 3394 Port: 8082, 3395 TargetPort: intstr.FromInt(8082)}, 3396 }, 3397 ClusterIP: "11.12.13.14", 3398 Type: "NodePort", 3399 }, 3400 } 3401 assert.NoError(cli.Create(context.Background(), &service2)) 3402 3403 // Perform Reconcile 3404 request := newRequest(params["TRAIT_NAMESPACE"], params["TRAIT_NAME"]) 3405 reconciler := newIngressTraitReconciler(cli) 3406 result, err := reconciler.Reconcile(context.TODO(), request) 3407 assert.NoError(err, "No error because reconcile worked but needs to be retried.") 3408 assert.Equal(true, result.Requeue, "Expected a requeue because the discovered service has multiple ports.") 3409 3410 gw := istioclient.Gateway{} 3411 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3412 assert.NoError(err) 3413 assert.Equal("ingressgateway", gw.Spec.Selector["istio"]) 3414 assert.Equal(testLoadBalancerAppGatewayServerHost, gw.Spec.Servers[0].Hosts[0]) 3415 assert.Equal(testTraitPortName, gw.Spec.Servers[0].Port.Name) 3416 assert.Equal(uint32(443), gw.Spec.Servers[0].Port.Number) 3417 assert.Equal("HTTPS", gw.Spec.Servers[0].Port.Protocol) 3418 assert.Equal("test-namespace-test-trait-cert-secret", gw.Spec.Servers[0].Tls.CredentialName) 3419 assert.Equal("SIMPLE", gw.Spec.Servers[0].Tls.Mode.String()) 3420 3421 vs := istioclient.VirtualService{} 3422 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: expectedTraitVSName}, &vs) 3423 assert.NoError(err) 3424 assert.Equal("test-namespace-test-appconf-gw", vs.Spec.Gateways[0]) 3425 assert.Len(vs.Spec.Gateways, 1) 3426 assert.Equal(testLoadBalancerAppGatewayServerHost, vs.Spec.Hosts[0]) 3427 assert.Len(vs.Spec.Hosts, 1) 3428 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "prefix:") 3429 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "/bobbys-front-end") 3430 assert.Len(vs.Spec.Http[0].Match, 1) 3431 assert.Equal("test-service-2", vs.Spec.Http[0].Route[0].Destination.Host) 3432 assert.Equal(8082, int(vs.Spec.Http[0].Route[0].Destination.Port.Number)) 3433 assert.Len(vs.Spec.Http[0].Route, 1) 3434 assert.Len(vs.Spec.Http, 1) 3435 } 3436 3437 // Test correct WebLogic service (i.e. with ClusterIP) getting picked after reconcile failure and retry. 3438 // GIVEN a new WebLogic workload/domain 3439 // AND no services have been created 3440 // WHEN an ingress trait is reconciled 3441 // THEN ensure that no gateways or virtual services are created 3442 // THEN create a service as the WebLogic operator would 3443 // THEN verity that the expected gateway and virtual services are created. 3444 func TestSelectExistingServiceForVirtualServiceDestinationAfterRetry(t *testing.T) { 3445 3446 assert := asserts.New(t) 3447 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 3448 params := map[string]string{ 3449 "NAMESPACE_NAME": "test-namespace", 3450 "APPCONF_NAME": "test-appconf", 3451 "APPCONF_NAMESPACE": "test-namespace", 3452 "COMPONENT_NAME": "test-comp", 3453 "COMPONENT_NAMESPACE": "test-namespace", 3454 "TRAIT_NAME": testTraitName, 3455 "TRAIT_NAMESPACE": "test-namespace", 3456 "WORKLOAD_NAME": "test-workload", 3457 "WORKLOAD_NAMESPACE": "test-namespace", 3458 "WORKLOAD_KIND": "VerrazzanoWebLogicWorkload", 3459 "DOMAIN_NAME": "test-domain", 3460 "DOMAIN_NAMESPACE": "test-namespace", 3461 "DOMAIN_UID": "test-domain-uid", 3462 } 3463 3464 // Create namespace 3465 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 3466 // Create Verrazzano ingress 3467 assert.NoError(cli.Create(context.Background(), newVerrazzanoIngress(testLoadBalancerIP))) 3468 // Create Istio ingress service 3469 assert.NoError(cli.Create(context.Background(), newIstioLoadBalancerService(testClusterIP, testLoadBalancerIP))) 3470 // Create application configuration 3471 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/appconf_with_ingress.yaml", params)) 3472 // Create application component 3473 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_component.yaml", params)) 3474 // Create WebLogic workload definition 3475 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/workloaddefinition_wls.yaml", params)) 3476 // Create trait 3477 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance.yaml", params)) 3478 // Create workload 3479 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_workload_instance.yaml", params)) 3480 // Create domain 3481 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/wls_domain_instance.yaml", params)) 3482 3483 // Perform Reconcile 3484 request := newRequest(params["TRAIT_NAMESPACE"], params["TRAIT_NAME"]) 3485 reconciler := newIngressTraitReconciler(cli) 3486 result, err := reconciler.Reconcile(context.TODO(), request) 3487 assert.NoError(err) 3488 assert.Equal(true, result.Requeue, "Expected no requeue as error expected.") 3489 3490 gw := istioclient.Gateway{} 3491 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3492 assert.False(k8serrors.IsNotFound(err), "Gateway should have been created.") 3493 3494 vs := istioclient.VirtualService{} 3495 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: expectedTraitVSName}, &vs) 3496 assert.True(k8serrors.IsNotFound(err), "No VirtualService should have been created.") 3497 3498 // Update a service. Update the ClusterIP of the service. 3499 service := k8score.Service{ 3500 ObjectMeta: metav1.ObjectMeta{ 3501 Name: "test-service", 3502 Namespace: params["NAMESPACE_NAME"], 3503 OwnerReferences: []metav1.OwnerReference{{ 3504 APIVersion: "weblogic.oracle/v8", 3505 Kind: "Domain", 3506 Name: params["DOMAIN_NAME"], 3507 UID: types.UID(params["DOMAIN_UID"]), 3508 }}, 3509 }, 3510 Spec: k8score.ServiceSpec{ 3511 Ports: []k8score.ServicePort{{ 3512 Name: "default", 3513 Protocol: "TCP", 3514 Port: 8001, 3515 TargetPort: intstr.FromInt(8001), 3516 }}, 3517 ClusterIP: testClusterIP, 3518 Type: "ClusterIP", 3519 }, 3520 } 3521 assert.NoError(cli.Create(context.Background(), &service)) 3522 3523 // Reconcile again. 3524 result, err = reconciler.Reconcile(context.TODO(), request) 3525 assert.NoError(err) 3526 assert.Equal(true, result.Requeue, "Expected requeue as status was updated.") 3527 3528 // Verify the Gateway was created and is valid. 3529 gw = istioclient.Gateway{} 3530 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: "test-namespace-test-appconf-gw"}, &gw) 3531 assert.NoError(err) 3532 assert.Equal("ingressgateway", gw.Spec.Selector["istio"]) 3533 assert.Equal(testLoadBalancerAppGatewayServerHost, gw.Spec.Servers[0].Hosts[0]) 3534 assert.Equal(testTraitPortName, gw.Spec.Servers[0].Port.Name) 3535 assert.Equal(uint32(443), gw.Spec.Servers[0].Port.Number) 3536 assert.Equal("HTTPS", gw.Spec.Servers[0].Port.Protocol) 3537 assert.Equal("test-namespace-test-trait-cert-secret", gw.Spec.Servers[0].Tls.CredentialName) 3538 assert.Equal("SIMPLE", gw.Spec.Servers[0].Tls.Mode.String()) 3539 3540 // Verify the VirtualService was created and is valid. 3541 vs = istioclient.VirtualService{} 3542 err = cli.Get(context.Background(), client.ObjectKey{Namespace: "test-namespace", Name: expectedTraitVSName}, &vs) 3543 assert.NoError(err) 3544 assert.Equal("test-namespace-test-appconf-gw", vs.Spec.Gateways[0]) 3545 assert.Len(vs.Spec.Gateways, 1) 3546 assert.Equal(testLoadBalancerAppGatewayServerHost, vs.Spec.Hosts[0]) 3547 assert.Len(vs.Spec.Hosts, 1) 3548 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "prefix:") 3549 assert.Contains(vs.Spec.Http[0].Match[0].Uri.String(), "/bobbys-front-end") 3550 assert.Len(vs.Spec.Http[0].Match, 1) 3551 assert.Equal("test-service", vs.Spec.Http[0].Route[0].Destination.Host) 3552 assert.Equal(uint32(8001), vs.Spec.Http[0].Route[0].Destination.Port.Number) 3553 assert.Len(vs.Spec.Http[0].Route, 1) 3554 assert.Len(vs.Spec.Http, 1) 3555 } 3556 3557 // newScheme creates a new scheme that includes this package's object to use for testing 3558 func newScheme() *runtime.Scheme { 3559 scheme := runtime.NewScheme() 3560 //_ = clientgoscheme.AddToScheme(scheme) 3561 _ = core.AddToScheme(scheme) 3562 _ = k8sapps.AddToScheme(scheme) 3563 _ = vzapi.AddToScheme(scheme) 3564 _ = k8score.AddToScheme(scheme) 3565 _ = certapiv1.AddToScheme(scheme) 3566 _ = k8net.AddToScheme(scheme) 3567 _ = istioclient.AddToScheme(scheme) 3568 _ = v1alpha2.SchemeBuilder.AddToScheme(scheme) 3569 3570 return scheme 3571 } 3572 3573 // newIngressTraitReconciler creates a new reconciler for testing 3574 // c - The Kerberos client to inject into the reconciler 3575 func newIngressTraitReconciler(c client.Client) Reconciler { 3576 l := zap.S().With("test") 3577 scheme := newScheme() 3578 reconciler := Reconciler{ 3579 Client: c, 3580 Log: l, 3581 Scheme: scheme} 3582 return reconciler 3583 } 3584 3585 // newRequest creates a new reconciler request for testing 3586 // namespace - The namespace to use in the request 3587 // name - The name to use in the request 3588 func newRequest(namespace string, name string) ctrl.Request { 3589 return ctrl.Request{ 3590 NamespacedName: types.NamespacedName{ 3591 Namespace: namespace, 3592 Name: name}} 3593 } 3594 3595 // convertToUnstructured converts an object to an Unstructured version 3596 // object - The object to convert to Unstructured 3597 func convertToUnstructured(object interface{}) (unstructured.Unstructured, error) { 3598 jbytes, err := json.Marshal(object) 3599 if err != nil { 3600 return unstructured.Unstructured{}, err 3601 } 3602 var u map[string]interface{} 3603 _ = json.Unmarshal(jbytes, &u) 3604 return unstructured.Unstructured{Object: u}, nil 3605 } 3606 3607 // appendAsUnstructured appends an object to the list after converting it to an Unstructured 3608 // list - The list to append to. 3609 // object - The object to convert to Unstructured and append to the list 3610 func appendAsUnstructured(list *unstructured.UnstructuredList, object interface{}) error { 3611 u, err := convertToUnstructured(object) 3612 if err != nil { 3613 return err 3614 } 3615 list.Items = append(list.Items, u) 3616 return nil 3617 } 3618 3619 // newVerrazzanoIngress creates a new Ranger Ingress with the provided IP address. 3620 func newVerrazzanoIngress(ipAddress string) *k8net.Ingress { 3621 rangerIngress := k8net.Ingress{ 3622 ObjectMeta: metav1.ObjectMeta{ 3623 Name: constants.VzConsoleIngress, 3624 Namespace: constants.VerrazzanoSystemNamespace, 3625 Annotations: map[string]string{ 3626 "external-dns.alpha.kubernetes.io/target": fmt.Sprintf("verrazzano-ingress.default.%s.nip.io", ipAddress), 3627 "verrazzano.io/dns.wildcard.domain": "nip.io", 3628 }, 3629 }, 3630 } 3631 return &rangerIngress 3632 } 3633 3634 // newIstioLoadBalancerService creates a new Istio LoadBalancer Service with the provided 3635 // clusterIPAddress and loadBalancerIPAddress 3636 func newIstioLoadBalancerService(clusterIPAddress string, loadBalancerIPAddress string) *k8score.Service { 3637 istioService := k8score.Service{ 3638 ObjectMeta: metav1.ObjectMeta{ 3639 Name: istioIngressGatewayName, 3640 Namespace: istioSystemNamespace, 3641 }, 3642 Spec: k8score.ServiceSpec{ 3643 ClusterIP: clusterIPAddress, 3644 Type: "LoadBalancer", 3645 }, 3646 Status: k8score.ServiceStatus{ 3647 LoadBalancer: k8score.LoadBalancerStatus{ 3648 Ingress: []k8score.LoadBalancerIngress{{ 3649 IP: loadBalancerIPAddress}}}}, 3650 } 3651 return &istioService 3652 } 3653 3654 // newUnstructuredService creates a service and returns it in Unstructured form 3655 // uid - The UID of the service 3656 // clusterIP - The cluster IP of the service 3657 func newUnstructuredService(uid types.UID, clusterIP string, port int32) (unstructured.Unstructured, error) { 3658 service := k8score.Service{ 3659 TypeMeta: metav1.TypeMeta{ 3660 APIVersion: "v1", 3661 Kind: "Service", 3662 }, 3663 ObjectMeta: metav1.ObjectMeta{ 3664 UID: uid, 3665 }, 3666 Spec: k8score.ServiceSpec{ 3667 ClusterIP: clusterIP, 3668 Ports: []k8score.ServicePort{{Port: port}}}, 3669 } 3670 return convertToUnstructured(service) 3671 } 3672 3673 // executeTemplate reads a template from a file and replaces values in the template from param maps 3674 // template - The filename of a template 3675 // params - a vararg of param maps 3676 func executeTemplate(templateFile string, data interface{}) (string, error) { 3677 file := "../../" + templateFile 3678 if _, err := os.Stat(file); err != nil { 3679 file = "../" + templateFile 3680 if _, err := os.Stat(file); err != nil { 3681 file = templateFile 3682 if _, err := os.Stat(file); err != nil { 3683 return "", err 3684 } 3685 } 3686 } 3687 b, err := os.ReadFile(file) 3688 if err != nil { 3689 return "", err 3690 } 3691 t, err := template.New(templateFile).Parse(string(b)) 3692 if err != nil { 3693 return "", err 3694 } 3695 var buf bytes.Buffer 3696 err = t.ExecuteTemplate(&buf, templateFile, data) 3697 if err != nil { 3698 return "", err 3699 } 3700 return buf.String(), nil 3701 } 3702 3703 // updateUnstructuredFromYAMLTemplate updates an unstructured from a populated YAML template file. 3704 // uns - The unstructured to update 3705 // template - The template file 3706 // params - The param maps to merge into the template 3707 func updateUnstructuredFromYAMLTemplate(uns *unstructured.Unstructured, template string, data interface{}) error { 3708 str, err := executeTemplate(template, data) 3709 if err != nil { 3710 return err 3711 } 3712 ybytes, err := yaml.YAMLToJSON([]byte(str)) 3713 if err != nil { 3714 return err 3715 } 3716 _, _, err = unstructured.UnstructuredJSONScheme.Decode(ybytes, nil, uns) 3717 if err != nil { 3718 return err 3719 } 3720 return nil 3721 } 3722 3723 // createResourceFromTemplate builds a resource by merging the data with the template file and then 3724 // creates the resource using the client. 3725 func createResourceFromTemplate(cli client.Client, template string, data interface{}) error { 3726 uns := unstructured.Unstructured{} 3727 if err := updateUnstructuredFromYAMLTemplate(&uns, template, data); err != nil { 3728 return err 3729 } 3730 if err := cli.Create(context.Background(), &uns); err != nil { 3731 return err 3732 } 3733 return nil 3734 } 3735 3736 // TestSuccessfullyCreateNewIngressForVerrazzanoWorkloadWithHTTPCookieIstioEnabled tests the Reconcile method for the following use case. 3737 // GIVEN a request to reconcile an ingress trait resource that applies to a Verrazzano workload type with HTTPCookie defined for session affinity and inside the Istio meash 3738 // WHEN the trait exists but the ingress does not 3739 // THEN ensure that the workload is unwrapped and the trait is created. 3740 func TestSuccessfullyCreateNewIngressForVerrazzanoWorkloadWithHTTPCookieIstioEnabled(t *testing.T) { 3741 3742 assert := asserts.New(t) 3743 mocker := gomock.NewController(t) 3744 mock := mocks.NewMockClient(mocker) 3745 mockStatus := mocks.NewMockStatusWriter(mocker) 3746 labels := map[string]string{"verrazzano-managed": "true", "istio-injection": "enabled"} 3747 namespace.Labels = labels 3748 // Expect a call to get the ingress trait resource. 3749 mock.EXPECT(). 3750 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 3751 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 3752 trait.TypeMeta = metav1.TypeMeta{ 3753 APIVersion: apiVersion, 3754 Kind: traitKind} 3755 trait.ObjectMeta = metav1.ObjectMeta{ 3756 Namespace: name.Namespace, 3757 Name: name.Name, 3758 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 3759 trait.Spec.Rules = []vzapi.IngressRule{{ 3760 Hosts: []string{"test-host"}, 3761 Paths: []vzapi.IngressPath{{Path: "test-path"}}, 3762 Destination: vzapi.IngressDestination{ 3763 Host: "test-service.test-space.svc.local", 3764 Port: 0, 3765 HTTPCookie: &vzapi.IngressDestinationHTTPCookie{ 3766 Name: "test-cookie", 3767 Path: "/", 3768 TTL: 30}, 3769 }}} 3770 trait.Spec.WorkloadReference = oamrt.TypedReference{ 3771 APIVersion: apiVersion, 3772 Kind: "VerrazzanoCoherenceWorkload", 3773 Name: testWorkloadName} 3774 return nil 3775 }) 3776 // Expect a call to update the ingress trait resource with a finalizer. 3777 mock.EXPECT(). 3778 Update(gomock.Any(), gomock.Any(), gomock.Any()). 3779 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 3780 assert.Equal(testNamespace, trait.Namespace) 3781 assert.Equal(testTraitName, trait.Name) 3782 assert.Len(trait.Finalizers, 1) 3783 assert.Equal(finalizerName, trait.Finalizers[0]) 3784 return nil 3785 }) 3786 3787 containedName := testwWorkloadName 3788 containedResource := map[string]interface{}{ 3789 "metadata": map[string]interface{}{ 3790 "name": containedName, 3791 }, 3792 } 3793 3794 // Expect a call to get the Verrazzano workload resource 3795 mock.EXPECT(). 3796 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 3797 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 3798 workload.SetAPIVersion(apiVersion) 3799 workload.SetKind("VerrazzanoCoherenceWorkload") 3800 workload.SetNamespace(name.Namespace) 3801 workload.SetName(name.Name) 3802 _ = unstructured.SetNestedMap(workload.Object, containedResource, "spec", "template") 3803 return nil 3804 }) 3805 // Expect a call to get the contained resource 3806 mock.EXPECT(). 3807 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: containedName}, gomock.Not(gomock.Nil()), gomock.Any()). 3808 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 3809 workload.SetUnstructuredContent(containedResource) 3810 workload.SetNamespace(name.Namespace) 3811 workload.SetAPIVersion("coherence.oracle.com/v1") 3812 workload.SetKind("Coherence") 3813 workload.SetUID(testWorkloadID) 3814 return nil 3815 }) 3816 // Expect a call to get the containerized workload resource definition 3817 mock.EXPECT(). 3818 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "coherences.coherence.oracle.com"}, gomock.Not(gomock.Nil()), gomock.Any()). 3819 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 3820 workloadDef.Namespace = name.Namespace 3821 workloadDef.Name = name.Name 3822 workloadDef.Spec.ChildResourceKinds = []v1alpha2.ChildResourceKind{ 3823 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 3824 {APIVersion: "v1", Kind: "Service", Selector: nil}, 3825 } 3826 return nil 3827 }) 3828 // Expect a call to list the child Deployment resources of the workload definition 3829 mock.EXPECT(). 3830 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 3831 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 3832 assert.Equal("DeploymentList", list.GetKind()) 3833 return nil 3834 }) 3835 appCertificateExpectations(mock) 3836 // Expect a call to list the child Service resources of the workload definition 3837 mock.EXPECT(). 3838 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 3839 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 3840 assert.Equal("ServiceList", list.GetKind()) 3841 return appendAsUnstructured(list, k8score.Service{ 3842 TypeMeta: metav1.TypeMeta{ 3843 APIVersion: "v1", 3844 Kind: "Service", 3845 }, 3846 ObjectMeta: metav1.ObjectMeta{ 3847 OwnerReferences: []metav1.OwnerReference{{ 3848 APIVersion: "core.oam.dev/v1alpha2", 3849 Kind: "ContainerizedWorkload", 3850 Name: testWorkloadName, 3851 UID: testWorkloadID, 3852 }}}, 3853 Spec: k8score.ServiceSpec{ 3854 ClusterIP: testClusterIP, 3855 Ports: []k8score.ServicePort{{Port: 42}}}, 3856 }) 3857 }) 3858 // Expect a call to get the app config and return that it is not found. 3859 mock.EXPECT(). 3860 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 3861 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 3862 app.TypeMeta = metav1.TypeMeta{ 3863 APIVersion: "core.oam.dev/v1alpha2", 3864 Kind: "ApplicationConfiguration", 3865 } 3866 return nil 3867 }) 3868 3869 deleteCertExpectations(mock, "test-space-myapp-cert") 3870 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 3871 createCertSuccessExpectations(mock) 3872 getGatewayForTraitNotFoundExpectations(mock) 3873 createIngressResourceSuccessExpectations(mock) 3874 traitVSNotFoundExpectation(mock) 3875 createIngressResSuccessExpectations(mock, assert) 3876 getMockStatusWriterExpectations(mock, mockStatus) 3877 3878 mock.EXPECT(). 3879 Get(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 3880 DoAndReturn(func(ctx context.Context, key client.ObjectKey, n *k8score.Namespace, opts ...client.GetOption) error { 3881 return nil 3882 }) 3883 // Expect a call to get the destination rule resource related to the ingress trait and return that it is not found. 3884 mock.EXPECT(). 3885 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: fmt.Sprintf(testRule, testTraitName)}, gomock.Not(gomock.Nil()), gomock.Any()). 3886 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "DestinationRule"}, fmt.Sprintf(testRule, testTraitName))) 3887 3888 // Expect a call to create the DestinationRule resource and return success 3889 mock.EXPECT(). 3890 Create(gomock.Any(), gomock.Any(), gomock.Any()). 3891 DoAndReturn(func(ctx context.Context, destinationrule *istioclient.DestinationRule, opts ...client.CreateOption) error { 3892 assert.Equal("test-service.test-space.svc.local", destinationrule.Spec.Host) 3893 lbPolicy := destinationrule.Spec.TrafficPolicy.LoadBalancer.LbPolicy.(*istionet.LoadBalancerSettings_ConsistentHash) 3894 hashKey := lbPolicy.ConsistentHash.HashKey.(*istionet.LoadBalancerSettings_ConsistentHashLB_HttpCookie) 3895 assert.Equal(int64(30), hashKey.HttpCookie.Ttl.Seconds) 3896 assert.Equal(int32(0), hashKey.HttpCookie.Ttl.Nanos) 3897 return nil 3898 }) 3899 // Expect a call to update the status of the ingress trait. 3900 mockStatus.EXPECT(). 3901 Update(gomock.Any(), gomock.Any(), gomock.Any()). 3902 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 3903 assert.Len(trait.Status.Conditions, 1) 3904 assert.Len(trait.Status.Resources, 4) 3905 return nil 3906 }) 3907 3908 // Create and make the request 3909 request := newRequest(testNamespace, testTraitName) 3910 reconciler := newIngressTraitReconciler(mock) 3911 result, err := reconciler.Reconcile(context.TODO(), request) 3912 3913 // Validate the results 3914 mocker.Finish() 3915 assert.NoError(err) 3916 assert.Equal(true, result.Requeue) 3917 assert.Equal(time.Duration(0), result.RequeueAfter) 3918 } 3919 3920 // TestSuccessfullyCreateNewIngressForVerrazzanoWorkloadWithHTTPCookieIstioDisabled tests the Reconcile method for the following use case. 3921 // GIVEN a request to reconcile an ingress trait resource that applies to a Verrazzano workload type with HTTPCookie defined for session affinity and outside the Istion mesh 3922 // WHEN the trait exists but the ingress does not 3923 // THEN ensure that the workload is unwrapped and the trait is created. 3924 func TestSuccessfullyCreateNewIngressForVerrazzanoWorkloadWithHTTPCookieIstioDisabled(t *testing.T) { 3925 3926 assert := asserts.New(t) 3927 mocker := gomock.NewController(t) 3928 mock := mocks.NewMockClient(mocker) 3929 mockStatus := mocks.NewMockStatusWriter(mocker) 3930 labels := map[string]string{"verrazzano-managed": "true", "istio-injection": "disabled"} 3931 namespace.Labels = labels 3932 // Expect a call to get the ingress trait resource. 3933 mock.EXPECT(). 3934 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 3935 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 3936 trait.TypeMeta = metav1.TypeMeta{ 3937 APIVersion: apiVersion, 3938 Kind: traitKind} 3939 trait.ObjectMeta = metav1.ObjectMeta{ 3940 Namespace: name.Namespace, 3941 Name: name.Name, 3942 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 3943 trait.Spec.Rules = []vzapi.IngressRule{{ 3944 Hosts: []string{"test-host"}, 3945 Paths: []vzapi.IngressPath{{Path: "test-path"}}, 3946 Destination: vzapi.IngressDestination{ 3947 Host: "test-service.test-space.svc.local", 3948 Port: 0, 3949 HTTPCookie: &vzapi.IngressDestinationHTTPCookie{ 3950 Name: "test-cookie", 3951 Path: "/", 3952 TTL: 30}, 3953 }}} 3954 trait.Spec.WorkloadReference = oamrt.TypedReference{ 3955 APIVersion: apiVersion, 3956 Kind: "VerrazzanoCoherenceWorkload", 3957 Name: testWorkloadName} 3958 return nil 3959 }) 3960 // Expect a call to update the ingress trait resource with a finalizer. 3961 mock.EXPECT(). 3962 Update(gomock.Any(), gomock.Any(), gomock.Any()). 3963 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 3964 assert.Equal(testNamespace, trait.Namespace) 3965 assert.Equal(testTraitName, trait.Name) 3966 assert.Len(trait.Finalizers, 1) 3967 assert.Equal(finalizerName, trait.Finalizers[0]) 3968 return nil 3969 }) 3970 3971 containedName := testwWorkloadName 3972 containedResource := map[string]interface{}{ 3973 "metadata": map[string]interface{}{ 3974 "name": containedName, 3975 }, 3976 } 3977 3978 // Expect a call to get the Verrazzano workload resource 3979 mock.EXPECT(). 3980 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 3981 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 3982 workload.SetAPIVersion(apiVersion) 3983 workload.SetKind("VerrazzanoCoherenceWorkload") 3984 workload.SetNamespace(name.Namespace) 3985 workload.SetName(name.Name) 3986 _ = unstructured.SetNestedMap(workload.Object, containedResource, "spec", "template") 3987 return nil 3988 }) 3989 // Expect a call to get the contained resource 3990 mock.EXPECT(). 3991 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: containedName}, gomock.Not(gomock.Nil()), gomock.Any()). 3992 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 3993 workload.SetUnstructuredContent(containedResource) 3994 workload.SetNamespace(name.Namespace) 3995 workload.SetAPIVersion("coherence.oracle.com/v1") 3996 workload.SetKind("Coherence") 3997 workload.SetUID(testWorkloadID) 3998 return nil 3999 }) 4000 // Expect a call to get the containerized workload resource definition 4001 mock.EXPECT(). 4002 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "coherences.coherence.oracle.com"}, gomock.Not(gomock.Nil()), gomock.Any()). 4003 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 4004 workloadDef.Namespace = name.Namespace 4005 workloadDef.Name = name.Name 4006 workloadDef.Spec.ChildResourceKinds = []v1alpha2.ChildResourceKind{ 4007 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 4008 {APIVersion: "v1", Kind: "Service", Selector: nil}, 4009 } 4010 return nil 4011 }) 4012 // Expect a call to list the child Deployment resources of the workload definition 4013 mock.EXPECT(). 4014 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 4015 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 4016 assert.Equal("DeploymentList", list.GetKind()) 4017 return nil 4018 }) 4019 appCertificateExpectations(mock) 4020 // Expect a call to list the child Service resources of the workload definition 4021 mock.EXPECT(). 4022 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 4023 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 4024 assert.Equal("ServiceList", list.GetKind()) 4025 return appendAsUnstructured(list, k8score.Service{ 4026 TypeMeta: metav1.TypeMeta{ 4027 APIVersion: "v1", 4028 Kind: "Service", 4029 }, 4030 ObjectMeta: metav1.ObjectMeta{ 4031 OwnerReferences: []metav1.OwnerReference{{ 4032 APIVersion: "core.oam.dev/v1alpha2", 4033 Kind: "ContainerizedWorkload", 4034 Name: testWorkloadName, 4035 UID: testWorkloadID, 4036 }}}, 4037 Spec: k8score.ServiceSpec{ 4038 ClusterIP: testClusterIP, 4039 Ports: []k8score.ServicePort{{Port: 42}}}, 4040 }) 4041 }) 4042 // Expect a call to get the app config and return that it is not found. 4043 mock.EXPECT(). 4044 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "myapp"}, gomock.Not(gomock.Nil()), gomock.Any()). 4045 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error { 4046 app.TypeMeta = metav1.TypeMeta{ 4047 APIVersion: "core.oam.dev/v1alpha2", 4048 Kind: "ApplicationConfiguration", 4049 } 4050 return nil 4051 }) 4052 4053 deleteCertExpectations(mock, "test-space-myapp-cert") 4054 deleteCertSecretExpectations(mock, "test-space-myapp-cert-secret") 4055 createCertSuccessExpectations(mock) 4056 getGatewayForTraitNotFoundExpectations(mock) 4057 createIngressResourceSuccessExpectations(mock) 4058 traitVSNotFoundExpectation(mock) 4059 createIngressResSuccessExpectations(mock, assert) 4060 getMockStatusWriterExpectations(mock, mockStatus) 4061 4062 mock.EXPECT(). 4063 Get(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 4064 DoAndReturn(func(ctx context.Context, key client.ObjectKey, n *k8score.Namespace, opts ...client.GetOption) error { 4065 return nil 4066 }) 4067 // Expect a call to get the destination rule resource related to the ingress trait and return that it is not found. 4068 mock.EXPECT(). 4069 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: fmt.Sprintf(testRule, testTraitName)}, gomock.Not(gomock.Nil()), gomock.Any()). 4070 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "DestinationRule"}, fmt.Sprintf(testRule, testTraitName))) 4071 4072 // Expect a call to create the DestinationRule resource and return success 4073 mock.EXPECT(). 4074 Create(gomock.Any(), gomock.Any(), gomock.Any()). 4075 DoAndReturn(func(ctx context.Context, destinationrule *istioclient.DestinationRule, opts ...client.CreateOption) error { 4076 assert.Equal("test-service.test-space.svc.local", destinationrule.Spec.Host) 4077 lbPolicy := destinationrule.Spec.TrafficPolicy.LoadBalancer.LbPolicy.(*istionet.LoadBalancerSettings_ConsistentHash) 4078 hashKey := lbPolicy.ConsistentHash.HashKey.(*istionet.LoadBalancerSettings_ConsistentHashLB_HttpCookie) 4079 mode := destinationrule.Spec.TrafficPolicy.Tls.Mode 4080 assert.Equal(int64(30), hashKey.HttpCookie.Ttl.Seconds) 4081 assert.Equal(int32(0), hashKey.HttpCookie.Ttl.Nanos) 4082 assert.Equal(istionet.ClientTLSSettings_DISABLE, mode) 4083 return nil 4084 }) 4085 // Expect a call to update the status of the ingress trait. 4086 mockStatus.EXPECT(). 4087 Update(gomock.Any(), gomock.Any(), gomock.Any()). 4088 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 4089 assert.Len(trait.Status.Conditions, 1) 4090 assert.Len(trait.Status.Resources, 4) 4091 return nil 4092 }) 4093 4094 // Create and make the request 4095 request := newRequest(testNamespace, testTraitName) 4096 reconciler := newIngressTraitReconciler(mock) 4097 result, err := reconciler.Reconcile(context.TODO(), request) 4098 4099 // Validate the results 4100 mocker.Finish() 4101 assert.NoError(err) 4102 assert.Equal(true, result.Requeue) 4103 assert.Equal(time.Duration(0), result.RequeueAfter) 4104 } 4105 4106 // TestReconcileKubeSystem tests to make sure we do not reconcile 4107 // Any resource that belong to the kube-system namespace 4108 func TestReconcileKubeSystem(t *testing.T) { 4109 4110 assert := asserts.New(t) 4111 4112 var mocker = gomock.NewController(t) 4113 var cli = mocks.NewMockClient(mocker) 4114 4115 // create a request and reconcile it 4116 request := newRequest(vzconst.KubeSystem, "unit-test-verrazzano-helidon-workload") 4117 reconciler := newIngressTraitReconciler(cli) 4118 result, err := reconciler.Reconcile(context.TODO(), request) 4119 4120 mocker.Finish() 4121 assert.Nil(err) 4122 assert.True(result.IsZero()) 4123 } 4124 4125 // TestUpdateGatewayServersList tests the updateGatewayServersList method for the following use case. 4126 // GIVEN a request to update the gateway servers list for an ingress trait resource 4127 // WHEN no existing Servers are present in the Gateway 4128 // THEN ensure that a new Gateway is appended to the servers list 4129 func TestUpdateGatewayServersList(t *testing.T) { 4130 4131 assert := asserts.New(t) 4132 4133 reconciler := createReconcilerWithFake() 4134 servers := reconciler.updateGatewayServersList([]*istionet.Server{}, &istionet.Server{Name: "server", Port: &istionet.Port{Name: "port"}}) 4135 assert.Len(servers, 1) 4136 assert.Equal("server", servers[0].Name) 4137 } 4138 4139 // TestUpdateGatewayServersUpgrade tests the updateGatewayServersList method for the upgrade to 1.3 case. 4140 // 4141 // Prior to 1.3, a single Server object was used to accumulate all hosts related to an app across all 4142 // IngressTrait definitions. Post-1.3, we replace this with a 1:1 mapping of Server objects to IngressTrait. 4143 // Each Server object will define port settings for all hosts in the IngressTrait and be recomputed on each reconcile. 4144 // 4145 // # On startup, the operator will reconcile all existing IngressTraits which will create the new mappings 4146 // 4147 // GIVEN a request to update the gateway servers list for an ingress trait resource 4148 // WHEN we are upgrading from a release before 1.3 where the Gateway maintains a single Server object for all hosts for an application 4149 // THEN ensure that the resulting servers list contains only an entry for the single IngressTrait being reconciled 4150 func TestUpdateGatewayServersUpgrade(t *testing.T) { 4151 4152 assert := asserts.New(t) 4153 4154 reconciler := createReconcilerWithFake() 4155 4156 trait1Hosts := []string{"trait1host1", "trait1host2"} 4157 4158 existingServerHosts := trait1Hosts 4159 existingServerHosts = append(existingServerHosts, []string{"trait2host1", "trait3host1"}...) 4160 4161 existingServersPre13 := []*istionet.Server{ 4162 { 4163 Hosts: existingServerHosts, 4164 Port: &istionet.Port{ 4165 Name: httpsLower, 4166 Number: 443, 4167 Protocol: httpsProtocol, 4168 }, 4169 }, 4170 } 4171 4172 trait1Server := &istionet.Server{ 4173 Name: testTraitName, 4174 Hosts: trait1Hosts, 4175 Port: &istionet.Port{ 4176 Name: fmt.Sprintf("%s-%s", httpsLower, testTraitName), 4177 Protocol: httpsProtocol, 4178 }, 4179 } 4180 expectedServers := []*istionet.Server{trait1Server} 4181 servers := reconciler.updateGatewayServersList(existingServersPre13, trait1Server) 4182 assert.Len(servers, 1) 4183 assert.Equal(expectedServers, servers) 4184 } 4185 4186 // TestUpdateGatewayServersUpdateTraitHosts tests the updateGatewayServersList 4187 // GIVEN a request to update the gateway servers list 4188 // WHEN when the Server object for existing Trait has an updated hosts list 4189 // THEN ensure the returned Servers list has the new Server for the IngressTrait 4190 func TestUpdateGatewayServersUpdateTraitHosts(t *testing.T) { 4191 4192 assert := asserts.New(t) 4193 4194 reconciler := createReconcilerWithFake() 4195 4196 trait1Hosts := []string{"trait1host1", "trait1host2"} 4197 trait2Hosts := []string{"trait2host1"} 4198 trait3Hosts := []string{"trait3host1", "trait3host2", "trait3host3"} 4199 4200 const trait1Name = "trait1" 4201 const trait2Name = "trait2" 4202 const trait3Name = "trait3" 4203 4204 existingServers := []*istionet.Server{ 4205 createGatewayServer(trait1Name, trait1Hosts), 4206 createGatewayServer(trait2Name, trait2Hosts), 4207 createGatewayServer(trait3Name, trait3Hosts), 4208 } 4209 4210 // Add a host to trait2 4211 updatedTrait2Server := createGatewayServer(trait2Name, append(trait2Hosts, "trait2Host2")) 4212 expectedServers := []*istionet.Server{ 4213 existingServers[0], 4214 updatedTrait2Server, 4215 existingServers[2], 4216 } 4217 servers := reconciler.updateGatewayServersList(existingServers, updatedTrait2Server) 4218 assert.Len(servers, 3) 4219 assert.Equal(expectedServers, servers) 4220 4221 // Prune the new host from trait2 4222 updatedTrait2ServerRemovedHost := createGatewayServer(trait2Name, trait2Hosts) 4223 expectedServers2 := []*istionet.Server{ 4224 existingServers[0], 4225 updatedTrait2ServerRemovedHost, 4226 existingServers[2], 4227 } 4228 servers2 := reconciler.updateGatewayServersList(existingServers, updatedTrait2ServerRemovedHost) 4229 assert.Len(servers2, 3) 4230 assert.Equal(expectedServers2, servers) 4231 4232 } 4233 4234 // TestUpdateGatewayServersNewTraitHost tests the updateGatewayServersList method when a new Trait Server is added 4235 // GIVEN a request to update the gateway servers list 4236 // WHEN we are adding a new IngressTrait 4237 // THEN ensure the returned Servers list has the new Server for the IngressTrait 4238 func TestUpdateGatewayServersNewTraitHost(t *testing.T) { 4239 4240 assert := asserts.New(t) 4241 4242 reconciler := createReconcilerWithFake() 4243 4244 trait1Hosts := []string{"trait1host1", "trait1host2"} 4245 trait2Hosts := []string{"trait2host1"} 4246 trait3Hosts := []string{"trait3host1", "trait3host2", "trait3host3"} 4247 4248 const trait1Name = "trait1" 4249 const trait2Name = "trait2" 4250 const trait3Name = "trait3" 4251 4252 existingServers := []*istionet.Server{ 4253 createGatewayServer(trait1Name, trait1Hosts), 4254 createGatewayServer(trait2Name, trait2Hosts), 4255 } 4256 4257 trait3Server := createGatewayServer(trait3Name, trait3Hosts) 4258 expectedServers := append(existingServers, trait3Server) 4259 4260 servers := reconciler.updateGatewayServersList(existingServers, trait3Server) 4261 assert.Len(servers, 3) 4262 assert.Equal(expectedServers, servers) 4263 } 4264 4265 // TestMutateGatewayAddTrait tests the mutateGateway method 4266 // GIVEN a request to mutate the app gateway 4267 // WHEN a new Trate/TraitRule is added 4268 // THEN ensure the returned Servers list has the new Server for the IngressTrait 4269 func TestMutateGatewayAddTrait(t *testing.T) { 4270 4271 assert := asserts.New(t) 4272 4273 trait1Hosts := []string{"trait1host1", "trait1host2"} 4274 trait2Hosts := []string{"trait2host1"} 4275 4276 const trait1Name = "trait1" 4277 const trait2Name = "trait2" 4278 const secretName = "secretName" 4279 4280 trait1Server := createGatewayServer(trait1Name, trait1Hosts, secretName) 4281 4282 const appName = "myapp" 4283 gw := &istioclient.Gateway{ 4284 ObjectMeta: metav1.ObjectMeta{Name: expectedAppGWName, Namespace: testNamespace}, 4285 Spec: istionet.Gateway{ 4286 Servers: []*istionet.Server{ 4287 trait1Server, 4288 }, 4289 }, 4290 } 4291 4292 trait := &vzapi.IngressTrait{ 4293 ObjectMeta: metav1.ObjectMeta{ 4294 Name: trait2Name, 4295 Namespace: testNamespace, 4296 Labels: map[string]string{ 4297 oam.LabelAppName: appName, 4298 }, 4299 }, 4300 Spec: vzapi.IngressTraitSpec{ 4301 Rules: []vzapi.IngressRule{ 4302 {Hosts: trait2Hosts}, 4303 }, 4304 WorkloadReference: createWorkloadReference(appName), 4305 }, 4306 } 4307 4308 reconciler := setupTraitTestFakes(appName, gw) 4309 _, _, err := reconciler.createOrUpdateChildResources(context.TODO(), trait, vzlog.DefaultLogger()) 4310 assert.NoError(err) 4311 4312 updatedGateway := &istioclient.Gateway{} 4313 assert.NoError(reconciler.Get(context.TODO(), types.NamespacedName{Name: gw.Name, Namespace: testNamespace}, updatedGateway)) 4314 updatedServers := updatedGateway.Spec.Servers 4315 assert.Len(updatedServers, 2) 4316 assert.Equal(updatedServers[0].Hosts, trait1Hosts) 4317 assert.Equal(updatedServers[1].Hosts, trait2Hosts) 4318 4319 } 4320 4321 func createWorkloadReference(appName string) oamrt.TypedReference { 4322 return oamrt.TypedReference{ 4323 APIVersion: "core.oam.dev/v1alpha2", 4324 Kind: "ContainerizedWorkload", 4325 Name: appName, 4326 } 4327 } 4328 4329 // TestMutateGatewayHostsAddRemoveTraitRule tests the createOrUpdateChildResources method 4330 // GIVEN a request to createOrUpdateChildResources 4331 // WHEN a new TraitRule has been added or remvoed to an existing Trait with new hosts 4332 // THEN ensure the gateway Server hosts lists for the Trait has been updated accordingly 4333 func TestMutateGatewayHostsAddRemoveTraitRule(t *testing.T) { 4334 4335 assert := asserts.New(t) 4336 4337 trait1Hosts := []string{"trait1host1", "trait1host2"} 4338 trait1NewHosts := []string{"trait1host3", "trait1host4", "trait1host2"} 4339 trait2Hosts := []string{"trait2host1"} 4340 4341 const trait1Name = "trait1" 4342 const trait2Name = "trait2" 4343 const secretName = "secretName" 4344 4345 trait1Server := createGatewayServer(trait1Name, trait1Hosts, secretName) 4346 trait2Server := createGatewayServer(trait2Name, trait2Hosts, secretName) 4347 4348 const appName = "myapp" 4349 4350 gw := &istioclient.Gateway{ 4351 ObjectMeta: metav1.ObjectMeta{Name: expectedAppGWName, Namespace: testNamespace}, 4352 Spec: istionet.Gateway{ 4353 Servers: []*istionet.Server{ 4354 trait1Server, 4355 trait2Server, 4356 }, 4357 }, 4358 } 4359 4360 reconciler := setupTraitTestFakes(appName, gw) 4361 4362 // Test updating a trait to add hosts 4363 trait1UpdatedHosts := append(trait1Hosts, []string{"trait1host3", "trait1host4"}...) 4364 updatedTrait := &vzapi.IngressTrait{ 4365 ObjectMeta: metav1.ObjectMeta{ 4366 Name: trait1Name, 4367 Namespace: testNamespace, 4368 Labels: map[string]string{ 4369 oam.LabelAppName: appName, 4370 }, 4371 }, 4372 Spec: vzapi.IngressTraitSpec{ 4373 Rules: []vzapi.IngressRule{ 4374 {Hosts: trait1Hosts}, 4375 {Hosts: trait1NewHosts}, 4376 }, 4377 WorkloadReference: createWorkloadReference(appName), 4378 }, 4379 } 4380 4381 _, _, err := reconciler.createOrUpdateChildResources(context.TODO(), updatedTrait, vzlog.DefaultLogger()) 4382 assert.NoError(err) 4383 4384 updatedGateway := &istioclient.Gateway{} 4385 assert.NoError(reconciler.Get(context.TODO(), types.NamespacedName{Name: expectedAppGWName, Namespace: testNamespace}, updatedGateway)) 4386 updatedServers := updatedGateway.Spec.Servers 4387 assert.Len(updatedServers, 2) 4388 assert.Equal(updatedServers[0].Hosts, trait1UpdatedHosts) 4389 assert.Equal(updatedServers[1].Hosts, trait2Hosts) 4390 4391 // Test removing the added rule and that the hosts list is restored 4392 updatedTraitRemovedRule := &vzapi.IngressTrait{ 4393 ObjectMeta: metav1.ObjectMeta{ 4394 Name: trait1Name, 4395 Namespace: testNamespace, 4396 Labels: map[string]string{ 4397 oam.LabelAppName: appName, 4398 }, 4399 }, 4400 Spec: vzapi.IngressTraitSpec{ 4401 Rules: []vzapi.IngressRule{ 4402 {Hosts: trait1Hosts}, 4403 }, 4404 WorkloadReference: createWorkloadReference(appName), 4405 }, 4406 } 4407 _, _, err2 := reconciler.createOrUpdateChildResources(context.TODO(), updatedTraitRemovedRule, vzlog.DefaultLogger()) 4408 assert.NoError(err2) 4409 4410 updatedGatewayRemovedRule := &istioclient.Gateway{} 4411 assert.NoError(reconciler.Get(context.TODO(), types.NamespacedName{Name: expectedAppGWName, Namespace: testNamespace}, updatedGatewayRemovedRule)) 4412 updatedServersRemovedRule := updatedGatewayRemovedRule.Spec.Servers 4413 assert.Len(updatedServers, 2) 4414 assert.Equal(updatedServersRemovedRule[0].Hosts, trait1Hosts) 4415 assert.Equal(updatedServersRemovedRule[1].Hosts, trait2Hosts) 4416 } 4417 4418 func setupTraitTestFakes(appName string, gw *istioclient.Gateway) Reconciler { 4419 appConfig := &v1alpha2.ApplicationConfiguration{ 4420 ObjectMeta: metav1.ObjectMeta{Name: appName, Namespace: testNamespace}, 4421 } 4422 4423 workload := &v1alpha2.ContainerizedWorkload{ 4424 ObjectMeta: metav1.ObjectMeta{ 4425 Name: appName, 4426 Namespace: testNamespace, 4427 UID: testWorkloadID, 4428 }, 4429 } 4430 4431 workloadDef := &v1alpha2.WorkloadDefinition{ 4432 ObjectMeta: metav1.ObjectMeta{Name: "containerizedworkloads.core.oam.dev"}, 4433 Spec: v1alpha2.WorkloadDefinitionSpec{ 4434 ChildResourceKinds: []v1alpha2.ChildResourceKind{ 4435 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 4436 {APIVersion: "v1", Kind: "Service", Selector: nil}, 4437 }, 4438 }, 4439 } 4440 4441 workloadService := &k8score.Service{ 4442 ObjectMeta: metav1.ObjectMeta{ 4443 Name: "testService", 4444 Namespace: testNamespace, 4445 OwnerReferences: []metav1.OwnerReference{{ 4446 APIVersion: "core.oam.dev/v1alpha2", 4447 Kind: "ContainerizedWorkload", 4448 Name: testWorkloadName, 4449 UID: testWorkloadID, 4450 }}}, 4451 Spec: k8score.ServiceSpec{ 4452 ClusterIP: testClusterIP, 4453 Ports: []k8score.ServicePort{{Port: 42}}}, 4454 } 4455 4456 reconciler := createReconcilerWithFake(appConfig, workload, workloadDef, workloadService, gw) 4457 return reconciler 4458 } 4459 func setupAppFakes(appName string) appFakeResources { 4460 appConfig := &v1alpha2.ApplicationConfiguration{ 4461 ObjectMeta: metav1.ObjectMeta{Name: appName, Namespace: testNamespace}, 4462 } 4463 4464 workload := &v1alpha2.ContainerizedWorkload{ 4465 ObjectMeta: metav1.ObjectMeta{ 4466 Name: testWorkloadName, 4467 Namespace: testNamespace, 4468 UID: testWorkloadID, 4469 }, 4470 } 4471 4472 workloadDef := &v1alpha2.WorkloadDefinition{ 4473 ObjectMeta: metav1.ObjectMeta{Name: "containerizedworkloads.core.oam.dev"}, 4474 Spec: v1alpha2.WorkloadDefinitionSpec{ 4475 ChildResourceKinds: []v1alpha2.ChildResourceKind{ 4476 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 4477 {APIVersion: "v1", Kind: "Service", Selector: nil}, 4478 }, 4479 }, 4480 } 4481 4482 workloadService := &k8score.Service{ 4483 ObjectMeta: metav1.ObjectMeta{ 4484 Name: "testService", 4485 Namespace: testNamespace, 4486 OwnerReferences: []metav1.OwnerReference{{ 4487 APIVersion: "core.oam.dev/v1alpha2", 4488 Kind: "ContainerizedWorkload", 4489 Name: testWorkloadName, 4490 UID: testWorkloadID, 4491 }}}, 4492 Spec: k8score.ServiceSpec{ 4493 ClusterIP: testClusterIP, 4494 Ports: []k8score.ServicePort{{Port: 42}}}, 4495 } 4496 4497 return appFakeResources{ 4498 appConfig: appConfig, 4499 workload: workload, 4500 workloadDef: workloadDef, 4501 workloadService: workloadService, 4502 } 4503 } 4504 4505 func getAppFakeResources(appFakes appFakeResources) []client.Object { 4506 var objs []client.Object 4507 objs = append(objs, appFakes.appConfig) 4508 objs = append(objs, appFakes.workload) 4509 objs = append(objs, appFakes.workloadDef) 4510 objs = append(objs, appFakes.workloadService) 4511 return objs 4512 } 4513 4514 func createGatewayServer(traitName string, traitHosts []string, secretName ...string) *istionet.Server { 4515 server := &istionet.Server{ 4516 Name: traitName, 4517 Hosts: traitHosts, 4518 Port: &istionet.Port{ 4519 Name: formatGatewaySeverPortName(traitName), 4520 Number: 443, 4521 Protocol: httpsProtocol, 4522 }, 4523 } 4524 if len(secretName) > 0 { 4525 server.Tls = &istionet.ServerTLSSettings{ 4526 Mode: istionet.ServerTLSSettings_SIMPLE, 4527 CredentialName: secretName[0], 4528 } 4529 } 4530 return server 4531 } 4532 4533 func createIngressResSuccessExpectations(mock *mocks.MockClient, assert *asserts.Assertions) { 4534 // Expect a call to create the ingress resource and return success 4535 mock.EXPECT(). 4536 Create(gomock.Any(), gomock.Any()). 4537 DoAndReturn(func(ctx context.Context, virtualservice *istioclient.VirtualService, opts ...client.CreateOption) error { 4538 assert.Len(virtualservice.Spec.Http, 1) 4539 assert.Len(virtualservice.Spec.Http[0].Route, 1) 4540 assert.Equal("test-service.test-space.svc.local", virtualservice.Spec.Http[0].Route[0].Destination.Host) 4541 return nil 4542 }) 4543 } 4544 4545 func createIngressResourceSuccessExpectations(mock *mocks.MockClient) { 4546 // Expect a call to create the ingress resource and return success 4547 mock.EXPECT(). 4548 Create(gomock.Any(), gomock.Any()). 4549 DoAndReturn(func(ctx context.Context, gateway *istioclient.Gateway, opts ...client.CreateOption) error { 4550 return nil 4551 }) 4552 } 4553 4554 func getGatewayForTraitNotFoundExpectations(mock *mocks.MockClient) { 4555 // Expect a call to get the gateway resource related to the ingress trait and return that it is not found. 4556 mock.EXPECT(). 4557 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: expectedAppGWName}, gomock.Not(gomock.Nil()), gomock.Any()). 4558 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "Gateway"}, expectedAppGWName)) 4559 } 4560 4561 func appCertificateExpectations(mock *mocks.MockClient) { 4562 // Expect a call to get the certificate related to the ingress trait 4563 mock.EXPECT(). 4564 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: "test-space-test-trait-cert"}, gomock.Not(gomock.Nil()), gomock.Any()). 4565 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "Certificate"}, "test-space-test-trait-cert")) 4566 } 4567 4568 func createCertSuccessExpectations(mock *mocks.MockClient) { 4569 // Expect a call to create the certificate and return success 4570 mock.EXPECT(). 4571 Create(gomock.Any(), gomock.Any()). 4572 DoAndReturn(func(ctx context.Context, certificate *certapiv1.Certificate, opts ...client.CreateOption) error { 4573 return nil 4574 }) 4575 } 4576 4577 // Expect a call to delete the certificate 4578 func deleteCertExpectations(mock *mocks.MockClient, certName string) { 4579 oldCert := certapiv1.Certificate{ 4580 ObjectMeta: metav1.ObjectMeta{ 4581 Namespace: constants.IstioSystemNamespace, 4582 Name: certName, 4583 }, 4584 } 4585 mock.EXPECT(). 4586 Delete(gomock.Any(), gomock.Eq(&oldCert), gomock.Any()). 4587 Return(nil) 4588 } 4589 4590 // Expect a call to delete the certificate secret 4591 func deleteCertSecretExpectations(mock *mocks.MockClient, secretName string) { 4592 oldSecret := k8score.Secret{ 4593 ObjectMeta: metav1.ObjectMeta{ 4594 Namespace: constants.IstioSystemNamespace, 4595 Name: secretName, 4596 }, 4597 } 4598 mock.EXPECT(). 4599 Delete(gomock.Any(), gomock.Eq(&oldSecret), gomock.Any()). 4600 Return(nil) 4601 } 4602 4603 func gatewayNotFoundExpectations(mock *mocks.MockClient) { 4604 getGatewayForTraitNotFoundExpectations(mock) 4605 } 4606 4607 func updateMockStatusExpectations(mockStatus *mocks.MockStatusWriter, assert *asserts.Assertions) { 4608 // Expect a call to update the status of the ingress trait. 4609 mockStatus.EXPECT(). 4610 Update(gomock.Any(), gomock.Any()). 4611 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, opts ...client.UpdateOption) error { 4612 assert.Len(trait.Status.Conditions, 1) 4613 assert.Len(trait.Status.Resources, 2) 4614 return nil 4615 }) 4616 } 4617 4618 func getMockStatusWriterExpectations(mock *mocks.MockClient, mockStatus *mocks.MockStatusWriter) { 4619 // Expect a call to get the status writer and return a mock. 4620 mock.EXPECT().Status().Return(mockStatus).AnyTimes() 4621 } 4622 4623 func createVSSuccessExpectations(mock *mocks.MockClient) { 4624 // Expect a call to create the virtual service resource and return success 4625 mock.EXPECT(). 4626 Create(gomock.Any(), gomock.Any()). 4627 DoAndReturn(func(ctx context.Context, virtualservice *istioclient.VirtualService, opts ...client.CreateOption) error { 4628 return nil 4629 }) 4630 } 4631 4632 func traitVSNotFoundExpectation(mock *mocks.MockClient) { 4633 // Expect a call to get the virtual service resource related to the ingress trait and return that it is not found. 4634 mock.EXPECT(). 4635 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: expectedTraitVSName}, gomock.Not(gomock.Nil()), gomock.Any()). 4636 Return(k8serrors.NewNotFound(schema.GroupResource{Group: testNamespace, Resource: "VirtualService"}, expectedTraitVSName)) 4637 } 4638 4639 func createAuthzPolicyRootPathSuccessExpectations(mock *mocks.MockClient, assert *asserts.Assertions, numRules int, numCondtions int) { 4640 // Expect a call to create the authorization policy resource and return success 4641 mock.EXPECT(). 4642 Create(gomock.Any(), gomock.Any()). 4643 DoAndReturn(func(ctx context.Context, authorizationPolicy *v1beta1.AuthorizationPolicy, opts ...client.CreateOption) error { 4644 assert.Equal(expectedAuthzPolicyNameRootPath, authorizationPolicy.Name, "wrong name") 4645 assert.Equal(istioSystemNamespace, authorizationPolicy.Namespace, "wrong namespace") 4646 assert.Len(authorizationPolicy.Spec.Rules, numRules, "wrong number of rules") 4647 for _, rule := range authorizationPolicy.Spec.Rules { 4648 assert.Len(rule.When, numCondtions, "wrong number of conditions") 4649 } 4650 return nil 4651 }) 4652 } 4653 4654 func createAuthzPolicySuccessExpectations(mock *mocks.MockClient, assert *asserts.Assertions, numRules int, numCondtions int) { 4655 // Expect a call to create the authorization policy resource and return success 4656 mock.EXPECT(). 4657 Create(gomock.Any(), gomock.Any()). 4658 DoAndReturn(func(ctx context.Context, authorizationPolicy *v1beta1.AuthorizationPolicy, opts ...client.CreateOption) error { 4659 assert.Equal(expectedAuthzPolicyName, authorizationPolicy.Name, "wrong name") 4660 assert.Equal(istioSystemNamespace, authorizationPolicy.Namespace, "wrong namespace") 4661 assert.Len(authorizationPolicy.Spec.Rules, numRules, "wrong number of rules") 4662 for _, rule := range authorizationPolicy.Spec.Rules { 4663 assert.Len(rule.When, numCondtions, "wrong number of conditions") 4664 } 4665 return nil 4666 }) 4667 } 4668 4669 func traitAuthzPolicyRootPathNotFoundExpectation(mock *mocks.MockClient) { 4670 // Expect a call to get the authorization policy resource for path only related to the ingress trait and return that it is not found. 4671 mock.EXPECT(). 4672 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: expectedAuthzPolicyNameRootPath}, gomock.Not(gomock.Nil()), gomock.Any()). 4673 Return(k8serrors.NewNotFound(schema.GroupResource{Group: istioSystemNamespace, Resource: "AuthorizationPolicy"}, expectedAuthzPolicyNameRootPath)) 4674 } 4675 4676 func traitAuthzPolicyNotFoundExpectation(mock *mocks.MockClient) { 4677 // Expect a call to get the authorization policy resource related to the ingress trait and return that it is not found. 4678 mock.EXPECT(). 4679 Get(gomock.Any(), types.NamespacedName{Namespace: istioSystemNamespace, Name: expectedAuthzPolicyName}, gomock.Not(gomock.Nil()), gomock.Any()). 4680 Return(k8serrors.NewNotFound(schema.GroupResource{Group: istioSystemNamespace, Resource: "AuthorizationPolicy"}, expectedAuthzPolicyName)) 4681 } 4682 4683 func childServiceExpectations(mock *mocks.MockClient, assert *asserts.Assertions) { 4684 // Expect a call to list the child Service resources of the containerized workload definition 4685 mock.EXPECT(). 4686 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 4687 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 4688 assert.Equal("ServiceList", list.GetKind()) 4689 return appendAsUnstructured(list, k8score.Service{ 4690 TypeMeta: metav1.TypeMeta{ 4691 APIVersion: "v1", 4692 Kind: "Service", 4693 }, 4694 ObjectMeta: metav1.ObjectMeta{ 4695 OwnerReferences: []metav1.OwnerReference{{ 4696 APIVersion: "core.oam.dev/v1alpha2", 4697 Kind: "ContainerizedWorkload", 4698 Name: testWorkloadName, 4699 UID: testWorkloadID, 4700 }}}, 4701 Spec: k8score.ServiceSpec{ 4702 ClusterIP: testClusterIP, 4703 Ports: []k8score.ServicePort{{Port: 42}}}, 4704 }) 4705 }) 4706 } 4707 4708 func listChildDeploymentExpectations(mock *mocks.MockClient, assert *asserts.Assertions) { 4709 // Expect a call to list the child Deployment resources of the containerized workload definition 4710 mock.EXPECT(). 4711 List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()). 4712 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 4713 assert.Equal("DeploymentList", list.GetKind()) 4714 return nil 4715 }) 4716 } 4717 4718 func workloadResourceDefinitionExpectations(mock *mocks.MockClient) { 4719 // Expect a call to get the containerized workload resource definition 4720 mock.EXPECT(). 4721 Get(gomock.Any(), types.NamespacedName{Namespace: "", Name: "containerizedworkloads.core.oam.dev"}, gomock.Not(gomock.Nil()), gomock.Any()). 4722 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workloadDef *v1alpha2.WorkloadDefinition, opts ...client.GetOption) error { 4723 workloadDef.Namespace = name.Namespace 4724 workloadDef.Name = name.Name 4725 workloadDef.Spec.ChildResourceKinds = []v1alpha2.ChildResourceKind{ 4726 {APIVersion: "apps/v1", Kind: "Deployment", Selector: nil}, 4727 {APIVersion: "v1", Kind: "Service", Selector: nil}, 4728 } 4729 return nil 4730 }) 4731 } 4732 4733 func workLoadResourceExpectations(mock *mocks.MockClient) { 4734 // Expect a call to get the containerized workload resource 4735 mock.EXPECT(). 4736 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testWorkloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 4737 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *unstructured.Unstructured, opts ...client.GetOption) error { 4738 workload.SetAPIVersion("core.oam.dev/v1alpha2") 4739 workload.SetKind("ContainerizedWorkload") 4740 workload.SetNamespace(name.Namespace) 4741 workload.SetName(name.Name) 4742 workload.SetUID(testWorkloadID) 4743 return nil 4744 }) 4745 } 4746 4747 func getIngressTraitResourceExpectations(mock *mocks.MockClient, assert *asserts.Assertions) { 4748 // Expect a call to get the ingress trait resource. 4749 mock.EXPECT(). 4750 Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: testTraitName}, gomock.Not(gomock.Nil()), gomock.Any()). 4751 DoAndReturn(func(ctx context.Context, name types.NamespacedName, trait *vzapi.IngressTrait, opts ...client.GetOption) error { 4752 trait.TypeMeta = metav1.TypeMeta{ 4753 APIVersion: apiVersion, 4754 Kind: traitKind} 4755 trait.ObjectMeta = metav1.ObjectMeta{ 4756 Namespace: name.Namespace, 4757 Name: name.Name, 4758 Labels: map[string]string{oam.LabelAppName: "myapp", oam.LabelAppComponent: "mycomp"}} 4759 trait.Spec.Rules = []vzapi.IngressRule{{ 4760 Hosts: []string{"test-host"}, 4761 Paths: []vzapi.IngressPath{{Path: "test-path"}}}} 4762 trait.Spec.WorkloadReference = oamrt.TypedReference{ 4763 APIVersion: "core.oam.dev/v1alpha2", 4764 Kind: "ContainerizedWorkload", 4765 Name: testWorkloadName} 4766 return nil 4767 }) 4768 // Expect a call to update the ingress trait resource with a finalizer. 4769 mock.EXPECT(). 4770 Update(gomock.Any(), gomock.Any(), gomock.Any()). 4771 DoAndReturn(func(ctx context.Context, trait *vzapi.IngressTrait, options ...client.UpdateOption) error { 4772 assert.Equal(testNamespace, trait.Namespace) 4773 assert.Equal(testTraitName, trait.Name) 4774 assert.Len(trait.Finalizers, 1) 4775 assert.Equal(finalizerName, trait.Finalizers[0]) 4776 return nil 4777 }) 4778 } 4779 4780 // TestIngressTraitIsDeleted tests the Reconcile method for the following use case 4781 // GIVEN a request to Reconcile the controller 4782 // WHEN the IngressTrait is found as being deleted 4783 // THEN cert and secret are deleted and gateway spec is cleaned up 4784 func TestIngressTraitIsDeleted(t *testing.T) { 4785 4786 assert := asserts.New(t) 4787 cli := fake.NewClientBuilder().WithScheme(newScheme()).Build() 4788 params := map[string]string{ 4789 "NAMESPACE_NAME": "test-namespace", 4790 "APPCONF_NAME": "test-appconf", 4791 "APPCONF_NAMESPACE": "test-namespace", 4792 "COMPONENT_NAME": "test-comp", 4793 "COMPONENT_NAMESPACE": "test-namespace", 4794 "TRAIT_NAME": "test-trait", 4795 "TRAIT_NAMESPACE": "test-namespace", 4796 "WORKLOAD_NAME": "test-workload", 4797 "WORKLOAD_NAMESPACE": "test-namespace", 4798 "WORKLOAD_KIND": "VerrazzanoWebLogicWorkload", 4799 "DOMAIN_NAME": "test-domain", 4800 "DOMAIN_NAMESPACE": "test-namespace", 4801 "DOMAIN_UID": "test-domain-uid", 4802 } 4803 istioNs := &k8score.Namespace{ 4804 TypeMeta: metav1.TypeMeta{ 4805 Kind: "Namespace", 4806 }, 4807 ObjectMeta: metav1.ObjectMeta{ 4808 Name: constants.IstioSystemNamespace, 4809 }, 4810 Spec: k8score.NamespaceSpec{}, 4811 Status: k8score.NamespaceStatus{}, 4812 } 4813 assert.NoError(cli.Create(context.TODO(), istioNs)) 4814 // Create Namespace 4815 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/managed_namespace.yaml", params)) 4816 // Create trait 4817 assert.NoError(createResourceFromTemplate(cli, "testdata/templates/ingress_trait_instance.yaml", params)) 4818 trait := &vzapi.IngressTrait{} 4819 assert.NoError(cli.Get(context.TODO(), types.NamespacedName{Namespace: params["TRAIT_NAMESPACE"], Name: params["TRAIT_NAME"]}, trait)) 4820 trait.Finalizers = []string{finalizerName} 4821 trait.DeletionTimestamp = &metav1.Time{Time: time.Now()} 4822 assert.NoError(cli.Update(context.TODO(), trait)) 4823 tt := vzapi.IngressTrait{} 4824 assert.NoError(cli.Get(context.TODO(), types.NamespacedName{Namespace: trait.Namespace, Name: trait.Name}, &tt)) 4825 assert.True(isIngressTraitBeingDeleted(&tt)) 4826 4827 sec := &k8score.Secret{ 4828 TypeMeta: metav1.TypeMeta{ 4829 Kind: "Secret", 4830 }, 4831 ObjectMeta: metav1.ObjectMeta{ 4832 Name: buildCertificateSecretName(trait), 4833 Namespace: constants.IstioSystemNamespace, 4834 }, 4835 Immutable: nil, 4836 Data: nil, 4837 StringData: nil, 4838 Type: "", 4839 } 4840 assert.NoError(cli.Create(context.TODO(), sec)) 4841 4842 cert := &certapiv1.Certificate{ 4843 TypeMeta: metav1.TypeMeta{ 4844 Kind: "Cerificate", 4845 }, 4846 ObjectMeta: metav1.ObjectMeta{ 4847 Name: buildCertificateName(trait), 4848 Namespace: istioSystemNamespace, 4849 }, 4850 Spec: certapiv1.CertificateSpec{}, 4851 Status: certapiv1.CertificateStatus{}, 4852 } 4853 assert.NoError(cli.Create(context.TODO(), cert)) 4854 4855 gwName := fmt.Sprintf("%s-%s-gw", trait.Namespace, trait.Labels[oam.LabelAppName]) 4856 server := []*istionet.Server{ 4857 { 4858 Name: trait.Name, 4859 }, 4860 } 4861 gw := &istioclient.Gateway{ 4862 TypeMeta: metav1.TypeMeta{ 4863 APIVersion: gatewayAPIVersion, 4864 Kind: gatewayKind, 4865 }, 4866 ObjectMeta: metav1.ObjectMeta{ 4867 Name: gwName, 4868 Namespace: trait.Namespace, 4869 }, 4870 Spec: istionet.Gateway{ 4871 Servers: server, 4872 }, 4873 Status: v1alpha1.IstioStatus{}, 4874 } 4875 assert.NoError(cli.Create(context.TODO(), gw)) 4876 reconciler := newIngressTraitReconciler(cli) 4877 request := newRequest(trait.Namespace, trait.Name) 4878 res, err := reconciler.Reconcile(context.TODO(), request) 4879 assert.NoError(err) 4880 assert.Equal(res.Requeue, false) 4881 4882 gw1 := &istioclient.Gateway{} 4883 assert.NoError(cli.Get(context.TODO(), types.NamespacedName{Name: gw.Name, Namespace: gw.Namespace}, gw1)) 4884 assert.Len(gw1.Spec.Servers, 0) 4885 4886 sec1 := k8score.Secret{} 4887 assert.True(k8serrors.IsNotFound(cli.Get(context.TODO(), types.NamespacedName{Namespace: sec.Namespace, Name: sec.Name}, &sec1))) 4888 4889 cert1 := certapiv1.Certificate{} 4890 assert.True(k8serrors.IsNotFound(cli.Get(context.TODO(), types.NamespacedName{Namespace: cert.Namespace, Name: cert.Name}, &cert1))) 4891 4892 trait1 := vzapi.IngressTrait{} 4893 assert.True(k8serrors.IsNotFound(cli.Get(context.TODO(), types.NamespacedName{Namespace: trait.Namespace, Name: trait.Name}, &trait1))) 4894 } 4895 4896 func createReconcilerWithFake(initObjs ...client.Object) Reconciler { 4897 cli := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(initObjs...).Build() 4898 reconciler := newIngressTraitReconciler(cli) 4899 return reconciler 4900 } 4901 4902 // TestReconcileFailed tests to make sure the failure metric is being exposed 4903 func TestReconcileFailed(t *testing.T) { 4904 4905 assert := asserts.New(t) 4906 cli := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build() 4907 // Create a request and reconcile it 4908 reconciler := newIngressTraitReconciler(cli) 4909 request := newRequest(testNamespace, "Test-Name") 4910 reconcileerrorCounterObject, err := metricsexporter.GetSimpleCounterMetric(metricsexporter.IngresstraitReconcileError) 4911 assert.NoError(err) 4912 // Expect a call to fetch the error 4913 reconcileFailedCounterBefore := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 4914 reconcileerrorCounterObject.Get().Inc() 4915 reconciler.Reconcile(context.TODO(), request) 4916 reconcileFailedCounterAfter := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 4917 assert.Equal(reconcileFailedCounterBefore, reconcileFailedCounterAfter-1) 4918 4919 }