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 }