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  }