github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/navigation/unstructured_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  	"encoding/json"
     9  	"fmt"
    10  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    11  	"testing"
    12  
    13  	oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    14  	"github.com/golang/mock/gomock"
    15  	asserts "github.com/stretchr/testify/assert"
    16  	"github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    17  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    18  	"go.uber.org/zap"
    19  	k8sapps "k8s.io/api/apps/v1"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    22  	"k8s.io/apimachinery/pkg/runtime"
    23  	"sigs.k8s.io/controller-runtime/pkg/client"
    24  )
    25  
    26  const (
    27  	testNamespace = "test-namespace"
    28  )
    29  
    30  // TestGetKindOfUnstructured tests the GetKindOfUnstructured function.
    31  func TestGetKindOfUnstructured(t *testing.T) {
    32  	assert := asserts.New(t)
    33  
    34  	var uns unstructured.Unstructured
    35  	var kind string
    36  	var err error
    37  
    38  	// GIVEN an unstructured with a valid kind
    39  	// WHEN the kind is extracted
    40  	// THEN verify that the kind is correct and there is no error
    41  	uns = unstructured.Unstructured{}
    42  	uns.SetGroupVersionKind(k8sapps.SchemeGroupVersion.WithKind("Deployment"))
    43  	kind, err = GetKindOfUnstructured(&uns)
    44  	assert.NoError(err)
    45  	assert.Equal("Deployment", kind)
    46  
    47  	// GIVEN an unstructured without a valid kind
    48  	// WHEN the kind is extracted
    49  	// THEN verify that the kind is empty and that an error was returned
    50  	uns = unstructured.Unstructured{}
    51  	kind, err = GetKindOfUnstructured(&uns)
    52  	assert.Error(err)
    53  	assert.Contains(err.Error(), "kind")
    54  	assert.Equal("", kind)
    55  
    56  	// GIVEN a nil input unstructured parameter
    57  	// WHEN the kind is extracted
    58  	// THEN verify that an error is returned
    59  	kind, err = GetKindOfUnstructured(nil)
    60  	assert.Error(err)
    61  	assert.Equal("", kind)
    62  }
    63  
    64  // TestGetAPIVersionOfUnstructured tests the GetAPIVersionOfUnstructured function.
    65  func TestGetAPIVersionOfUnstructured(t *testing.T) {
    66  	assert := asserts.New(t)
    67  
    68  	var uns unstructured.Unstructured
    69  	var apiver string
    70  	var err error
    71  
    72  	// GIVEN a nil unstructured input parameter
    73  	// WHEN the APIVersion is extracted
    74  	// THEN verify an error is returned
    75  	apiver, err = GetAPIVersionOfUnstructured(nil)
    76  	assert.Error(err)
    77  	assert.Equal("", apiver)
    78  
    79  	// GIVEN a nil unstructured without an api version
    80  	// WHEN the APIVersion is extracted
    81  	// THEN verify an error is returned
    82  	uns = unstructured.Unstructured{}
    83  	apiver, err = GetAPIVersionOfUnstructured(&uns)
    84  	assert.Error(err)
    85  	assert.Contains("unstructured does not contain api version", err.Error())
    86  	assert.Equal("", apiver)
    87  
    88  	// GIVEN a nil unstructured with an api version
    89  	// WHEN the APIVersion is extracted
    90  	// THEN verify the api version is correct and there is no error
    91  	uns = unstructured.Unstructured{}
    92  	uns.SetGroupVersionKind(k8sapps.SchemeGroupVersion.WithKind("Deployment"))
    93  	apiver, err = GetAPIVersionOfUnstructured(&uns)
    94  	assert.NoError(err)
    95  	assert.Equal("apps/v1", apiver)
    96  }
    97  
    98  // TestGetAPIVersionKindOfUnstructured tests the GetAPIVersionKindOfUnstructured function
    99  func TestGetAPIVersionKindOfUnstructured(t *testing.T) {
   100  	assert := asserts.New(t)
   101  
   102  	var uns unstructured.Unstructured
   103  	var avk string
   104  	var err error
   105  
   106  	// GIVEN a nil unstructured parameter
   107  	// WHEN the api version kind is extracted
   108  	// THEN verify an error is returned
   109  	avk, err = GetAPIVersionKindOfUnstructured(nil)
   110  	assert.Error(err)
   111  	assert.Equal("", avk)
   112  
   113  	// GIVEN an invalid unstructured parameter
   114  	// WHEN the api version kind is extracted
   115  	// THEN verify an error is returned
   116  	uns = unstructured.Unstructured{}
   117  	avk, err = GetAPIVersionKindOfUnstructured(&uns)
   118  	assert.Error(err)
   119  	assert.Equal("", avk)
   120  
   121  	// GIVEN an unstructured parameter with an invalid api version kind
   122  	// WHEN the api version kind is extracted
   123  	// THEN verify an error is returned
   124  	uns = unstructured.Unstructured{}
   125  	uns.SetAPIVersion(k8sapps.SchemeGroupVersion.String())
   126  	avk, err = GetAPIVersionKindOfUnstructured(&uns)
   127  	assert.Error(err)
   128  	assert.Equal("", avk)
   129  
   130  	// GIVEN an unstructured parameter with an valid api version kind
   131  	// WHEN the api version kind is extracted
   132  	// THEN verify the correct api version kind is returned
   133  	uns = unstructured.Unstructured{}
   134  	uns.SetGroupVersionKind(k8sapps.SchemeGroupVersion.WithKind("Deployment"))
   135  	avk, err = GetAPIVersionKindOfUnstructured(&uns)
   136  	assert.NoError(err)
   137  	assert.Equal("apps/v1.Deployment", avk)
   138  }
   139  
   140  // TestGetUnstructuredChildResourcesByAPIVersionKindsPositive tests the FetchUnstructuredChildResourcesByAPIVersionKinds function.
   141  // GIVEN a valid list of child resources
   142  // WHEN a request is made to list those child resources
   143  // THEN verify that the children are returned
   144  func TestGetUnstructuredChildResourcesByAPIVersionKindsPositive(t *testing.T) {
   145  	assert := asserts.New(t)
   146  
   147  	var mocker *gomock.Controller
   148  	var cli *mocks.MockClient
   149  	var ctx = context.TODO()
   150  	var err error
   151  	var children []*unstructured.Unstructured
   152  
   153  	mocker = gomock.NewController(t)
   154  	cli = mocks.NewMockClient(mocker)
   155  	options := []client.ListOption{client.InNamespace(testNamespace)}
   156  	cli.EXPECT().
   157  		List(gomock.Eq(ctx), gomock.Not(gomock.Nil()), options).
   158  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
   159  			assert.Equal("Deployment", resources.GetKind())
   160  			return AppendAsUnstructured(resources, k8sapps.Deployment{
   161  				TypeMeta: metav1.TypeMeta{
   162  					APIVersion: k8sapps.SchemeGroupVersion.String(),
   163  					Kind:       "test-invalid-kind"},
   164  				ObjectMeta: metav1.ObjectMeta{
   165  					Name: "test-deployment-name",
   166  					OwnerReferences: []metav1.OwnerReference{{
   167  						APIVersion: oamcore.ContainerizedWorkloadKindAPIVersion,
   168  						Kind:       oamcore.ContainerizedWorkloadKind,
   169  						Name:       "test-workload-name",
   170  						UID:        "test-workload-uid"}}}})
   171  		})
   172  	children, err = FetchUnstructuredChildResourcesByAPIVersionKinds(ctx, cli, vzlog.DefaultLogger(), testNamespace, "test-workload-uid", []oamcore.ChildResourceKind{{APIVersion: "apps/v1", Kind: "Deployment"}})
   173  	mocker.Finish()
   174  	assert.NoError(err)
   175  	assert.Len(children, 1)
   176  }
   177  
   178  // TestGetUnstructuredChildResourcesByAPIVersionKindsNegative tests the FetchUnstructuredChildResourcesByAPIVersionKinds function.
   179  // GIVEN a request to list child resources
   180  // WHEN a the underlying kubernetes call fails
   181  // THEN verify that the error is propigated to the caller
   182  func TestFetchUnstructuredChildResourcesByAPIVersionKindsNegative(t *testing.T) {
   183  	assert := asserts.New(t)
   184  
   185  	var mocker *gomock.Controller
   186  	var cli *mocks.MockClient
   187  	var ctx = context.TODO()
   188  	var err error
   189  	var children []*unstructured.Unstructured
   190  
   191  	mocker = gomock.NewController(t)
   192  	cli = mocks.NewMockClient(mocker)
   193  	options := []client.ListOption{client.InNamespace(testNamespace)}
   194  	cli.EXPECT().
   195  		List(gomock.Eq(ctx), gomock.Not(gomock.Nil()), options).
   196  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
   197  			return fmt.Errorf("test-error")
   198  		})
   199  	children, err = FetchUnstructuredChildResourcesByAPIVersionKinds(ctx, cli, vzlog.DefaultLogger(), testNamespace, "test-workload-uid", []oamcore.ChildResourceKind{{APIVersion: "apps/v1", Kind: "Deployment"}})
   200  	mocker.Finish()
   201  	assert.Error(err)
   202  	assert.Equal("test-error", err.Error())
   203  	assert.Len(children, 0)
   204  }
   205  
   206  // TestGetUnstructuredChildResourcesByDeploymentPositive tests the FetchUnstructuredChildResourcesByAPIVersionKinds function.
   207  // GIVEN a valid list of child resources and the Workload is a child as is with native Kubernetes Kinds such as Deployment
   208  // WHEN a request is made to list those child resources
   209  // THEN verify that the children are returned
   210  func TestGetUnstructuredChildResourcesByDeploymentPositive(t *testing.T) {
   211  	assert := asserts.New(t)
   212  
   213  	var mocker *gomock.Controller
   214  	var cli *mocks.MockClient
   215  	var ctx = context.TODO()
   216  	var err error
   217  	var children []*unstructured.Unstructured
   218  
   219  	mocker = gomock.NewController(t)
   220  	cli = mocks.NewMockClient(mocker)
   221  	options := []client.ListOption{client.InNamespace(testNamespace)}
   222  	cli.EXPECT().
   223  		List(gomock.Eq(ctx), gomock.Not(gomock.Nil()), options).
   224  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
   225  			assert.Equal("Deployment", resources.GetKind())
   226  			return AppendAsUnstructured(resources, k8sapps.Deployment{
   227  				TypeMeta: metav1.TypeMeta{
   228  					APIVersion: k8sapps.SchemeGroupVersion.String(),
   229  					Kind:       "test-invalid-kind"},
   230  				ObjectMeta: metav1.ObjectMeta{
   231  					Name: "test-deployment-name",
   232  					UID:  "test-workload-uid",
   233  					OwnerReferences: []metav1.OwnerReference{{
   234  						APIVersion: oamcore.ContainerizedWorkloadKindAPIVersion,
   235  						Kind:       oamcore.ContainerizedWorkloadKind,
   236  						Name:       "test-workload-name",
   237  						UID:        "wrong-workload-uid"}}}})
   238  		})
   239  	children, err = FetchUnstructuredChildResourcesByAPIVersionKinds(ctx, cli, vzlog.DefaultLogger(), testNamespace, "test-workload-uid", []oamcore.ChildResourceKind{{APIVersion: "apps/v1", Kind: "Deployment"}})
   240  	mocker.Finish()
   241  	assert.NoError(err)
   242  	assert.Len(children, 1)
   243  }
   244  
   245  // TestFetchUnstructuredByReference tests the FetchUnstructuredByReference function
   246  func TestFetchUnstructuredByReference(t *testing.T) {
   247  	assert := asserts.New(t)
   248  
   249  	var mocker *gomock.Controller
   250  	var cli *mocks.MockClient
   251  	var ctx = context.TODO()
   252  	var err error
   253  	var uns *unstructured.Unstructured
   254  
   255  	// GIVEN a valid reference
   256  	// WHEN an underlying k8s api call fails
   257  	// THEN propagate the error to the caller
   258  	mocker = gomock.NewController(t)
   259  	cli = mocks.NewMockClient(mocker)
   260  	cli.EXPECT().
   261  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-space", Name: "test-name"}), gomock.Not(gomock.Nil()), gomock.Any()).
   262  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, uns *unstructured.Unstructured, opts ...client.GetOption) error {
   263  			return fmt.Errorf("test-error")
   264  		})
   265  	uns, err = FetchUnstructuredByReference(ctx, cli, zap.S(), v1alpha1.QualifiedResourceRelation{
   266  		APIVersion: "test-api/ver",
   267  		Kind:       "test-kind",
   268  		Namespace:  "test-space",
   269  		Name:       "test-name",
   270  		Role:       "test-role"})
   271  	mocker.Finish()
   272  	assert.Nil(uns)
   273  	assert.Error(err)
   274  
   275  	// GIVEN a valid reference
   276  	// WHEN an unstructured resource is requested for the reference
   277  	// THEN verify the returned unstructured resource has correct information
   278  	mocker = gomock.NewController(t)
   279  	cli = mocks.NewMockClient(mocker)
   280  	cli.EXPECT().
   281  		Get(gomock.Eq(ctx), gomock.Eq(client.ObjectKey{Namespace: "test-space", Name: "test-name"}), gomock.Not(gomock.Nil()), gomock.Any()).
   282  		DoAndReturn(func(ctx context.Context, key client.ObjectKey, uns *unstructured.Unstructured, opts ...client.GetOption) error {
   283  			uns.SetNamespace(key.Namespace)
   284  			uns.SetName(key.Name)
   285  			return nil
   286  		})
   287  	uns, err = FetchUnstructuredByReference(ctx, cli, zap.S(), v1alpha1.QualifiedResourceRelation{
   288  		APIVersion: "test-api/ver",
   289  		Kind:       "test-kind",
   290  		Namespace:  "test-space",
   291  		Name:       "test-name",
   292  		Role:       "test-role"})
   293  	mocker.Finish()
   294  	assert.NotNil(uns)
   295  	assert.NoError(err)
   296  	assert.Equal("test-space", uns.GetNamespace())
   297  	assert.Equal("test-name", uns.GetName())
   298  }
   299  
   300  // TestConvertRawExtensionToUnstructured tests the ConvertRawExtensionToUnstructured function.
   301  // GIVEN a runtime.RawExtension object
   302  // WHEN it is converted to an unstructured.Unstructured
   303  // THEN verify that the resulting unstructured has the correct api version, kind, metadata, and spec values
   304  func TestConvertRawExtensionToUnstructured(t *testing.T) {
   305  	assert := asserts.New(t)
   306  
   307  	json := `{"apiVersion":"coherence.oracle.com/v1","kind":"Coherence","metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}`
   308  	extension := runtime.RawExtension{Raw: []byte(json)}
   309  
   310  	u, err := ConvertRawExtensionToUnstructured(&extension)
   311  
   312  	assert.NoError(err)
   313  	assert.NotNil(u)
   314  	assert.Equal("coherence.oracle.com/v1", u.GetAPIVersion())
   315  	assert.Equal("Coherence", u.GetKind())
   316  
   317  	name, _, err := unstructured.NestedString(u.Object, "metadata", "name")
   318  	assert.NoError(err)
   319  	assert.Equal("unit-test-cluster", name)
   320  
   321  	replicas, _, err := unstructured.NestedInt64(u.Object, "spec", "replicas")
   322  	assert.NoError(err)
   323  	assert.Equal(int64(3), replicas)
   324  }
   325  
   326  // ConvertToUnstructured converts an object to an Unstructured version
   327  // object - The object to convert to Unstructured
   328  func ConvertToUnstructured(object interface{}) (unstructured.Unstructured, error) {
   329  	jbytes, err := json.Marshal(object)
   330  	if err != nil {
   331  		return unstructured.Unstructured{}, err
   332  	}
   333  	var u map[string]interface{}
   334  	_ = json.Unmarshal(jbytes, &u)
   335  	return unstructured.Unstructured{Object: u}, nil
   336  }
   337  
   338  // AppendAsUnstructured appends an object to the list after converting it to an Unstructured
   339  // list - The list to append to.
   340  // object - The object to convert to Unstructured and append to the list
   341  func AppendAsUnstructured(list *unstructured.UnstructuredList, object interface{}) error {
   342  	u, err := ConvertToUnstructured(object)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	list.Items = append(list.Items, u)
   347  	return nil
   348  }