github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/helidonworkload/helidonworkload_controller_test.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package helidonworkload
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    14  
    15  	"github.com/go-logr/logr"
    16  	"github.com/prometheus/client_golang/prometheus/testutil"
    17  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    18  
    19  	oamapi "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    20  	"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
    21  	"github.com/golang/mock/gomock"
    22  	asserts "github.com/stretchr/testify/assert"
    23  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    24  	"github.com/verrazzano/verrazzano/application-operator/controllers/logging"
    25  	"github.com/verrazzano/verrazzano/application-operator/controllers/metricstrait"
    26  	"github.com/verrazzano/verrazzano/application-operator/metricsexporter"
    27  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    28  	"go.uber.org/zap"
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/intstr"
    38  	k8scheme "k8s.io/client-go/kubernetes/scheme"
    39  	ctrl "sigs.k8s.io/controller-runtime"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    42  	"sigs.k8s.io/yaml"
    43  )
    44  
    45  const (
    46  	namespace          = "unit-test-namespace"
    47  	testRestartVersion = "new-restart"
    48  	helidonWorkload    = "testdata/templates/helidon_workload.yaml"
    49  )
    50  
    51  // TestReconcilerSetupWithManager test the creation of the VerrazzanoHelidonWorkload reconciler.
    52  // GIVEN a controller implementation
    53  // WHEN the controller is created
    54  // THEN verify no error is returned
    55  func TestReconcilerSetupWithManager(t *testing.T) {
    56  	assert := asserts.New(t)
    57  
    58  	var mocker *gomock.Controller
    59  	var mgr *mocks.MockManager
    60  	var cli *mocks.MockClient
    61  	var scheme *runtime.Scheme
    62  	var reconciler Reconciler
    63  	var err error
    64  
    65  	mocker = gomock.NewController(t)
    66  	mgr = mocks.NewMockManager(mocker)
    67  	cli = mocks.NewMockClient(mocker)
    68  	scheme = runtime.NewScheme()
    69  	_ = vzapi.AddToScheme(scheme)
    70  	reconciler = Reconciler{Client: cli, Scheme: scheme}
    71  	mgr.EXPECT().GetControllerOptions().AnyTimes()
    72  	mgr.EXPECT().GetScheme().Return(scheme)
    73  	mgr.EXPECT().GetLogger().Return(logr.Discard())
    74  	mgr.EXPECT().SetFields(gomock.Any()).Return(nil).AnyTimes()
    75  	mgr.EXPECT().Add(gomock.Any()).Return(nil).AnyTimes()
    76  	err = reconciler.SetupWithManager(mgr)
    77  	mocker.Finish()
    78  	assert.NoError(err)
    79  }
    80  
    81  // TestReconcileWorkloadNotFound tests reconciling a VerrazzanoHelidonWorkload when the workload
    82  // cannot be fetched. This happens when the workload has been deleted by the OAM runtime.
    83  // GIVEN a VerrazzanoHelidonWorkload resource has been deleted
    84  // WHEN the controller Reconcile function is called and we attempt to fetch the workload
    85  // THEN return success from the controller as there is nothing more to do
    86  func TestReconcileWorkloadNotFound(t *testing.T) {
    87  
    88  	assert := asserts.New(t)
    89  
    90  	var mocker = gomock.NewController(t)
    91  	var cli = mocks.NewMockClient(mocker)
    92  
    93  	// expect a call to fetch the VerrazzanoHelidonWorkload
    94  	cli.EXPECT().
    95  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
    96  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
    97  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "")
    98  		})
    99  
   100  	// create a request and reconcile it
   101  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   102  	reconciler := newReconciler(cli)
   103  	result, err := reconciler.Reconcile(context.TODO(), request)
   104  
   105  	mocker.Finish()
   106  	assert.NoError(err)
   107  	assert.Equal(false, result.Requeue)
   108  }
   109  
   110  // TestReconcileFetchWorkloadError tests reconciling a VerrazzanoHelidonWorkload when the workload
   111  // cannot be fetched due to an unexpected error.
   112  // GIVEN a VerrazzanoHelidonWorkload resource has been created
   113  // WHEN the controller Reconcile function is called and we attempt to fetch the workload and get an error
   114  // THEN return the error
   115  func TestReconcileFetchWorkloadError(t *testing.T) {
   116  
   117  	assert := asserts.New(t)
   118  
   119  	var mocker = gomock.NewController(t)
   120  	var cli = mocks.NewMockClient(mocker)
   121  
   122  	// expect a call to fetch the VerrazzanoHelidonWorkload
   123  	cli.EXPECT().
   124  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   125  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   126  			return k8serrors.NewBadRequest("An error has occurred")
   127  		})
   128  
   129  	// create a request and reconcile it
   130  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   131  	reconciler := newReconciler(cli)
   132  	result, err := reconciler.Reconcile(context.TODO(), request)
   133  
   134  	mocker.Finish()
   135  	assert.Nil(err)
   136  	assert.True(result.Requeue)
   137  }
   138  
   139  // TestReconcileWorkloadMissingData tests reconciling a VerrazzanoHelidonWorkload when the workload
   140  // can be fetched but doesn't contain all required data.
   141  // GIVEN a VerrazzanoHelidonWorkload resource has been created
   142  // WHEN the controller Reconcile function is called and we attempt to validate the workload and get an error
   143  // THEN return the error
   144  func TestReconcileWorkloadMissingData(t *testing.T) {
   145  
   146  	assert := asserts.New(t)
   147  	var mocker = gomock.NewController(t)
   148  	var cli = mocks.NewMockClient(mocker)
   149  
   150  	appConfigName := "unit-test-app-config"
   151  	componentName := "unit-test-component"
   152  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName}
   153  	helidonTestContainerPort := corev1.ContainerPort{
   154  		ContainerPort: 8080,
   155  		Name:          "http",
   156  	}
   157  	helidonTestContainer := corev1.Container{
   158  		Name:  "hello-helidon-container-new",
   159  		Image: "ghcr.io/verrazzano/example-helidon-greet-app-v1:1.0.0-1-20211215184123-0a1b633",
   160  		Ports: []corev1.ContainerPort{
   161  			helidonTestContainerPort,
   162  		},
   163  	}
   164  	deploymentTemplate := &vzapi.DeploymentTemplate{
   165  		Metadata: metav1.ObjectMeta{
   166  			Namespace: namespace,
   167  			Labels: map[string]string{
   168  				"app": "hello-helidon-deploy-new",
   169  			},
   170  		},
   171  		PodSpec: corev1.PodSpec{
   172  			Containers: []corev1.Container{
   173  				helidonTestContainer,
   174  			},
   175  		},
   176  	}
   177  
   178  	// expect a call to fetch the VerrazzanoHelidonWorkload
   179  	cli.EXPECT().
   180  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   181  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   182  			workload.Spec.DeploymentTemplate = *deploymentTemplate
   183  			workload.ObjectMeta.Labels = labels
   184  			workload.APIVersion = vzapi.SchemeGroupVersion.String()
   185  			workload.Kind = "VerrazzanoHelidonWorkload"
   186  			workload.Namespace = namespace
   187  			return nil
   188  		})
   189  
   190  	// create a request and reconcile it
   191  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   192  	reconciler := newReconciler(cli)
   193  	result, err := reconciler.Reconcile(context.TODO(), request)
   194  
   195  	mocker.Finish()
   196  	assert.Nil(err)
   197  	assert.True(result.Requeue)
   198  }
   199  
   200  // TestReconcileCreateHelidon tests the basic happy path of reconciling a VerrazzanoHelidonWorkload. We
   201  // expect to write out a Deployment and Service but we aren't adding logging or any other scopes or traits.
   202  // GIVEN a VerrazzanoHelidonWorkload resource is created
   203  // WHEN the controller Reconcile function is called
   204  // THEN expect a Deployment and Service to be written
   205  func TestReconcileCreateHelidon(t *testing.T) {
   206  
   207  	assert := asserts.New(t)
   208  	var mocker = gomock.NewController(t)
   209  	var cli = mocks.NewMockClient(mocker)
   210  	mockStatus := mocks.NewMockStatusWriter(mocker)
   211  
   212  	appConfigName := "unit-test-app-config"
   213  	componentName := "unit-test-component"
   214  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName}
   215  	helidonTestContainerPort := corev1.ContainerPort{
   216  		ContainerPort: 8080,
   217  		Name:          "http",
   218  	}
   219  	helidonTestContainer := corev1.Container{
   220  		Name:  "hello-helidon-container-new",
   221  		Image: "ghcr.io/verrazzano/example-helidon-greet-app-v1:1.0.0-1-20211215184123-0a1b633",
   222  		Ports: []corev1.ContainerPort{
   223  			helidonTestContainerPort,
   224  		},
   225  	}
   226  
   227  	deploymentTemplate := &vzapi.DeploymentTemplate{
   228  		Metadata: metav1.ObjectMeta{
   229  			Name:      "hello-helidon-deployment-new",
   230  			Namespace: namespace,
   231  			Labels: map[string]string{
   232  				"app": "hello-helidon-deploy-new",
   233  			},
   234  		},
   235  		PodSpec: corev1.PodSpec{
   236  			Containers: []corev1.Container{
   237  				helidonTestContainer,
   238  			},
   239  		},
   240  		Selector: metav1.LabelSelector{
   241  			MatchLabels: map[string]string{
   242  				"app": "hello-helidon",
   243  			},
   244  			MatchExpressions: []metav1.LabelSelectorRequirement{{
   245  				Key:      "app",
   246  				Operator: "In",
   247  				Values:   []string{"hello-helidon"},
   248  			}},
   249  		},
   250  	}
   251  	// expect call to fetch existing deployment
   252  	cli.EXPECT().
   253  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "hello-helidon-deployment-new"}, gomock.Not(gomock.Nil()), gomock.Any()).
   254  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
   255  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test")
   256  		})
   257  	// expect a call to fetch the application configuration
   258  	cli.EXPECT().
   259  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-app-config"}, gomock.Not(gomock.Nil()), gomock.Any()).
   260  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
   261  			appconf.Namespace = name.Namespace
   262  			appconf.Name = name.Name
   263  			appconf.APIVersion = oamapi.SchemeGroupVersion.String()
   264  			appconf.Kind = oamapi.ApplicationConfigurationKind
   265  			appconf.Spec.Components = []oamapi.ApplicationConfigurationComponent{{ComponentName: "unit-test-component"}}
   266  			return nil
   267  		})
   268  	// expect a call to fetch the VerrazzanoHelidonWorkload
   269  	cli.EXPECT().
   270  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   271  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   272  			workload.Spec.DeploymentTemplate = *deploymentTemplate
   273  			workload.ObjectMeta.Labels = labels
   274  			workload.APIVersion = vzapi.SchemeGroupVersion.String()
   275  			workload.Kind = "VerrazzanoHelidonWorkload"
   276  			workload.Namespace = namespace
   277  			return nil
   278  		})
   279  	// expect a call to create the Deployment
   280  	cli.EXPECT().
   281  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   282  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
   283  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
   284  			assert.Equal(deploymentKind, deploy.Kind)
   285  			// make sure the OAM component and app name labels were copied
   286  			expectedLabels := map[string]string{
   287  				"app":                 "hello-helidon-deploy-new",
   288  				oam.LabelAppName:      "unit-test-app-config",
   289  				oam.LabelAppComponent: "unit-test-component"}
   290  			assert.Equal(expectedLabels, deploy.GetLabels())
   291  			assert.Equal([]corev1.Container{
   292  				helidonTestContainer,
   293  			}, deploy.Spec.Template.Spec.Containers)
   294  			return nil
   295  		})
   296  	// expect a call to create the Service
   297  	cli.EXPECT().
   298  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   299  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
   300  			// make sure the OAM component and app name labels were copied
   301  			assert.Equal("unit-test-app-config", service.Labels[oam.LabelAppName])
   302  			assert.Equal("unit-test-component", service.Labels[oam.LabelAppComponent])
   303  			assert.Equal(serviceAPIVersion, service.APIVersion)
   304  			assert.Equal(serviceKind, service.Kind)
   305  			return nil
   306  		})
   307  	// expect a call to status update
   308  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
   309  	mockStatus.EXPECT().
   310  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   311  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
   312  			assert.Len(workload.Status.Resources, 2)
   313  			return nil
   314  		})
   315  
   316  	// create a request and reconcile it
   317  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   318  	reconciler := newReconciler(cli)
   319  	result, err := reconciler.Reconcile(context.TODO(), request)
   320  
   321  	mocker.Finish()
   322  	assert.NoError(err)
   323  	assert.Equal(false, result.Requeue)
   324  }
   325  
   326  // TestReconcileCreateHelidonWithServiceTemplate tests the basic happy path of reconciling a VerrazzanoHelidonWorkload. We
   327  // expect to write out a Deployment and Service from the but we aren't adding logging or any other scopes or traits.
   328  // GIVEN a VerrazzanoHelidonWorkload with a ServiceTemplate
   329  // WHEN the controller Reconcile function is called
   330  // THEN expect a Deployment and Service to be written using the ServiceTemplate
   331  func TestReconcileCreateHelidonWithServiceTemplate(t *testing.T) {
   332  
   333  	assert := asserts.New(t)
   334  	var mocker = gomock.NewController(t)
   335  	var cli = mocks.NewMockClient(mocker)
   336  	mockStatus := mocks.NewMockStatusWriter(mocker)
   337  
   338  	appConfigName := "unit-test-app-config"
   339  	componentName := "unit-test-component"
   340  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName}
   341  	helidonTestContainerPort := corev1.ContainerPort{
   342  		ContainerPort: 8080,
   343  		Name:          "http",
   344  	}
   345  	helidonTestContainer := corev1.Container{
   346  		Name:  "hello-helidon-container-new",
   347  		Image: "ghcr.io/verrazzano/example-helidon-greet-app-v1:1.0.0-1-20211215184123-0a1b633",
   348  		Ports: []corev1.ContainerPort{
   349  			helidonTestContainerPort,
   350  		},
   351  	}
   352  	testServicePort := corev1.ServicePort{
   353  		Name:       "test-port",
   354  		Port:       1111,
   355  		TargetPort: intstr.FromInt(2222),
   356  		Protocol:   corev1.ProtocolTCP,
   357  	}
   358  
   359  	deploymentTemplate := &vzapi.DeploymentTemplate{
   360  		Metadata: metav1.ObjectMeta{
   361  			Name:      "hello-helidon-deployment-new",
   362  			Namespace: namespace,
   363  			Labels: map[string]string{
   364  				"app": "hello-helidon-deploy-new",
   365  			},
   366  		},
   367  		PodSpec: corev1.PodSpec{
   368  			Containers: []corev1.Container{
   369  				helidonTestContainer,
   370  			},
   371  		},
   372  		Selector: metav1.LabelSelector{
   373  			MatchLabels: map[string]string{
   374  				"app": "hello-helidon",
   375  			},
   376  			MatchExpressions: []metav1.LabelSelectorRequirement{{
   377  				Key:      "app",
   378  				Operator: "In",
   379  				Values:   []string{"hello-helidon"},
   380  			}},
   381  		},
   382  	}
   383  
   384  	serviceTemplate := &vzapi.ServiceTemplate{
   385  		Metadata: metav1.ObjectMeta{
   386  			Labels:      map[string]string{"test-key": "test-val"},
   387  			Annotations: map[string]string{"test-key": "test-val"},
   388  		},
   389  		ServiceSpec: corev1.ServiceSpec{
   390  			Ports: []corev1.ServicePort{testServicePort},
   391  		},
   392  	}
   393  
   394  	// expect call to fetch existing deployment
   395  	cli.EXPECT().
   396  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "hello-helidon-deployment-new"}, gomock.Not(gomock.Nil()), gomock.Any()).
   397  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
   398  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test")
   399  		})
   400  	// expect a call to fetch the application configuration
   401  	cli.EXPECT().
   402  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-app-config"}, gomock.Not(gomock.Nil()), gomock.Any()).
   403  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
   404  			appconf.Namespace = name.Namespace
   405  			appconf.Name = name.Name
   406  			appconf.APIVersion = oamapi.SchemeGroupVersion.String()
   407  			appconf.Kind = oamapi.ApplicationConfigurationKind
   408  			appconf.Spec.Components = []oamapi.ApplicationConfigurationComponent{{ComponentName: "unit-test-component"}}
   409  			return nil
   410  		})
   411  	// expect a call to fetch the VerrazzanoHelidonWorkload
   412  	cli.EXPECT().
   413  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   414  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   415  			workload.Spec.DeploymentTemplate = *deploymentTemplate
   416  			workload.Spec.ServiceTemplate = *serviceTemplate
   417  			workload.ObjectMeta.Labels = labels
   418  			workload.APIVersion = vzapi.SchemeGroupVersion.String()
   419  			workload.Kind = "VerrazzanoHelidonWorkload"
   420  			workload.Namespace = namespace
   421  			return nil
   422  		})
   423  	// expect a call to create the Deployment
   424  	cli.EXPECT().
   425  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   426  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
   427  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
   428  			assert.Equal(deploymentKind, deploy.Kind)
   429  			// make sure the OAM component and app name labels were copied
   430  			assert.Equal(map[string]string{"app": "hello-helidon-deploy-new", "app.oam.dev/component": "unit-test-component", "app.oam.dev/name": "unit-test-app-config"}, deploy.GetLabels())
   431  			assert.Equal([]corev1.Container{
   432  				helidonTestContainer,
   433  			}, deploy.Spec.Template.Spec.Containers)
   434  			return nil
   435  		})
   436  	// expect a call to create the Service
   437  	cli.EXPECT().
   438  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   439  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
   440  			assert.Equal(serviceAPIVersion, service.APIVersion)
   441  			assert.Equal(serviceKind, service.Kind)
   442  			assert.Equal(deploymentTemplate.Metadata.Name, service.GetName())
   443  			assert.Equal(deploymentTemplate.Metadata.Namespace, service.GetNamespace())
   444  			assert.Contains(service.GetLabels(), labelKey)
   445  			assert.Contains(service.GetLabels(), "test-key")
   446  			assert.Contains(service.GetAnnotations(), "test-key")
   447  			assert.Contains(service.Spec.Selector, labelKey)
   448  			assert.Equal(service.Spec.Ports[0], testServicePort)
   449  			assert.Equal(service.Spec.Type, corev1.ServiceTypeClusterIP)
   450  			return nil
   451  		})
   452  	// expect a call to status update
   453  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
   454  	mockStatus.EXPECT().
   455  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   456  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
   457  			assert.Len(workload.Status.Resources, 2)
   458  			return nil
   459  		})
   460  
   461  	// create a request and reconcile it
   462  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   463  	reconciler := newReconciler(cli)
   464  	result, err := reconciler.Reconcile(context.TODO(), request)
   465  
   466  	mocker.Finish()
   467  	assert.NoError(err)
   468  	assert.Equal(false, result.Requeue)
   469  }
   470  
   471  // TestReconcileCreateHelidonWithMultipleContainers tests the basic happy path of reconciling a VerrazzanoHelidonWorkload with multiple containers.
   472  // We expect to write out a Deployment and Service but we aren't adding logging or any other scopes or traits.
   473  // GIVEN a VerrazzanoHelidonWorkload resource is created
   474  // AND that the workload has multiple containers
   475  // WHEN the controller Reconcile function is called
   476  // THEN expect a Deployment and Service to be written with multiple containers
   477  func TestReconcileCreateHelidonWithMultipleContainers(t *testing.T) {
   478  
   479  	assert := asserts.New(t)
   480  	var mocker = gomock.NewController(t)
   481  	var cli = mocks.NewMockClient(mocker)
   482  	mockStatus := mocks.NewMockStatusWriter(mocker)
   483  
   484  	appConfigName := "unit-test-app-config"
   485  	componentName := "unit-test-component"
   486  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName}
   487  	helidonTestContainerPort := corev1.ContainerPort{
   488  		ContainerPort: 8080,
   489  		Name:          "http",
   490  	}
   491  	helidonTestContainerPort2 := corev1.ContainerPort{
   492  		ContainerPort: 8081,
   493  		Name:          "udp",
   494  		Protocol:      corev1.ProtocolUDP,
   495  	}
   496  	helidonTestContainer := corev1.Container{
   497  		Name:  "hello-helidon-container-new",
   498  		Image: "ghcr.io/verrazzano/example-helidon-greet-app-v1:1.0.0-1-20211215184123-0a1b633",
   499  		Ports: []corev1.ContainerPort{
   500  			helidonTestContainerPort,
   501  		},
   502  	}
   503  	helidonTestContainer2 := corev1.Container{
   504  		Name:  "hello-helidon-container-new2",
   505  		Image: "ghcr.io/verrazzano/example-helidon-greet-app-v1:1.0.0-1-20211215184123-0a1b633",
   506  		Ports: []corev1.ContainerPort{
   507  			helidonTestContainerPort2,
   508  		},
   509  	}
   510  	deploymentTemplate := &vzapi.DeploymentTemplate{
   511  		Metadata: metav1.ObjectMeta{
   512  			Name:      "hello-helidon-deployment-new",
   513  			Namespace: namespace,
   514  			Labels: map[string]string{
   515  				"app": "hello-helidon-deploy-new",
   516  			},
   517  		},
   518  		PodSpec: corev1.PodSpec{
   519  			Containers: []corev1.Container{
   520  				helidonTestContainer,
   521  				helidonTestContainer2,
   522  			},
   523  		},
   524  	}
   525  
   526  	// expect call to fetch existing deployment
   527  	cli.EXPECT().
   528  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "hello-helidon-deployment-new"}, gomock.Not(gomock.Nil()), gomock.Any()).
   529  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
   530  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test")
   531  		})
   532  	// expect a call to fetch the application configuration
   533  	cli.EXPECT().
   534  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-app-config"}, gomock.Not(gomock.Nil()), gomock.Any()).
   535  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
   536  			appconf.Namespace = name.Namespace
   537  			appconf.Name = name.Name
   538  			appconf.APIVersion = oamapi.SchemeGroupVersion.String()
   539  			appconf.Kind = oamapi.ApplicationConfigurationKind
   540  			appconf.Spec.Components = []oamapi.ApplicationConfigurationComponent{{ComponentName: "unit-test-component"}}
   541  			return nil
   542  		})
   543  	// expect a call to fetch the VerrazzanoHelidonWorkload
   544  	cli.EXPECT().
   545  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   546  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   547  			workload.Spec.DeploymentTemplate = *deploymentTemplate
   548  			workload.ObjectMeta.Labels = labels
   549  			workload.APIVersion = vzapi.SchemeGroupVersion.String()
   550  			workload.Kind = "VerrazzanoHelidonWorkload"
   551  			workload.Namespace = namespace
   552  			return nil
   553  		})
   554  	// expect a call to create the Deployment
   555  	cli.EXPECT().
   556  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   557  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
   558  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
   559  			assert.Equal(deploymentKind, deploy.Kind)
   560  			// make sure the OAM component and app name labels were copied
   561  			assert.Equal(map[string]string{"app": "hello-helidon-deploy-new", "app.oam.dev/component": "unit-test-component", "app.oam.dev/name": "unit-test-app-config"}, deploy.GetLabels())
   562  			assert.Equal([]corev1.Container{
   563  				helidonTestContainer,
   564  				helidonTestContainer2,
   565  			}, deploy.Spec.Template.Spec.Containers)
   566  
   567  			return nil
   568  		})
   569  	// expect a call to create the Service
   570  	cli.EXPECT().
   571  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   572  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
   573  			assert.Equal(serviceAPIVersion, service.APIVersion)
   574  			assert.Equal(serviceKind, service.Kind)
   575  			assert.Equal(service.Spec.Ports[0].Name, helidonTestContainer.Name+"-"+strconv.FormatInt(int64(helidonTestContainer.Ports[0].ContainerPort), 10))
   576  			assert.Equal(service.Spec.Ports[0].Port, helidonTestContainer.Ports[0].ContainerPort)
   577  			assert.Equal(service.Spec.Ports[0].TargetPort, intstr.FromInt(int(helidonTestContainer.Ports[0].ContainerPort)))
   578  			assert.Equal(service.Spec.Ports[0].Protocol, corev1.ProtocolTCP)
   579  			assert.Equal(service.Spec.Ports[1].Name, helidonTestContainer2.Name+"-"+strconv.FormatInt(int64(helidonTestContainer2.Ports[0].ContainerPort), 10))
   580  			assert.Equal(service.Spec.Ports[1].Port, helidonTestContainer2.Ports[0].ContainerPort)
   581  			assert.Equal(service.Spec.Ports[1].TargetPort, intstr.FromInt(int(helidonTestContainer2.Ports[0].ContainerPort)))
   582  			assert.Equal(service.Spec.Ports[1].Protocol, helidonTestContainer2.Ports[0].Protocol)
   583  			return nil
   584  		})
   585  	// expect a call to status update
   586  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
   587  	mockStatus.EXPECT().
   588  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   589  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
   590  			assert.Len(workload.Status.Resources, 2)
   591  			return nil
   592  		})
   593  
   594  	// create a request and reconcile it
   595  	request := newRequest(namespace, "unit-test-verrazzano-helidon-workload")
   596  	reconciler := newReconciler(cli)
   597  	result, err := reconciler.Reconcile(context.TODO(), request)
   598  
   599  	mocker.Finish()
   600  	assert.NoError(err)
   601  	assert.Equal(false, result.Requeue)
   602  }
   603  
   604  // TestReconcileCreateVerrazzanoHelidonWorkloadWithLoggingScope tests the basic happy path of reconciling a VerrazzanoHelidonWorkload that has a logging scope.
   605  // We expect to write out a Deployment, Service and Configmap.
   606  // GIVEN a VerrazzanoHelidonWorkload resource is created
   607  // AND that the workload has a logging scope applied
   608  // WHEN the controller Reconcile function is called
   609  // THEN expect a Deployment, Service and Configmap to be written
   610  func TestReconcileCreateVerrazzanoHelidonWorkloadWithLoggingScope(t *testing.T) {
   611  
   612  	assert := asserts.New(t)
   613  	var mocker = gomock.NewController(t)
   614  	var cli = mocks.NewMockClient(mocker)
   615  	mockStatus := mocks.NewMockStatusWriter(mocker)
   616  
   617  	testNamespace := "test-namespace"
   618  	loggingSecretName := "test-secret-name"
   619  
   620  	fluentdImage := "unit-test-image:latest"
   621  	// set the Fluentd image which is obtained via env then reset at end of test
   622  	initialDefaultFluentdImage := logging.DefaultFluentdImage
   623  	logging.DefaultFluentdImage = fluentdImage
   624  	defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }()
   625  
   626  	params := map[string]string{
   627  		"##APPCONF_NAME##":          "test-appconf",
   628  		"##APPCONF_NAMESPACE##":     testNamespace,
   629  		"##COMPONENT_NAME##":        "test-component",
   630  		"##SCOPE_NAME##":            "test-scope",
   631  		"##SCOPE_NAMESPACE##":       testNamespace,
   632  		"##INGEST_URL##":            "http://test-ingest-host:9200",
   633  		"##INGEST_SECRET_NAME##":    loggingSecretName,
   634  		"##FLUENTD_IMAGE##":         "test-fluentd-image-name",
   635  		"##WORKLOAD_APIVER##":       "oam.verrazzano.io/v1alpha1",
   636  		"##WORKLOAD_KIND##":         "VerrazzanoHelidonWorkload",
   637  		"##WORKLOAD_NAME##":         "test-workload-name",
   638  		"##WORKLOAD_NAMESPACE##":    testNamespace,
   639  		"##DEPLOYMENT_NAME##":       "test-deployment",
   640  		"##CONTAINER_NAME##":        "test-container",
   641  		"##CONTAINER_IMAGE##":       "test-container-image",
   642  		"##CONTAINER_PORT_NAME##":   "http",
   643  		"##CONTAINER_PORT_NUMBER##": "8080",
   644  		"##LOGGING_SCOPE_NAME##":    "test-logging-scope",
   645  		"##INGRESS_TRAIT_NAME##":    "test-ingress-trait",
   646  		"##INGRESS_TRAIT_PATH##":    "/test-ingress-path",
   647  	}
   648  	// expect call to fetch existing deployment
   649  	cli.EXPECT().
   650  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-deployment"}, gomock.Not(gomock.Nil()), gomock.Any()).
   651  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
   652  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test")
   653  		})
   654  	// expect a call to fetch the application configuration
   655  	cli.EXPECT().
   656  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-appconf"}, gomock.Not(gomock.Nil()), gomock.Any()).
   657  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
   658  			assert.NoError(updateObjectFromYAMLTemplate(appconf, "testdata/templates/helidon_appconf_with_ingress_and_logging.yaml", params))
   659  			return nil
   660  		}).Times(1)
   661  	// expect a call to fetch the VerrazzanoHelidonWorkload
   662  	cli.EXPECT().
   663  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   664  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   665  			assert.NoError(updateObjectFromYAMLTemplate(workload, helidonWorkload, params))
   666  			return nil
   667  		}).Times(1)
   668  
   669  	// expect a call to create the Deployment
   670  	cli.EXPECT().
   671  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   672  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
   673  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
   674  			assert.Equal(deploymentKind, deploy.Kind)
   675  			// make sure the OAM component and app name labels were copied
   676  			assert.Equal(map[string]string{"app.oam.dev/component": "test-component", "app.oam.dev/name": "test-appconf"}, deploy.GetLabels())
   677  			assert.Equal(params["##CONTAINER_NAME##"], deploy.Spec.Template.Spec.Containers[0].Name)
   678  			assert.Len(deploy.Spec.Template.Spec.Containers, 1, "Expect 4 containers: app+sidecar")
   679  
   680  			// The app container should be unmodified for the Helidon use case.
   681  			c, found := findContainer(deploy.Spec.Template.Spec.Containers, "test-container")
   682  			assert.True(found, "Expected to find app container test-container")
   683  			assert.Equal(c.Image, "test-container-image")
   684  			assert.Len(c.Ports, 1)
   685  			assert.Equal(c.Ports[0].Name, "http")
   686  			assert.Equal(c.Ports[0].ContainerPort, int32(8080))
   687  			assert.Nil(c.VolumeMounts, "Expected app container to have no volume mounts")
   688  			return nil
   689  		})
   690  	// expect a call to create the Service
   691  	cli.EXPECT().
   692  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   693  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
   694  			assert.Equal(serviceAPIVersion, service.APIVersion)
   695  			assert.Equal(serviceKind, service.Kind)
   696  			return nil
   697  		})
   698  	// expect a call to status update
   699  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
   700  	mockStatus.EXPECT().
   701  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   702  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
   703  			assert.Len(workload.Status.Resources, 2)
   704  			return nil
   705  		})
   706  
   707  	// create a request and reconcile it
   708  	request := newRequest(testNamespace, "test-verrazzano-helidon-workload")
   709  	reconciler := newReconciler(cli)
   710  	result, err := reconciler.Reconcile(context.TODO(), request)
   711  
   712  	mocker.Finish()
   713  	assert.NoError(err)
   714  	assert.Equal(false, result.Requeue)
   715  }
   716  
   717  // TestReconcileCreateVerrazzanoHelidonWorkloadWithMultipleContainersAndLoggingScope tests correct sidecar setup for a workload with multiple containers.
   718  // GIVEN a VerrazzanoHelidonWorkload resource is created
   719  // AND that the workload has multiple containers
   720  // AND that the workload has a logging scope applied
   721  // WHEN the controller Reconcile function is called
   722  // THEN expect a Deployment, Service and Configmap to be written
   723  // AND expect that each application container has an associated logging sidecar container
   724  func TestReconcileCreateVerrazzanoHelidonWorkloadWithMultipleContainersAndLoggingScope(t *testing.T) {
   725  
   726  	assert := asserts.New(t)
   727  	var mocker = gomock.NewController(t)
   728  	var cli = mocks.NewMockClient(mocker)
   729  	mockStatus := mocks.NewMockStatusWriter(mocker)
   730  
   731  	testNamespace := "test-namespace"
   732  	loggingSecretName := "test-secret-name"
   733  
   734  	fluentdImage := "unit-test-image:latest"
   735  	// set the Fluentd image which is obtained via env then reset at end of test
   736  	initialDefaultFluentdImage := logging.DefaultFluentdImage
   737  	logging.DefaultFluentdImage = fluentdImage
   738  	defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }()
   739  
   740  	params := map[string]string{
   741  		"##APPCONF_NAME##":            "test-appconf",
   742  		"##APPCONF_NAMESPACE##":       testNamespace,
   743  		"##COMPONENT_NAME##":          "test-component",
   744  		"##SCOPE_NAME##":              "test-scope",
   745  		"##SCOPE_NAMESPACE##":         testNamespace,
   746  		"##INGEST_URL##":              "http://test-ingest-host:9200",
   747  		"##INGEST_SECRET_NAME##":      loggingSecretName,
   748  		"##FLUENTD_IMAGE##":           "test-fluentd-image-name",
   749  		"##WORKLOAD_APIVER##":         "oam.verrazzano.io/v1alpha1",
   750  		"##WORKLOAD_KIND##":           "VerrazzanoHelidonWorkload",
   751  		"##WORKLOAD_NAME##":           "test-workload-name",
   752  		"##WORKLOAD_NAMESPACE##":      testNamespace,
   753  		"##DEPLOYMENT_NAME##":         "test-deployment",
   754  		"##CONTAINER_NAME_1##":        "test-container-1",
   755  		"##CONTAINER_IMAGE_1##":       "test-container-image-1",
   756  		"##CONTAINER_PORT_NAME_1##":   "http1",
   757  		"##CONTAINER_PORT_NUMBER_1##": "8081",
   758  		"##CONTAINER_NAME_2##":        "test-container-2",
   759  		"##CONTAINER_IMAGE_2##":       "test-container-image-2",
   760  		"##CONTAINER_PORT_NAME_2##":   "http2",
   761  		"##CONTAINER_PORT_NUMBER_2##": "8082",
   762  		"##LOGGING_SCOPE_NAME##":      "test-logging-scope",
   763  		"##INGRESS_TRAIT_NAME##":      "test-ingress-trait",
   764  		"##INGRESS_TRAIT_PATH##":      "/test-ingress-path",
   765  	}
   766  	// expect call to fetch existing deployment
   767  	cli.EXPECT().
   768  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-deployment"}, gomock.Not(gomock.Nil()), gomock.Any()).
   769  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
   770  			return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test")
   771  		})
   772  	// expect a call to fetch the application configuration
   773  	cli.EXPECT().
   774  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-appconf"}, gomock.Not(gomock.Nil()), gomock.Any()).
   775  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
   776  			assert.NoError(updateObjectFromYAMLTemplate(appconf, "testdata/templates/helidon_appconf_with_ingress_and_logging.yaml", params))
   777  			return nil
   778  		})
   779  	// expect a call to fetch the VerrazzanoHelidonWorkload
   780  	cli.EXPECT().
   781  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
   782  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
   783  			assert.NoError(updateObjectFromYAMLTemplate(workload, "testdata/templates/helidon_workload_multi_container.yaml", params))
   784  			return nil
   785  		}).Times(1)
   786  
   787  	// expect a call to create the Deployment
   788  	cli.EXPECT().
   789  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   790  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
   791  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
   792  			assert.Equal(deploymentKind, deploy.Kind)
   793  			// make sure the OAM component and app name labels were copied
   794  			assert.Equal(map[string]string{"app.oam.dev/component": "test-component", "app.oam.dev/name": "test-appconf"}, deploy.GetLabels())
   795  			assert.Equal(params["##CONTAINER_NAME_1##"], deploy.Spec.Template.Spec.Containers[0].Name)
   796  
   797  			// There should be 4 containers because a sidecar will be added for each original container.
   798  			assert.Len(deploy.Spec.Template.Spec.Containers, 2, "Expect 2 containers.")
   799  
   800  			// The first app container should be unmodified.
   801  			c, found := findContainer(deploy.Spec.Template.Spec.Containers, "test-container-1")
   802  			assert.True(found, "Expected to find app container test-container")
   803  			assert.Equal(c.Image, "test-container-image-1")
   804  			assert.Len(c.Ports, 1)
   805  			assert.Equal(c.Ports[0].Name, "http1")
   806  			assert.Equal(c.Ports[0].ContainerPort, int32(8081))
   807  			assert.Nil(c.VolumeMounts, "Expected app container to have no volume mounts")
   808  
   809  			// The second app container should be unmodified.
   810  			c, found = findContainer(deploy.Spec.Template.Spec.Containers, "test-container-2")
   811  			assert.True(found, "Expected to find app container test-container")
   812  			assert.Equal(c.Image, "test-container-image-2")
   813  			assert.Len(c.Ports, 1)
   814  			assert.Equal(c.Ports[0].Name, "http2")
   815  			assert.Equal(c.Ports[0].ContainerPort, int32(8082))
   816  			assert.Nil(c.VolumeMounts, "Expected app container to have no volume mounts")
   817  			return nil
   818  		})
   819  	// expect a call to create the Service
   820  	cli.EXPECT().
   821  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   822  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
   823  			assert.Equal(serviceAPIVersion, service.APIVersion)
   824  			assert.Equal(serviceKind, service.Kind)
   825  			return nil
   826  		})
   827  	// expect a call to status update
   828  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
   829  	mockStatus.EXPECT().
   830  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   831  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
   832  			assert.Len(workload.Status.Resources, 2)
   833  			return nil
   834  		})
   835  
   836  	// create a request and reconcile it
   837  	request := newRequest(testNamespace, "test-verrazzano-helidon-workload")
   838  	reconciler := newReconciler(cli)
   839  	result, err := reconciler.Reconcile(context.TODO(), request)
   840  
   841  	mocker.Finish()
   842  	assert.NoError(err)
   843  	assert.Equal(false, result.Requeue)
   844  }
   845  
   846  // newScheme creates a new scheme that includes this package's object to use for testing
   847  func newScheme() *runtime.Scheme {
   848  	scheme := runtime.NewScheme()
   849  	_ = vzapi.AddToScheme(scheme)
   850  	return scheme
   851  }
   852  
   853  // newReconciler creates a new reconciler for testing
   854  // c - The K8s client to inject into the reconciler
   855  func newReconciler(c client.Client) Reconciler {
   856  	scheme := newScheme()
   857  	metricsReconciler := &metricstrait.Reconciler{Client: c, Scheme: scheme, Scraper: "verrazzano-system/vmi-system-prometheus-0"}
   858  	return Reconciler{
   859  		Client:  c,
   860  		Log:     zap.S().With("test"),
   861  		Scheme:  scheme,
   862  		Metrics: metricsReconciler,
   863  	}
   864  }
   865  
   866  // newRequest creates a new reconciler request for testing
   867  // namespace - The namespace to use in the request
   868  // name - The name to use in the request
   869  func newRequest(namespace string, name string) ctrl.Request {
   870  	return ctrl.Request{
   871  		NamespacedName: types.NamespacedName{
   872  			Namespace: namespace,
   873  			Name:      name,
   874  		},
   875  	}
   876  }
   877  
   878  // readTemplate reads a string template from a file and replaces values in the template from param maps
   879  // template - The filename of a template
   880  // params - a vararg of param maps
   881  func readTemplate(template string, params ...map[string]string) (string, error) {
   882  	bytes, err := os.ReadFile("../../" + template)
   883  	if err != nil {
   884  		bytes, err = os.ReadFile("../" + template)
   885  		if err != nil {
   886  			bytes, err = os.ReadFile(template)
   887  			if err != nil {
   888  				return "", err
   889  			}
   890  		}
   891  	}
   892  	content := string(bytes)
   893  	for _, p := range params {
   894  		for k, v := range p {
   895  			content = strings.ReplaceAll(content, k, v)
   896  		}
   897  	}
   898  	return content, nil
   899  }
   900  
   901  // updateUnstructuredFromYAMLTemplate updates an unstructured from a populated YAML template file.
   902  // uns - The unstructured to update
   903  // template - The template file
   904  // params - The param maps to merge into the template
   905  func updateUnstructuredFromYAMLTemplate(uns *unstructured.Unstructured, template string, params ...map[string]string) error {
   906  	str, err := readTemplate(template, params...)
   907  	if err != nil {
   908  		return err
   909  	}
   910  	bytes, err := yaml.YAMLToJSON([]byte(str))
   911  	if err != nil {
   912  		return err
   913  	}
   914  	_, _, err = unstructured.UnstructuredJSONScheme.Decode(bytes, nil, uns)
   915  	if err != nil {
   916  		return err
   917  	}
   918  	return nil
   919  }
   920  
   921  // updateObjectFromYAMLTemplate updates an object from a populated YAML template file.
   922  // uns - The unstructured to update
   923  // template - The template file
   924  // params - The param maps to merge into the template
   925  func updateObjectFromYAMLTemplate(obj interface{}, template string, params ...map[string]string) error {
   926  	uns := unstructured.Unstructured{}
   927  	err := updateUnstructuredFromYAMLTemplate(&uns, template, params...)
   928  	if err != nil {
   929  		return err
   930  	}
   931  	err = runtime.DefaultUnstructuredConverter.FromUnstructured(uns.Object, obj)
   932  	if err != nil {
   933  		return err
   934  	}
   935  	return nil
   936  }
   937  
   938  // findContainer finds a container in a slice by name.
   939  func findContainer(containers []corev1.Container, name string) (*corev1.Container, bool) {
   940  	for i, c := range containers {
   941  		if c.Name == name {
   942  			return &containers[i], true
   943  		}
   944  	}
   945  	return nil, false
   946  }
   947  
   948  func getTestDeployment(restartVersion string) *appsv1.Deployment {
   949  	deployment := &appsv1.Deployment{}
   950  	annotateRestartVersion(deployment, restartVersion)
   951  	return deployment
   952  }
   953  
   954  func annotateRestartVersion(deployment *appsv1.Deployment, restartVersion string) {
   955  	deployment.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
   956  	deployment.Spec.Template.ObjectMeta.Annotations[vzconst.RestartVersionAnnotation] = restartVersion
   957  }
   958  
   959  // TestReconcileRestart tests reconciling a VerrazzanoHelidonWorkload when the restart-version specified in the annotations.
   960  // This should result in restart-version written to the Helidon Deployment.
   961  // GIVEN a VerrazzanoHelidonWorkload resource
   962  // WHEN the controller Reconcile function is called and the restart-version is specified
   963  // THEN the restart-version written
   964  func TestReconcileRestart(t *testing.T) {
   965  
   966  	assert := asserts.New(t)
   967  	var mocker = gomock.NewController(t)
   968  	var cli = mocks.NewMockClient(mocker)
   969  	mockStatus := mocks.NewMockStatusWriter(mocker)
   970  
   971  	testNamespace := "test-namespace"
   972  	loggingSecretName := "test-secret-name"
   973  
   974  	appConfigName := "test-appconf"
   975  	componentName := "test-component"
   976  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName}
   977  	annotations := map[string]string{vzconst.RestartVersionAnnotation: testRestartVersion}
   978  
   979  	fluentdImage := "unit-test-image:latest"
   980  	// set the Fluentd image which is obtained via env then reset at end of test
   981  	initialDefaultFluentdImage := logging.DefaultFluentdImage
   982  	logging.DefaultFluentdImage = fluentdImage
   983  	defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }()
   984  
   985  	params := map[string]string{
   986  		"##APPCONF_NAME##":          appConfigName,
   987  		"##APPCONF_NAMESPACE##":     testNamespace,
   988  		"##COMPONENT_NAME##":        componentName,
   989  		"##SCOPE_NAME##":            "test-scope",
   990  		"##SCOPE_NAMESPACE##":       testNamespace,
   991  		"##INGEST_URL##":            "http://test-ingest-host:9200",
   992  		"##INGEST_SECRET_NAME##":    loggingSecretName,
   993  		"##FLUENTD_IMAGE##":         "test-fluentd-image-name",
   994  		"##WORKLOAD_APIVER##":       "oam.verrazzano.io/v1alpha1",
   995  		"##WORKLOAD_KIND##":         "VerrazzanoHelidonWorkload",
   996  		"##WORKLOAD_NAME##":         "test-workload-name",
   997  		"##WORKLOAD_NAMESPACE##":    testNamespace,
   998  		"##DEPLOYMENT_NAME##":       "test-deployment",
   999  		"##CONTAINER_NAME##":        "test-container",
  1000  		"##CONTAINER_IMAGE##":       "test-container-image",
  1001  		"##CONTAINER_PORT_NAME##":   "http",
  1002  		"##CONTAINER_PORT_NUMBER##": "8080",
  1003  		"##LOGGING_SCOPE_NAME##":    "test-logging-scope",
  1004  		"##INGRESS_TRAIT_NAME##":    "test-ingress-trait",
  1005  		"##INGRESS_TRAIT_PATH##":    "/test-ingress-path",
  1006  	}
  1007  	// expect call to fetch existing deployment
  1008  	cli.EXPECT().
  1009  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-deployment"}, gomock.Not(gomock.Nil()), gomock.Any()).
  1010  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
  1011  			return nil
  1012  		})
  1013  	// expect a call to fetch the application configuration
  1014  	cli.EXPECT().
  1015  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-appconf"}, gomock.Not(gomock.Nil()), gomock.Any()).
  1016  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appconf *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
  1017  			assert.NoError(updateObjectFromYAMLTemplate(appconf, "testdata/templates/helidon_appconf_with_ingress_and_logging.yaml", params))
  1018  			return nil
  1019  		}).Times(1)
  1020  	// expect a call to fetch the VerrazzanoHelidonWorkload
  1021  	cli.EXPECT().
  1022  		Get(gomock.Any(), types.NamespacedName{Namespace: testNamespace, Name: "test-verrazzano-helidon-workload"}, gomock.Not(gomock.Nil()), gomock.Any()).
  1023  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
  1024  			assert.NoError(updateObjectFromYAMLTemplate(workload, helidonWorkload, params))
  1025  			workload.ObjectMeta.Labels = labels
  1026  			workload.ObjectMeta.Annotations = annotations
  1027  			return nil
  1028  		}).Times(1)
  1029  
  1030  	// expect a call to create the Deployment
  1031  	cli.EXPECT().
  1032  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1033  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, patch client.Patch, applyOpts ...client.PatchOption) error {
  1034  			assert.Equal(deploymentAPIVersion, deploy.APIVersion)
  1035  			assert.Equal(deploymentKind, deploy.Kind)
  1036  			// make sure the OAM component and app name labels were copied
  1037  			assert.Equal(labels, deploy.GetLabels())
  1038  			assert.Equal(params["##CONTAINER_NAME##"], deploy.Spec.Template.Spec.Containers[0].Name)
  1039  			assert.Len(deploy.Spec.Template.Spec.Containers, 1)
  1040  
  1041  			// The app container should be unmodified for the Helidon use case.
  1042  			c, found := findContainer(deploy.Spec.Template.Spec.Containers, "test-container")
  1043  			assert.True(found, "Expected to find app container test-container")
  1044  			assert.Equal(c.Image, "test-container-image")
  1045  			assert.Len(c.Ports, 1)
  1046  			assert.Equal(c.Ports[0].Name, "http")
  1047  			assert.Equal(c.Ports[0].ContainerPort, int32(8080))
  1048  			assert.Nil(c.VolumeMounts, "Expected app container to have no volume mounts")
  1049  			return nil
  1050  		})
  1051  	// expect a call to create the Service
  1052  	cli.EXPECT().
  1053  		Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1054  		DoAndReturn(func(ctx context.Context, service *corev1.Service, patch client.Patch, applyOpts ...client.PatchOption) error {
  1055  			assert.Equal(serviceAPIVersion, service.APIVersion)
  1056  			assert.Equal(serviceKind, service.Kind)
  1057  			return nil
  1058  		})
  1059  	// expect a call to list the deployment
  1060  	cli.EXPECT().
  1061  		List(gomock.Any(), gomock.Any(), gomock.Any()).
  1062  		DoAndReturn(func(ctx context.Context, list *appsv1.DeploymentList, opts ...client.ListOption) error {
  1063  			list.Items = []appsv1.Deployment{*getTestDeployment("")}
  1064  			return nil
  1065  		})
  1066  	// expect a call to fetch the deployment
  1067  	cli.EXPECT().
  1068  		Get(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).
  1069  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, deployment *appsv1.Deployment, opt ...client.GetOption) error {
  1070  			annotateRestartVersion(deployment, "")
  1071  			return nil
  1072  		})
  1073  	// expect a call to update the deployment
  1074  	cli.EXPECT().
  1075  		Update(gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Deployment{}), gomock.Any()).
  1076  		DoAndReturn(func(ctx context.Context, deploy *appsv1.Deployment, opts ...client.UpdateOption) error {
  1077  			assert.Equal(testRestartVersion, deploy.Spec.Template.ObjectMeta.Annotations[vzconst.RestartVersionAnnotation])
  1078  			return nil
  1079  		})
  1080  	// expect a call to status update
  1081  	cli.EXPECT().Status().Return(mockStatus).AnyTimes()
  1082  	mockStatus.EXPECT().
  1083  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
  1084  		DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoHelidonWorkload, opts ...client.UpdateOption) error {
  1085  			assert.Len(workload.Status.Resources, 2)
  1086  			return nil
  1087  		})
  1088  
  1089  	// create a request and reconcile it
  1090  	request := newRequest(testNamespace, "test-verrazzano-helidon-workload")
  1091  	reconciler := newReconciler(cli)
  1092  	result, err := reconciler.Reconcile(context.TODO(), request)
  1093  
  1094  	mocker.Finish()
  1095  	assert.NoError(err)
  1096  	assert.Equal(false, result.Requeue)
  1097  }
  1098  
  1099  // TestReconcileKubeSystem tests to make sure we do not reconcile
  1100  // Any resource that belong to the kube-system namespace
  1101  func TestReconcileKubeSystem(t *testing.T) {
  1102  
  1103  	assert := asserts.New(t)
  1104  
  1105  	var mocker = gomock.NewController(t)
  1106  	var cli = mocks.NewMockClient(mocker)
  1107  
  1108  	// create a request and reconcile it
  1109  	request := newRequest(vzconst.KubeSystem, "unit-test-verrazzano-helidon-workload")
  1110  	reconciler := newReconciler(cli)
  1111  	result, err := reconciler.Reconcile(context.TODO(), request)
  1112  
  1113  	// Validate the results
  1114  	mocker.Finish()
  1115  	assert.Nil(err)
  1116  	assert.True(result.IsZero())
  1117  }
  1118  
  1119  // TestReconcileFailed tests to make sure the failure metric is being exposed
  1120  func TestReconcileFailed(t *testing.T) {
  1121  	testAppConfigName := "unit-test-app-config"
  1122  	testNamespace := "test-ns"
  1123  
  1124  	assert := asserts.New(t)
  1125  	clientBuilder := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
  1126  	// Create a request and reconcile it
  1127  	reconciler := newReconciler(clientBuilder)
  1128  	request := newRequest(testNamespace, testAppConfigName)
  1129  	reconcileerrorCounterObject, err := metricsexporter.GetSimpleCounterMetric(metricsexporter.HelidonReconcileError)
  1130  	assert.NoError(err)
  1131  	// Expect a call to fetch the error
  1132  	reconcileFailedCounterBefore := testutil.ToFloat64(reconcileerrorCounterObject.Get())
  1133  	reconcileerrorCounterObject.Get().Inc()
  1134  	reconciler.Reconcile(context.TODO(), request)
  1135  	reconcileFailedCounterAfter := testutil.ToFloat64(reconcileerrorCounterObject.Get())
  1136  	assert.Equal(reconcileFailedCounterBefore, reconcileFailedCounterAfter-1)
  1137  }
  1138  
  1139  // TestAddMetrics tests to add metrics and make sure no error comes
  1140  func TestAddMetrics(t *testing.T) {
  1141  	assert := asserts.New(t)
  1142  	var mocker = gomock.NewController(t)
  1143  	var cli = mocks.NewMockClient(mocker)
  1144  	appConfigName := "test-appconf"
  1145  	componentName := "test-component"
  1146  	testNamespace := "test-namespace"
  1147  	loggingSecretName := "test-secret-name"
  1148  	params := map[string]string{
  1149  		"##APPCONF_NAME##":       appConfigName,
  1150  		"##APPCONF_NAMESPACE##":  testNamespace,
  1151  		"##COMPONENT_NAME##":     componentName,
  1152  		"##SCOPE_NAME##":         "test-scope",
  1153  		"##SCOPE_NAMESPACE##":    testNamespace,
  1154  		"##INGEST_URL##":         "http://test-ingest-host:9200",
  1155  		"##INGEST_SECRET_NAME##": loggingSecretName,
  1156  		"##FLUENTD_IMAGE##":      "test-fluentd-image-name",
  1157  		"##WORKLOAD_APIVER##":    "oam.verrazzano.io/v1alpha1",
  1158  		"##WORKLOAD_KIND##":      "VerrazzanoHelidonWorkload",
  1159  		"##WORKLOAD_NAME##":      "test-workload-name",
  1160  		"##WORKLOAD_NAMESPACE##": testNamespace,
  1161  		"##DEPLOYMENT_NAME##":    "test-deployment",
  1162  		"##LOGGING_SCOPE_NAME##": "test-logging-scope",
  1163  		"##INGRESS_TRAIT_NAME##": "test-ingress-trait",
  1164  		"##INGRESS_TRAIT_PATH##": "/test-ingress-path",
  1165  	}
  1166  
  1167  	labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: componentName}
  1168  	annotations := map[string]string{vzconst.RestartVersionAnnotation: testRestartVersion}
  1169  	request := types.NamespacedName{
  1170  		Namespace: testNamespace,
  1171  		Name:      componentName,
  1172  	}
  1173  	reconciler := newReconciler(cli)
  1174  	// Fetch the workload
  1175  	var workload vzapi.VerrazzanoHelidonWorkload
  1176  	cli.EXPECT().
  1177  		// GIVEN a default mocker client
  1178  		// WHEN we call Get from the addMetrics method
  1179  		// THEN the call feeds workload to get metrics traits
  1180  		Get(gomock.Any(), request, gomock.Not(gomock.Nil()), gomock.Any()).
  1181  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload1 *vzapi.VerrazzanoHelidonWorkload, opt ...client.GetOption) error {
  1182  			assert.NoError(updateObjectFromYAMLTemplate(workload1, helidonWorkload, params))
  1183  			workload1.ObjectMeta.Labels = labels
  1184  			workload1.ObjectMeta.Annotations = annotations
  1185  			workload1.ObjectMeta.Namespace = testNamespace
  1186  			workload1.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Name: testNamespace}}
  1187  			return nil
  1188  		})
  1189  
  1190  	cli.EXPECT().
  1191  		// GIVEN a default mocker client
  1192  		// WHEN we call Get from the ComponentFromWorkloadLabels method
  1193  		// THEN the call feeds workload and component to get metrics traits
  1194  		Get(gomock.Any(), request, gomock.Not(gomock.Nil()), gomock.Any()).
  1195  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload1 *oamapi.ApplicationConfiguration, opt ...client.GetOption) error {
  1196  			assert.NoError(updateObjectFromYAMLTemplate(workload1, helidonWorkload, params))
  1197  			workload1.ObjectMeta.Labels = labels
  1198  			workload1.ObjectMeta.Annotations = annotations
  1199  			workload1.ObjectMeta.Namespace = testNamespace
  1200  			workload1.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Name: testNamespace}}
  1201  			workload1.Spec.Components = []oamapi.ApplicationConfigurationComponent{{ComponentName: componentName, Traits: []oamapi.ComponentTrait{{Trait: runtime.RawExtension{Raw: []byte(`{"kind":"MetricsTrait"}`)}}}}}
  1202  			return nil
  1203  		})
  1204  
  1205  	cli.EXPECT().
  1206  		// GIVEN a default mocker client
  1207  		// WHEN we call List from the MetricsTraitFromWorkloadLabels method
  1208  		// THEN the call gets MetricsTrait object associated with the workload
  1209  		List(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).
  1210  		DoAndReturn(func(ctx context.Context, list *vzapi.MetricsTraitList, opts ...client.ListOption) error {
  1211  			list.Items = []vzapi.MetricsTrait{{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, OwnerReferences: []metav1.OwnerReference{{Name: testNamespace}}}}}
  1212  			list.Items[0].Spec.WorkloadReference.Name = params["##WORKLOAD_NAME##"]
  1213  			return nil
  1214  		})
  1215  
  1216  	err := reconciler.Get(context.TODO(), request, &workload)
  1217  	assert.NoError(err)
  1218  
  1219  	log, err := clusters.GetResourceLogger("verrazzanohelidonworkload", request, &workload)
  1220  	assert.NoError(err)
  1221  	// Unwrap the apps/DeploymentSpec and meta/ObjectMeta
  1222  	deploy, err := reconciler.convertWorkloadToDeployment(&workload, log)
  1223  	assert.NoError(err)
  1224  	assert.NoError(reconciler.addMetrics(context.TODO(), log, workload.Namespace, &workload, deploy))
  1225  	mocker.Finish()
  1226  }