github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/navigation/trait_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 navigation
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    12  
    13  	oamrt "github.com/crossplane/crossplane-runtime/apis/common/v1"
    14  	"github.com/golang/mock/gomock"
    15  	asserts "github.com/stretchr/testify/assert"
    16  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    17  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    18  	"go.uber.org/zap"
    19  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	"sigs.k8s.io/controller-runtime/pkg/client"
    25  )
    26  
    27  // TestFetchTrait tests various usages of FetchTrait
    28  func TestFetchTrait(t *testing.T) {
    29  	assert := asserts.New(t)
    30  
    31  	var mocker *gomock.Controller
    32  	var cli *mocks.MockClient
    33  	var trait *vzapi.MetricsTrait
    34  	var err error
    35  	var name types.NamespacedName
    36  
    37  	// GIVEN a name for a trait that does exists
    38  	// WHEN the trait is fetched
    39  	// THEN verify that the returned trait has correct content
    40  	mocker = gomock.NewController(t)
    41  	cli = mocks.NewMockClient(mocker)
    42  	name = types.NamespacedName{Namespace: "test-namespace", Name: "test-name"}
    43  	cli.EXPECT().Get(gomock.Eq(context.TODO()), gomock.Eq(name), gomock.Not(gomock.Nil()), gomock.Any()).
    44  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, trait *vzapi.MetricsTrait, opts ...client.GetOption) error {
    45  			trait.Name = "test-name"
    46  			return nil
    47  		})
    48  	trait, err = FetchTrait(context.TODO(), cli, zap.S(), name)
    49  	mocker.Finish()
    50  	assert.NoError(err)
    51  	assert.Equal("test-name", trait.Name)
    52  
    53  	// GIVEN a name for a trait that does not exist
    54  	// WHEN the trait is fetched
    55  	// THEN verify that both the returned trait and error are nil
    56  	mocker = gomock.NewController(t)
    57  	cli = mocks.NewMockClient(mocker)
    58  	name = types.NamespacedName{Namespace: "test-namespace", Name: "test-name"}
    59  	cli.EXPECT().Get(gomock.Eq(context.TODO()), gomock.Eq(name), gomock.Not(gomock.Nil()), gomock.Any()).
    60  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, trait *vzapi.MetricsTrait, opts ...client.GetOption) error {
    61  			return k8serrors.NewNotFound(schema.GroupResource{Group: trait.APIVersion, Resource: trait.Kind}, key.Name)
    62  		})
    63  	trait, err = FetchTrait(context.TODO(), cli, zap.S(), name)
    64  	mocker.Finish()
    65  	assert.Nil(trait)
    66  	assert.NoError(err)
    67  
    68  	// GIVEN a name for a trait that should exist
    69  	// WHEN the trait is fetched and there is an underlying error
    70  	// THEN verify that the error is propagated
    71  	mocker = gomock.NewController(t)
    72  	cli = mocks.NewMockClient(mocker)
    73  	cli.EXPECT().Get(gomock.Eq(context.TODO()), gomock.Eq(name), gomock.Not(gomock.Nil()), gomock.Any()).
    74  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, trait *vzapi.MetricsTrait, opts ...client.GetOption) error {
    75  			return fmt.Errorf("test-error")
    76  		})
    77  	name = types.NamespacedName{Namespace: "test-namespace", Name: "test-name"}
    78  	trait, err = FetchTrait(context.TODO(), cli, zap.S(), name)
    79  	mocker.Finish()
    80  	assert.Nil(trait)
    81  	assert.Error(err)
    82  	assert.Equal("test-error", err.Error())
    83  }
    84  
    85  // TestFetchWorkloadFromTrait tests various usages of FetchWorkloadFromTrait
    86  func TestFetchWorkloadFromTrait(t *testing.T) {
    87  	assert := asserts.New(t)
    88  
    89  	var mocker *gomock.Controller
    90  	var cli *mocks.MockClient
    91  	var ctx = context.TODO()
    92  	var trait *vzapi.IngressTrait
    93  	var err error
    94  	var uns *unstructured.Unstructured
    95  
    96  	// GIVEN a trait with a reference to a workload that can be found
    97  	// WHEN the workload is fetched via the trait
    98  	// THEN verify the workload content is correct
    99  	mocker = gomock.NewController(t)
   100  	cli = mocks.NewMockClient(mocker)
   101  	trait = &vzapi.IngressTrait{
   102  		TypeMeta:   metav1.TypeMeta{Kind: "IngressTrait", APIVersion: "oam.verrazzano.io/v1alpha1"},
   103  		ObjectMeta: metav1.ObjectMeta{Name: "test-trait-name", Namespace: "test-trait-namespace"},
   104  		Spec: vzapi.IngressTraitSpec{WorkloadReference: oamrt.TypedReference{
   105  			APIVersion: "core.oam.dev/v1alpha2", Kind: "ContainerizedWorkload", Name: "test-workload-name"}}}
   106  	cli.EXPECT().
   107  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-trait-namespace", Name: "test-workload-name"}), gomock.Not(gomock.Nil()), gomock.Any()).
   108  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   109  			obj.SetNamespace(key.Namespace)
   110  			obj.SetName(key.Name)
   111  			return nil
   112  		})
   113  	uns, err = FetchWorkloadFromTrait(ctx, cli, vzlog.DefaultLogger(), trait)
   114  	mocker.Finish()
   115  	assert.NoError(err)
   116  	assert.NotNil(uns)
   117  	assert.Equal("test-trait-namespace", uns.GetNamespace())
   118  	assert.Equal("test-workload-name", uns.GetName())
   119  
   120  	// GIVEN a trait with a reference to a workload
   121  	// WHEN a failure occurs attempting to fetch the trait's workload
   122  	// THEN verify the error is propagated
   123  	mocker = gomock.NewController(t)
   124  	cli = mocks.NewMockClient(mocker)
   125  	trait = &vzapi.IngressTrait{
   126  		TypeMeta:   metav1.TypeMeta{Kind: "IngressTrait", APIVersion: "oam.verrazzano.io/v1alpha1"},
   127  		ObjectMeta: metav1.ObjectMeta{Name: "test-trait-name", Namespace: "test-trait-namespace"},
   128  		Spec: vzapi.IngressTraitSpec{WorkloadReference: oamrt.TypedReference{
   129  			APIVersion: "core.oam.dev/v1alpha2", Kind: "ContainerizedWorkload", Name: "test-workload-name"}}}
   130  	cli.EXPECT().
   131  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-trait-namespace", Name: "test-workload-name"}), gomock.Not(gomock.Nil()), gomock.Any()).
   132  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   133  			return fmt.Errorf("test-error")
   134  		})
   135  	uns, err = FetchWorkloadFromTrait(ctx, cli, vzlog.DefaultLogger(), trait)
   136  	mocker.Finish()
   137  	assert.Nil(uns)
   138  	assert.Error(err)
   139  	assert.Equal("test-error", err.Error())
   140  
   141  	// GIVEN a trait with a reference to a Verrazzano workload type
   142  	// WHEN the workload is fetched via the trait
   143  	// THEN verify that the contained workload is unwrapped and returned
   144  	mocker = gomock.NewController(t)
   145  	cli = mocks.NewMockClient(mocker)
   146  
   147  	workloadAPIVersion := "oam.verrazzano.io/v1alpha1"
   148  	workloadKind := "VerrazzanoCoherenceWorkload"
   149  
   150  	containedAPIVersion := "coherence.oracle.com/v1"
   151  	containedKind := "Coherence"
   152  	containedName := "unit-test-resource"
   153  
   154  	containedResource := map[string]interface{}{
   155  		"metadata": map[string]interface{}{
   156  			"name": containedName,
   157  		},
   158  	}
   159  
   160  	trait = &vzapi.IngressTrait{
   161  		TypeMeta:   metav1.TypeMeta{Kind: "IngressTrait", APIVersion: "oam.verrazzano.io/v1alpha1"},
   162  		ObjectMeta: metav1.ObjectMeta{Name: "test-trait-name", Namespace: "test-trait-namespace"},
   163  		Spec: vzapi.IngressTraitSpec{WorkloadReference: oamrt.TypedReference{
   164  			APIVersion: workloadAPIVersion, Kind: workloadKind, Name: "test-workload-name"}}}
   165  
   166  	// expect a call to fetch the referenced workload
   167  	cli.EXPECT().
   168  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-trait-namespace", Name: "test-workload-name"}), gomock.Not(gomock.Nil()), gomock.Any()).
   169  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   170  			obj.SetNamespace(key.Namespace)
   171  			obj.SetName(key.Name)
   172  			obj.SetAPIVersion(workloadAPIVersion)
   173  			obj.SetKind(workloadKind)
   174  			unstructured.SetNestedMap(obj.Object, containedResource, "spec", "template")
   175  			return nil
   176  		})
   177  	// expect a call to fetch the contained workload that is wrapped by the Verrazzano workload
   178  	cli.EXPECT().
   179  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-trait-namespace", Name: containedName}), gomock.Not(gomock.Nil()), gomock.Any()).
   180  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   181  			obj.SetUnstructuredContent(containedResource)
   182  			obj.SetAPIVersion(containedAPIVersion)
   183  			obj.SetKind(containedKind)
   184  			return nil
   185  		})
   186  
   187  	uns, err = FetchWorkloadFromTrait(ctx, cli, vzlog.DefaultLogger(), trait)
   188  
   189  	mocker.Finish()
   190  	assert.NoError(err)
   191  	assert.NotNil(uns)
   192  	assert.Equal(containedAPIVersion, uns.GetAPIVersion())
   193  	assert.Equal(containedKind, uns.GetKind())
   194  	assert.Equal(containedName, uns.GetName())
   195  
   196  	// GIVEN a trait with a reference to a VerrazzanoHelidonWorkload that can be found
   197  	// WHEN the workload is fetched via the trait
   198  	// THEN verify the workload content is correct
   199  	mocker = gomock.NewController(t)
   200  	cli = mocks.NewMockClient(mocker)
   201  	trait = &vzapi.IngressTrait{
   202  		TypeMeta:   metav1.TypeMeta{Kind: "IngressTrait", APIVersion: "oam.verrazzano.io/v1alpha1"},
   203  		ObjectMeta: metav1.ObjectMeta{Name: "test-trait-name", Namespace: "test-trait-namespace"},
   204  		Spec: vzapi.IngressTraitSpec{WorkloadReference: oamrt.TypedReference{
   205  			APIVersion: "oam.verrazzano.io/v1alpha1", Kind: "VerrazzanoHelidonWorkload", Name: "test-helidon-workload"}}}
   206  	cli.EXPECT().
   207  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-trait-namespace", Name: "test-helidon-workload"}), gomock.Not(gomock.Nil()), gomock.Any()).
   208  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   209  			obj.SetNamespace(key.Namespace)
   210  			obj.SetName(key.Name)
   211  			return nil
   212  		})
   213  	uns, err = FetchWorkloadFromTrait(ctx, cli, vzlog.DefaultLogger(), trait)
   214  	mocker.Finish()
   215  	assert.NoError(err)
   216  	assert.NotNil(uns)
   217  
   218  	assert.Equal("test-trait-namespace", uns.GetNamespace())
   219  	assert.Equal("test-helidon-workload", uns.GetName())
   220  }
   221  
   222  // TestFetchWorkloadResource tests various usages of FetchWorkloadResource
   223  func TestFetchWorkloadResource(t *testing.T) {
   224  	assert := asserts.New(t)
   225  
   226  	var mocker *gomock.Controller
   227  	var cli *mocks.MockClient
   228  	var ctx = context.TODO()
   229  	var err error
   230  	var uns *unstructured.Unstructured
   231  
   232  	// GIVEN a non Verrazzano specific workload
   233  	// WHEN the workload resource is fetched
   234  	// THEN verify the workload is returned as is
   235  	mocker = gomock.NewController(t)
   236  	cli = mocks.NewMockClient(mocker)
   237  	uns = &unstructured.Unstructured{}
   238  	containerizedWorkloadName := "container-workload"
   239  	containerizedWorkloadNamespace := "default"
   240  	containerizedWorkloadAPIVersion := "core.oam.dev/v1alpha2"
   241  	containerizedWorkloadKind := "ContainerizedWorkload"
   242  	uns.SetNamespace(containerizedWorkloadNamespace)
   243  	uns.SetName(containerizedWorkloadName)
   244  	uns.SetAPIVersion(containerizedWorkloadAPIVersion)
   245  	uns.SetKind(containerizedWorkloadKind)
   246  	uns, err = FetchWorkloadResource(ctx, cli, vzlog.DefaultLogger(), uns)
   247  	assert.NoError(err)
   248  	assert.Equal(containerizedWorkloadNamespace, uns.GetNamespace())
   249  	assert.Equal(containerizedWorkloadName, uns.GetName())
   250  	assert.Equal(containerizedWorkloadAPIVersion, uns.GetAPIVersion())
   251  	assert.Equal(containerizedWorkloadKind, uns.GetKind())
   252  
   253  	// GIVEN a Verrazzano specific workload
   254  	// WHEN the workload resource is fetched
   255  	// THEN verify the contained workload is returned
   256  	mocker = gomock.NewController(t)
   257  	cli = mocks.NewMockClient(mocker)
   258  	uns = &unstructured.Unstructured{}
   259  	workloadName := "coherence-workload"
   260  	workloadNamespace := "default"
   261  	workloadAPIVersion := "oam.verrazzano.io/v1alpha1"
   262  	workloadKind := "VerrazzanoCoherenceWorkload"
   263  
   264  	containedAPIVersion := "coherence.oracle.com/v1"
   265  	containedKind := "Coherence"
   266  	containedName := "unit-test-resource"
   267  	containedNamespace := "default"
   268  
   269  	containedResource := map[string]interface{}{
   270  		"metadata": map[string]interface{}{
   271  			"name": containedName,
   272  		},
   273  	}
   274  
   275  	uns.SetNamespace(workloadNamespace)
   276  	uns.SetName(workloadName)
   277  	uns.SetAPIVersion(workloadAPIVersion)
   278  	uns.SetKind(workloadKind)
   279  	unstructured.SetNestedMap(uns.Object, containedResource, "spec", "template")
   280  	// expect a call to fetch the contained workload that is wrapped by the Verrazzano workload
   281  	cli.EXPECT().
   282  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: containedNamespace, Name: containedName}), gomock.Not(gomock.Nil()), gomock.Any()).
   283  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   284  			obj.SetUnstructuredContent(containedResource)
   285  			obj.SetAPIVersion(containedAPIVersion)
   286  			obj.SetKind(containedKind)
   287  			obj.SetNamespace(containedNamespace)
   288  			return nil
   289  		})
   290  
   291  	uns, err = FetchWorkloadResource(ctx, cli, vzlog.DefaultLogger(), uns)
   292  	assert.NoError(err)
   293  	assert.Equal(containedNamespace, uns.GetNamespace())
   294  	assert.Equal(containedName, uns.GetName())
   295  	assert.Equal(containedAPIVersion, uns.GetAPIVersion())
   296  	assert.Equal(containedKind, uns.GetKind())
   297  
   298  	// GIVEN a Verrazzano specific workload
   299  	// WHEN a failure occurs attempting to fetch the workload resource
   300  	// THEN verify the error is propagated
   301  	mocker = gomock.NewController(t)
   302  	cli = mocks.NewMockClient(mocker)
   303  	uns = &unstructured.Unstructured{}
   304  	workloadName = "coherence-workload"
   305  	workloadNamespace = "default"
   306  	workloadAPIVersion = "oam.verrazzano.io/v1alpha1"
   307  	workloadKind = "VerrazzanoCoherenceWorkload"
   308  
   309  	containedAPIVersion = "coherence.oracle.com/v1"
   310  	containedKind = "Coherence"
   311  	containedName = "unit-test-resource"
   312  	containedNamespace = "default"
   313  
   314  	containedResource = map[string]interface{}{
   315  		"metadata": map[string]interface{}{
   316  			"name": containedName,
   317  		},
   318  	}
   319  
   320  	uns.SetNamespace(workloadNamespace)
   321  	uns.SetName(workloadName)
   322  	uns.SetAPIVersion(workloadAPIVersion)
   323  	uns.SetKind(workloadKind)
   324  	unstructured.SetNestedMap(uns.Object, containedResource, "spec", "template")
   325  	// expect a call to fetch the contained workload that is wrapped by the Verrazzano workload and return error
   326  	cli.EXPECT().
   327  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: containedNamespace, Name: containedName}), gomock.Not(gomock.Nil()), gomock.Any()).
   328  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured, opts ...client.GetOption) error {
   329  			return fmt.Errorf("test-error")
   330  		})
   331  
   332  	uns, err = FetchWorkloadResource(ctx, cli, vzlog.DefaultLogger(), uns)
   333  	mocker.Finish()
   334  	assert.Nil(uns)
   335  	assert.Error(err)
   336  	assert.Equal("test-error", err.Error())
   337  }
   338  
   339  // TestIsWeblogicWorkloadKind tests the IsWeblogicWorkloadKind function
   340  // GIVEN a trait with a WorkloadReference
   341  // WHEN a call to IsWeblogicWorkloadKind is made
   342  // THEN return true if the trait references a VerrazzanoWebLogic workload kind, false otherwise
   343  func TestIsWeblogicWorkloadKind(t *testing.T) {
   344  	assert := asserts.New(t)
   345  
   346  	trait := &vzapi.IngressTrait{}
   347  	assert.False(IsWeblogicWorkloadKind(trait))
   348  
   349  	trait = &vzapi.IngressTrait{
   350  		Spec: vzapi.IngressTraitSpec{
   351  			WorkloadReference: oamrt.TypedReference{Kind: "VerrazzanoCoherenceWorkload"},
   352  		},
   353  	}
   354  	assert.False(IsWeblogicWorkloadKind(trait))
   355  
   356  	trait = &vzapi.IngressTrait{
   357  		Spec: vzapi.IngressTraitSpec{
   358  			WorkloadReference: oamrt.TypedReference{Kind: "VerrazzanoWebLogicWorkload"},
   359  		},
   360  	}
   361  	assert.True(IsWeblogicWorkloadKind(trait))
   362  }