...

Source file src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go

Documentation: k8s.io/apiextensions-apiserver/test/integration

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package integration
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    29  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    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  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/apimachinery/pkg/util/wait"
    37  	"k8s.io/apimachinery/pkg/watch"
    38  	"k8s.io/client-go/dynamic"
    39  )
    40  
    41  func TestServerUp(t *testing.T) {
    42  	tearDown, _, _, err := fixtures.StartDefaultServerWithClients(t)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	defer tearDown()
    47  }
    48  
    49  func TestNamespaceScopedCRUD(t *testing.T) {
    50  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	defer tearDown()
    55  
    56  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
    57  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  
    62  	ns := "not-the-default"
    63  
    64  	testSimpleCRUD(t, ns, noxuDefinition, dynamicClient)
    65  	testFieldSelector(t, ns, noxuDefinition, dynamicClient)
    66  }
    67  
    68  func TestClusterScopedCRUD(t *testing.T) {
    69  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	defer tearDown()
    74  
    75  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
    76  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  
    81  	ns := ""
    82  	testSimpleCRUD(t, ns, noxuDefinition, dynamicClient)
    83  	testFieldSelector(t, ns, noxuDefinition, dynamicClient)
    84  }
    85  
    86  func testSimpleCRUD(t *testing.T, ns string, noxuDefinition *apiextensionsv1.CustomResourceDefinition, dynamicClient dynamic.Interface) {
    87  	noxuResourceClients := map[string]dynamic.ResourceInterface{}
    88  	noxuWatchs := map[string]watch.Interface{}
    89  	disabledVersions := map[string]bool{}
    90  	for _, v := range noxuDefinition.Spec.Versions {
    91  		disabledVersions[v.Name] = !v.Served
    92  	}
    93  	for _, v := range noxuDefinition.Spec.Versions {
    94  		noxuResourceClients[v.Name] = newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
    95  
    96  		noxuWatch, err := noxuResourceClients[v.Name].Watch(context.TODO(), metav1.ListOptions{})
    97  		if disabledVersions[v.Name] {
    98  			if !errors.IsNotFound(err) {
    99  				t.Errorf("expected the watch operation fail with NotFound for disabled version %s, got error: %v", v.Name, err)
   100  			}
   101  		} else {
   102  			if err != nil {
   103  				t.Fatal(err)
   104  			}
   105  			noxuWatchs[v.Name] = noxuWatch
   106  		}
   107  	}
   108  	defer func() {
   109  		for _, w := range noxuWatchs {
   110  			w.Stop()
   111  		}
   112  	}()
   113  
   114  	for version, noxuResourceClient := range noxuResourceClients {
   115  		createdNoxuInstance, err := instantiateVersionedCustomResource(t, fixtures.NewVersionedNoxuInstance(ns, "foo", version), noxuResourceClient, noxuDefinition, version)
   116  		if disabledVersions[version] {
   117  			if !errors.IsNotFound(err) {
   118  				t.Errorf("expected the CR creation fail with NotFound for disabled version %s, got error: %v", version, err)
   119  			}
   120  			continue
   121  		}
   122  		if err != nil {
   123  			t.Fatalf("unable to create noxu Instance:%v", err)
   124  		}
   125  		if e, a := noxuDefinition.Spec.Group+"/"+version, createdNoxuInstance.GetAPIVersion(); e != a {
   126  			t.Errorf("expected %v, got %v", e, a)
   127  		}
   128  		for watchVersion, noxuWatch := range noxuWatchs {
   129  			select {
   130  			case watchEvent := <-noxuWatch.ResultChan():
   131  				if e, a := watch.Added, watchEvent.Type; e != a {
   132  					t.Errorf("expected %v, got %v", e, a)
   133  					break
   134  				}
   135  				createdObjectMeta, err := meta.Accessor(watchEvent.Object)
   136  				if err != nil {
   137  					t.Fatal(err)
   138  				}
   139  				// it should have a UUID
   140  				if len(createdObjectMeta.GetUID()) == 0 {
   141  					t.Errorf("missing uuid: %#v", watchEvent.Object)
   142  				}
   143  				if e, a := ns, createdObjectMeta.GetNamespace(); e != a {
   144  					t.Errorf("expected %v, got %v", e, a)
   145  				}
   146  				createdTypeMeta, err := meta.TypeAccessor(watchEvent.Object)
   147  				if err != nil {
   148  					t.Fatal(err)
   149  				}
   150  				if e, a := noxuDefinition.Spec.Group+"/"+watchVersion, createdTypeMeta.GetAPIVersion(); e != a {
   151  					t.Errorf("expected %v, got %v", e, a)
   152  				}
   153  				if e, a := noxuDefinition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
   154  					t.Errorf("expected %v, got %v", e, a)
   155  				}
   156  			case <-time.After(5 * time.Second):
   157  				t.Errorf("missing watch event")
   158  			}
   159  		}
   160  
   161  		// Check get for all versions
   162  		for version2, noxuResourceClient2 := range noxuResourceClients {
   163  			// Get test
   164  			gottenNoxuInstance, err := noxuResourceClient2.Get(context.TODO(), "foo", metav1.GetOptions{})
   165  
   166  			if disabledVersions[version2] {
   167  				if !errors.IsNotFound(err) {
   168  					t.Errorf("expected the get operation fail with NotFound for disabled version %s, got error: %v", version2, err)
   169  
   170  				}
   171  			} else {
   172  				if err != nil {
   173  					t.Fatal(err)
   174  				}
   175  
   176  				if e, a := version2, gottenNoxuInstance.GroupVersionKind().Version; !reflect.DeepEqual(e, a) {
   177  					t.Errorf("expected %v, got %v", e, a)
   178  				}
   179  			}
   180  
   181  			// List test
   182  			listWithItem, err := noxuResourceClient2.List(context.TODO(), metav1.ListOptions{})
   183  			if disabledVersions[version2] {
   184  				if !errors.IsNotFound(err) {
   185  					t.Errorf("expected the list operation fail with NotFound for disabled version %s, got error: %v", version2, err)
   186  
   187  				}
   188  			} else {
   189  				if err != nil {
   190  					t.Fatal(err)
   191  				}
   192  				if e, a := 1, len(listWithItem.Items); e != a {
   193  					t.Errorf("expected %v, got %v", e, a)
   194  				}
   195  				if e, a := version2, listWithItem.GroupVersionKind().Version; !reflect.DeepEqual(e, a) {
   196  					t.Errorf("expected %v, got %v", e, a)
   197  				}
   198  				if e, a := version2, listWithItem.Items[0].GroupVersionKind().Version; !reflect.DeepEqual(e, a) {
   199  					t.Errorf("expected %v, got %v", e, a)
   200  				}
   201  			}
   202  		}
   203  
   204  		// Update test
   205  		for version2, noxuResourceClient2 := range noxuResourceClients {
   206  			var gottenNoxuInstance *unstructured.Unstructured
   207  			if disabledVersions[version2] {
   208  				gottenNoxuInstance = &unstructured.Unstructured{}
   209  				gottenNoxuInstance.SetName("foo")
   210  			} else {
   211  				gottenNoxuInstance, err = noxuResourceClient2.Get(context.TODO(), "foo", metav1.GetOptions{})
   212  				if err != nil {
   213  					t.Fatal(err)
   214  				}
   215  			}
   216  
   217  			gottenNoxuInstance.Object["updated"] = version2
   218  			updatedNoxuInstance, err := noxuResourceClient2.Update(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
   219  			if disabledVersions[version2] {
   220  				if !errors.IsNotFound(err) {
   221  					t.Errorf("expected the update operation fail with NotFound for disabled version %s, got error: %v", version2, err)
   222  				}
   223  			} else {
   224  				if err != nil {
   225  					t.Fatal(err)
   226  				}
   227  
   228  				if updated, ok := updatedNoxuInstance.Object["updated"]; !ok {
   229  					t.Errorf("expected string 'updated' field")
   230  				} else if updated, ok := updated.(string); !ok || updated != version2 {
   231  					t.Errorf("expected string 'updated' field to equal %q, got %q of type %T", version2, updated, updated)
   232  				}
   233  
   234  				if e, a := version2, updatedNoxuInstance.GroupVersionKind().Version; !reflect.DeepEqual(e, a) {
   235  					t.Errorf("expected %v, got %v", e, a)
   236  				}
   237  
   238  				for _, noxuWatch := range noxuWatchs {
   239  					select {
   240  					case watchEvent := <-noxuWatch.ResultChan():
   241  						eventMetadata, err := meta.Accessor(watchEvent.Object)
   242  						if err != nil {
   243  							t.Fatal(err)
   244  						}
   245  
   246  						if watchEvent.Type != watch.Modified {
   247  							t.Errorf("expected modified event, got %v", watchEvent.Type)
   248  							break
   249  						}
   250  
   251  						// it should have a UUID
   252  						createdMetadata, err := meta.Accessor(createdNoxuInstance)
   253  						if err != nil {
   254  							t.Fatal(err)
   255  						}
   256  						if e, a := createdMetadata.GetUID(), eventMetadata.GetUID(); e != a {
   257  							t.Errorf("expected equal UID for (expected) %v, and (actual) %v", createdNoxuInstance, watchEvent.Object)
   258  						}
   259  
   260  					case <-time.After(5 * time.Second):
   261  						t.Errorf("missing watch event")
   262  					}
   263  				}
   264  			}
   265  		}
   266  
   267  		// Delete test
   268  		if err := noxuResourceClient.Delete(context.TODO(), "foo", *metav1.NewDeleteOptions(0)); err != nil {
   269  			t.Fatal(err)
   270  		}
   271  
   272  		listWithoutItem, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   273  		if err != nil {
   274  			t.Fatal(err)
   275  		}
   276  		if e, a := 0, len(listWithoutItem.Items); e != a {
   277  			t.Errorf("expected %v, got %v", e, a)
   278  		}
   279  
   280  		for _, noxuWatch := range noxuWatchs {
   281  			select {
   282  			case watchEvent := <-noxuWatch.ResultChan():
   283  				eventMetadata, err := meta.Accessor(watchEvent.Object)
   284  				if err != nil {
   285  					t.Fatal(err)
   286  				}
   287  
   288  				if watchEvent.Type != watch.Deleted {
   289  					t.Errorf("expected delete event, got %v", watchEvent.Type)
   290  					break
   291  				}
   292  
   293  				// it should have a UUID
   294  				createdMetadata, err := meta.Accessor(createdNoxuInstance)
   295  				if err != nil {
   296  					t.Fatal(err)
   297  				}
   298  				if e, a := createdMetadata.GetUID(), eventMetadata.GetUID(); e != a {
   299  					t.Errorf("expected equal UID for (expected) %v, and (actual) %v", createdNoxuInstance, watchEvent.Object)
   300  				}
   301  
   302  			case <-time.After(5 * time.Second):
   303  				t.Errorf("missing watch event")
   304  			}
   305  		}
   306  
   307  		// Delete test
   308  		if err := noxuResourceClient.DeleteCollection(context.TODO(), *metav1.NewDeleteOptions(0), metav1.ListOptions{}); err != nil {
   309  			t.Fatal(err)
   310  		}
   311  	}
   312  }
   313  
   314  func TestInvalidCRUD(t *testing.T) {
   315  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  	defer tearDown()
   320  
   321  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
   322  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   323  	if err != nil {
   324  		t.Fatal(err)
   325  	}
   326  
   327  	noxuResourceClients := map[string]dynamic.ResourceInterface{}
   328  	noxuWatchs := map[string]watch.Interface{}
   329  	disabledVersions := map[string]bool{}
   330  	for _, v := range noxuDefinition.Spec.Versions {
   331  		disabledVersions[v.Name] = !v.Served
   332  	}
   333  	for _, v := range noxuDefinition.Spec.Versions {
   334  		noxuResourceClients[v.Name] = newNamespacedCustomResourceVersionedClient("", dynamicClient, noxuDefinition, v.Name)
   335  
   336  		noxuWatch, err := noxuResourceClients[v.Name].Watch(context.TODO(), metav1.ListOptions{})
   337  		if disabledVersions[v.Name] {
   338  			if !errors.IsNotFound(err) {
   339  				t.Errorf("expected the watch operation fail with NotFound for disabled version %s, got error: %v", v.Name, err)
   340  			}
   341  		} else {
   342  			if err != nil {
   343  				t.Fatal(err)
   344  			}
   345  			noxuWatchs[v.Name] = noxuWatch
   346  		}
   347  	}
   348  	defer func() {
   349  		for _, w := range noxuWatchs {
   350  			w.Stop()
   351  		}
   352  	}()
   353  
   354  	for version, noxuResourceClient := range noxuResourceClients {
   355  		// Case when typeless Unstructured object is passed
   356  		typelessInstance := &unstructured.Unstructured{}
   357  		if _, err := noxuResourceClient.Create(context.TODO(), typelessInstance, metav1.CreateOptions{}); !errors.IsBadRequest(err) {
   358  			t.Errorf("expected badrequest for submitting empty object, got %#v", err)
   359  		}
   360  		// Case when apiVersion and Kind would be set up from GVK, but no other objects are present
   361  		typedNoBodyInstance := &unstructured.Unstructured{
   362  			Object: map[string]interface{}{
   363  				"apiVersion": "mygroup.example.com/" + version,
   364  				"kind":       "WishIHadChosenNoxu",
   365  			},
   366  		}
   367  		if _, err := noxuResourceClient.Create(context.TODO(), typedNoBodyInstance, metav1.CreateOptions{}); !errors.IsInvalid(err) {
   368  			t.Errorf("expected invalid request for submitting malformed object, got %#v", err)
   369  		}
   370  	}
   371  }
   372  
   373  func testFieldSelector(t *testing.T, ns string, noxuDefinition *apiextensionsv1.CustomResourceDefinition, dynamicClient dynamic.Interface) {
   374  	noxuResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)
   375  	initialList, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	if e, a := 0, len(initialList.Items); e != a {
   380  		t.Errorf("expected %v, got %v", e, a)
   381  	}
   382  	initialListTypeMeta, err := meta.TypeAccessor(initialList)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Versions[0].Name, initialListTypeMeta.GetAPIVersion(); e != a {
   387  		t.Errorf("expected %v, got %v", e, a)
   388  	}
   389  	if e, a := noxuDefinition.Spec.Names.ListKind, initialListTypeMeta.GetKind(); e != a {
   390  		t.Errorf("expected %v, got %v", e, a)
   391  	}
   392  
   393  	initialListListMeta, err := meta.ListAccessor(initialList)
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	noxuWatch, err := noxuResourceClient.Watch(
   398  		context.TODO(),
   399  		metav1.ListOptions{
   400  			ResourceVersion: initialListListMeta.GetResourceVersion(),
   401  			FieldSelector:   "metadata.name=foo",
   402  		},
   403  	)
   404  	if err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	defer noxuWatch.Stop()
   408  
   409  	_, err = instantiateCustomResource(t, fixtures.NewNoxuInstance(ns, "bar"), noxuResourceClient, noxuDefinition)
   410  	if err != nil {
   411  		t.Fatalf("unable to create noxu Instance:%v", err)
   412  	}
   413  	createdNoxuInstanceFoo, err := instantiateCustomResource(t, fixtures.NewNoxuInstance(ns, "foo"), noxuResourceClient, noxuDefinition)
   414  	if err != nil {
   415  		t.Fatalf("unable to create noxu Instance:%v", err)
   416  	}
   417  
   418  	select {
   419  	case watchEvent := <-noxuWatch.ResultChan():
   420  		if e, a := watch.Added, watchEvent.Type; e != a {
   421  			t.Errorf("expected %v, got %v", e, a)
   422  			break
   423  		}
   424  		createdObjectMeta, err := meta.Accessor(watchEvent.Object)
   425  		if err != nil {
   426  			t.Fatal(err)
   427  		}
   428  		// it should have a UUID
   429  		if len(createdObjectMeta.GetUID()) == 0 {
   430  			t.Errorf("missing uuid: %#v", watchEvent.Object)
   431  		}
   432  		if e, a := ns, createdObjectMeta.GetNamespace(); e != a {
   433  			t.Errorf("expected %v, got %v", e, a)
   434  		}
   435  		if e, a := "foo", createdObjectMeta.GetName(); e != a {
   436  			t.Errorf("expected %v, got %v", e, a)
   437  		}
   438  		createdTypeMeta, err := meta.TypeAccessor(watchEvent.Object)
   439  		if err != nil {
   440  			t.Fatal(err)
   441  		}
   442  		if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Versions[0].Name, createdTypeMeta.GetAPIVersion(); e != a {
   443  			t.Errorf("expected %v, got %v", e, a)
   444  		}
   445  		if e, a := noxuDefinition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
   446  			t.Errorf("expected %v, got %v", e, a)
   447  		}
   448  
   449  	case <-time.After(5 * time.Second):
   450  		t.Errorf("missing watch event")
   451  	}
   452  
   453  	gottenNoxuInstance, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
   454  	if err != nil {
   455  		t.Fatal(err)
   456  	}
   457  	if e, a := createdNoxuInstanceFoo, gottenNoxuInstance; !reflect.DeepEqual(e, a) {
   458  		t.Errorf("expected %v, got %v", e, a)
   459  	}
   460  
   461  	listWithItem, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{FieldSelector: "metadata.name=foo"})
   462  	if err != nil {
   463  		t.Fatal(err)
   464  	}
   465  	if e, a := 1, len(listWithItem.Items); e != a {
   466  		t.Errorf("expected %v, got %v", e, a)
   467  	}
   468  	if e, a := *createdNoxuInstanceFoo, listWithItem.Items[0]; !reflect.DeepEqual(e, a) {
   469  		t.Errorf("expected %v, got %v", e, a)
   470  	}
   471  
   472  	if err := noxuResourceClient.Delete(context.TODO(), "bar", metav1.DeleteOptions{}); err != nil {
   473  		t.Fatal(err)
   474  	}
   475  	if err := noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{}); err != nil {
   476  		t.Fatal(err)
   477  	}
   478  
   479  	listWithoutItem, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   480  	if err != nil {
   481  		t.Fatal(err)
   482  	}
   483  	if e, a := 0, len(listWithoutItem.Items); e != a {
   484  		t.Errorf("expected %v, got %v", e, a)
   485  	}
   486  
   487  	select {
   488  	case watchEvent := <-noxuWatch.ResultChan():
   489  		if e, a := watch.Deleted, watchEvent.Type; e != a {
   490  			t.Errorf("expected %v, got %v", e, a)
   491  			break
   492  		}
   493  		deletedObjectMeta, err := meta.Accessor(watchEvent.Object)
   494  		if err != nil {
   495  			t.Fatal(err)
   496  		}
   497  		// it should have a UUID
   498  		createdObjectMeta, err := meta.Accessor(createdNoxuInstanceFoo)
   499  		if err != nil {
   500  			t.Fatal(err)
   501  		}
   502  		if e, a := createdObjectMeta.GetUID(), deletedObjectMeta.GetUID(); e != a {
   503  			t.Errorf("expected %v, got %v", e, a)
   504  		}
   505  		if e, a := ns, createdObjectMeta.GetNamespace(); e != a {
   506  			t.Errorf("expected %v, got %v", e, a)
   507  		}
   508  		if e, a := "foo", createdObjectMeta.GetName(); e != a {
   509  			t.Errorf("expected %v, got %v", e, a)
   510  		}
   511  
   512  	case <-time.After(5 * time.Second):
   513  		t.Errorf("missing watch event")
   514  	}
   515  }
   516  
   517  func TestDiscovery(t *testing.T) {
   518  	group := "mygroup.example.com"
   519  	version := "v1beta1"
   520  
   521  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   522  	if err != nil {
   523  		t.Fatal(err)
   524  	}
   525  	defer tearDown()
   526  
   527  	scope := apiextensionsv1.NamespaceScoped
   528  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(scope)
   529  	if _, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient); err != nil {
   530  		t.Fatal(err)
   531  	}
   532  
   533  	// check whether it shows up in discovery properly
   534  	resources, err := apiExtensionClient.Discovery().ServerResourcesForGroupVersion(group + "/" + version)
   535  	if err != nil {
   536  		t.Fatal(err)
   537  	}
   538  
   539  	if len(resources.APIResources) != 1 {
   540  		t.Fatalf("Expected exactly the resource \"noxus\" in group version %v/%v via discovery, got: %v", group, version, resources.APIResources)
   541  	}
   542  
   543  	r := resources.APIResources[0]
   544  	if r.Name != "noxus" {
   545  		t.Fatalf("Expected exactly the resource \"noxus\" in group version %v/%v via discovery, got: %v", group, version, r.Name)
   546  	}
   547  	if r.Kind != "WishIHadChosenNoxu" {
   548  		t.Fatalf("Expected exactly the kind \"WishIHadChosenNoxu\" in group version %v/%v via discovery, got: %v", group, version, r.Kind)
   549  	}
   550  
   551  	s := []string{"foo", "bar", "abc", "def"}
   552  	if !reflect.DeepEqual(r.ShortNames, s) {
   553  		t.Fatalf("Expected exactly the shortnames `foo, bar, abc, def` in group version %v/%v via discovery, got: %v", group, version, r.ShortNames)
   554  	}
   555  
   556  	sort.Strings(r.Verbs)
   557  	expectedVerbs := []string{"create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"}
   558  	if !reflect.DeepEqual([]string(r.Verbs), expectedVerbs) {
   559  		t.Fatalf("Unexpected verbs for resource \"noxus\" in group version %v/%v via discovery: expected=%v got=%v", group, version, expectedVerbs, r.Verbs)
   560  	}
   561  
   562  	if !reflect.DeepEqual(r.Categories, []string{"all"}) {
   563  		t.Fatalf("Expected exactly the category \"all\" in group version %v/%v via discovery, got: %v", group, version, r.Categories)
   564  	}
   565  }
   566  
   567  func TestNoNamespaceReject(t *testing.T) {
   568  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   569  	if err != nil {
   570  		t.Fatal(err)
   571  	}
   572  	defer tearDown()
   573  
   574  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   575  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   576  	if err != nil {
   577  		t.Fatal(err)
   578  	}
   579  
   580  	ns := ""
   581  	noxuResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)
   582  	initialList, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   583  	if err != nil {
   584  		t.Fatal(err)
   585  	}
   586  	if e, a := 0, len(initialList.Items); e != a {
   587  		t.Errorf("expected %v, got %v", e, a)
   588  	}
   589  	initialListTypeMeta, err := meta.TypeAccessor(initialList)
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Versions[0].Name, initialListTypeMeta.GetAPIVersion(); e != a {
   594  		t.Errorf("expected %v, got %v", e, a)
   595  	}
   596  	if e, a := noxuDefinition.Spec.Names.ListKind, initialListTypeMeta.GetKind(); e != a {
   597  		t.Errorf("expected %v, got %v", e, a)
   598  	}
   599  
   600  	createdNoxuInstance, err := instantiateCustomResource(t, fixtures.NewNoxuInstance(ns, "foo"), noxuResourceClient, noxuDefinition)
   601  	if err == nil {
   602  		t.Fatalf("unexpected non-error: an empty namespace may not be set during creation while creating noxu instance: %v ", createdNoxuInstance)
   603  	}
   604  }
   605  
   606  func TestSameNameDiffNamespace(t *testing.T) {
   607  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   608  	if err != nil {
   609  		t.Fatal(err)
   610  	}
   611  	defer tearDown()
   612  
   613  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   614  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   615  	if err != nil {
   616  		t.Fatal(err)
   617  	}
   618  
   619  	ns1 := "namespace-1"
   620  	testSimpleCRUD(t, ns1, noxuDefinition, dynamicClient)
   621  	ns2 := "namespace-2"
   622  	testSimpleCRUD(t, ns2, noxuDefinition, dynamicClient)
   623  
   624  }
   625  
   626  func TestPreserveInt(t *testing.T) {
   627  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   628  	if err != nil {
   629  		t.Fatal(err)
   630  	}
   631  	defer tearDown()
   632  
   633  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
   634  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   635  	if err != nil {
   636  		t.Fatal(err)
   637  	}
   638  
   639  	ns := "not-the-default"
   640  	noxuNamespacedResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)
   641  
   642  	noxuInstanceToCreate := fixtures.NewNoxuInstance(ns, "foo")
   643  	createdNoxuInstance, err := noxuNamespacedResourceClient.Create(context.TODO(), noxuInstanceToCreate, metav1.CreateOptions{})
   644  	if err != nil {
   645  		t.Fatal(err)
   646  	}
   647  
   648  	originalJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, createdNoxuInstance)
   649  	if err != nil {
   650  		t.Fatalf("unexpected error: %v", err)
   651  	}
   652  
   653  	gottenNoxuInstance, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON)
   654  	if err != nil {
   655  		t.Fatalf("unexpected error: %v", err)
   656  	}
   657  
   658  	// Check if int is preserved.
   659  	unstructuredObj := gottenNoxuInstance.(*unstructured.Unstructured).Object
   660  	num := unstructuredObj["num"].(map[string]interface{})
   661  	num1 := num["num1"].(int64)
   662  	num2 := num["num2"].(int64)
   663  	if num1 != 9223372036854775807 || num2 != 1000000 {
   664  		t.Errorf("Expected %v, got %v, %v", `9223372036854775807, 1000000`, num1, num2)
   665  	}
   666  }
   667  
   668  func TestPatch(t *testing.T) {
   669  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   670  	if err != nil {
   671  		t.Fatal(err)
   672  	}
   673  	defer tearDown()
   674  
   675  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
   676  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   677  	if err != nil {
   678  		t.Fatal(err)
   679  	}
   680  
   681  	ns := "not-the-default"
   682  	noxuNamespacedResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)
   683  
   684  	t.Logf("Creating foo")
   685  	noxuInstanceToCreate := fixtures.NewNoxuInstance(ns, "foo")
   686  	_, err = noxuNamespacedResourceClient.Create(context.TODO(), noxuInstanceToCreate, metav1.CreateOptions{})
   687  	if err != nil {
   688  		t.Fatal(err)
   689  	}
   690  
   691  	t.Logf("Patching .num.num2 to 999")
   692  	patch := []byte(`{"num": {"num2":999}}`)
   693  	patchedNoxuInstance, err := noxuNamespacedResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{})
   694  	if err != nil {
   695  		t.Fatalf("unexpected error: %v", err)
   696  	}
   697  	expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "num", "num2")
   698  	rv, found, err := unstructured.NestedString(patchedNoxuInstance.UnstructuredContent(), "metadata", "resourceVersion")
   699  	if err != nil {
   700  		t.Fatal(err)
   701  	}
   702  	if !found {
   703  		t.Fatalf("metadata.resourceVersion not found")
   704  	}
   705  
   706  	// a patch with no change
   707  	t.Logf("Patching .num.num2 again to 999")
   708  	patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{})
   709  	if err != nil {
   710  		t.Fatalf("unexpected error: %v", err)
   711  	}
   712  	// make sure no-op patch does not increment resourceVersion
   713  	expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "num", "num2")
   714  	expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
   715  
   716  	// an empty patch
   717  	t.Logf("Applying empty patch")
   718  	patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{})
   719  	if err != nil {
   720  		t.Fatalf("unexpected error: %v", err)
   721  	}
   722  	// an empty patch is a no-op patch. make sure it does not increment resourceVersion
   723  	expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "num", "num2")
   724  	expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
   725  
   726  	originalJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, patchedNoxuInstance)
   727  	if err != nil {
   728  		t.Fatalf("unexpected error: %v", err)
   729  	}
   730  
   731  	gottenNoxuInstance, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON)
   732  	if err != nil {
   733  		t.Fatalf("unexpected error: %v", err)
   734  	}
   735  
   736  	// Check if int is preserved.
   737  	unstructuredObj := gottenNoxuInstance.(*unstructured.Unstructured).Object
   738  	num := unstructuredObj["num"].(map[string]interface{})
   739  	num1 := num["num1"].(int64)
   740  	num2 := num["num2"].(int64)
   741  	if num1 != 9223372036854775807 || num2 != 999 {
   742  		t.Errorf("Expected %v, got %v, %v", `9223372036854775807, 999`, num1, num2)
   743  	}
   744  }
   745  
   746  func TestCrossNamespaceListWatch(t *testing.T) {
   747  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   748  	if err != nil {
   749  		t.Fatal(err)
   750  	}
   751  	defer tearDown()
   752  
   753  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   754  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   755  	if err != nil {
   756  		t.Fatal(err)
   757  	}
   758  
   759  	ns := ""
   760  	noxuResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)
   761  	initialList, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   762  	if err != nil {
   763  		t.Fatal(err)
   764  	}
   765  	if e, a := 0, len(initialList.Items); e != a {
   766  		t.Errorf("expected %v, got %v", e, a)
   767  	}
   768  
   769  	initialListListMeta, err := meta.ListAccessor(initialList)
   770  	if err != nil {
   771  		t.Fatal(err)
   772  	}
   773  
   774  	noxuWatch, err := noxuResourceClient.Watch(context.TODO(), metav1.ListOptions{ResourceVersion: initialListListMeta.GetResourceVersion()})
   775  	if err != nil {
   776  		t.Fatal(err)
   777  	}
   778  	defer noxuWatch.Stop()
   779  
   780  	instances := make(map[string]*unstructured.Unstructured)
   781  	ns1 := "namespace-1"
   782  	noxuNamespacedResourceClient1 := newNamespacedCustomResourceClient(ns1, dynamicClient, noxuDefinition)
   783  	instances[ns1] = createInstanceWithNamespaceHelper(t, ns1, "foo1", noxuNamespacedResourceClient1, noxuDefinition)
   784  	noxuNamespacesWatch1, err := noxuNamespacedResourceClient1.Watch(context.TODO(), metav1.ListOptions{ResourceVersion: initialListListMeta.GetResourceVersion()})
   785  	if err != nil {
   786  		t.Fatalf("Failed to watch namespace: %s, error: %v", ns1, err)
   787  	}
   788  	defer noxuNamespacesWatch1.Stop()
   789  
   790  	ns2 := "namespace-2"
   791  	noxuNamespacedResourceClient2 := newNamespacedCustomResourceClient(ns2, dynamicClient, noxuDefinition)
   792  	instances[ns2] = createInstanceWithNamespaceHelper(t, ns2, "foo2", noxuNamespacedResourceClient2, noxuDefinition)
   793  	noxuNamespacesWatch2, err := noxuNamespacedResourceClient2.Watch(context.TODO(), metav1.ListOptions{ResourceVersion: initialListListMeta.GetResourceVersion()})
   794  	if err != nil {
   795  		t.Fatalf("Failed to watch namespace: %s, error: %v", ns2, err)
   796  	}
   797  	defer noxuNamespacesWatch2.Stop()
   798  
   799  	createdList, err := noxuResourceClient.List(context.TODO(), metav1.ListOptions{})
   800  	if err != nil {
   801  		t.Fatal(err)
   802  	}
   803  
   804  	if e, a := 2, len(createdList.Items); e != a {
   805  		t.Errorf("expected %v, got %v", e, a)
   806  	}
   807  
   808  	for _, a := range createdList.Items {
   809  		if e := instances[a.GetNamespace()]; !reflect.DeepEqual(e, &a) {
   810  			t.Errorf("expected %v, got %v", e, a)
   811  		}
   812  	}
   813  
   814  	addEvents := 0
   815  	for addEvents < 2 {
   816  		select {
   817  		case watchEvent := <-noxuWatch.ResultChan():
   818  			if e, a := watch.Added, watchEvent.Type; e != a {
   819  				t.Fatalf("expected %v, got %v", e, a)
   820  			}
   821  			createdObjectMeta, err := meta.Accessor(watchEvent.Object)
   822  			if err != nil {
   823  				t.Fatal(err)
   824  			}
   825  			if len(createdObjectMeta.GetUID()) == 0 {
   826  				t.Errorf("missing uuid: %#v", watchEvent.Object)
   827  			}
   828  			createdTypeMeta, err := meta.TypeAccessor(watchEvent.Object)
   829  			if err != nil {
   830  				t.Fatal(err)
   831  			}
   832  			if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Versions[0].Name, createdTypeMeta.GetAPIVersion(); e != a {
   833  				t.Errorf("expected %v, got %v", e, a)
   834  			}
   835  			if e, a := noxuDefinition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
   836  				t.Errorf("expected %v, got %v", e, a)
   837  			}
   838  			delete(instances, createdObjectMeta.GetNamespace())
   839  			addEvents++
   840  		case <-time.After(5 * time.Second):
   841  			t.Fatalf("missing watch event")
   842  		}
   843  	}
   844  	if e, a := 0, len(instances); e != a {
   845  		t.Errorf("expected %v, got %v", e, a)
   846  	}
   847  
   848  	checkNamespacesWatchHelper(t, ns1, noxuNamespacesWatch1)
   849  	checkNamespacesWatchHelper(t, ns2, noxuNamespacesWatch2)
   850  }
   851  
   852  func createInstanceWithNamespaceHelper(t *testing.T, ns string, name string, noxuNamespacedResourceClient dynamic.ResourceInterface, noxuDefinition *apiextensionsv1.CustomResourceDefinition) *unstructured.Unstructured {
   853  	createdInstance, err := instantiateCustomResource(t, fixtures.NewNoxuInstance(ns, name), noxuNamespacedResourceClient, noxuDefinition)
   854  	if err != nil {
   855  		t.Fatalf("unable to create noxu Instance:%v", err)
   856  	}
   857  	return createdInstance
   858  }
   859  
   860  func checkNamespacesWatchHelper(t *testing.T, ns string, namespacedwatch watch.Interface) {
   861  	namespacedAddEvent := 0
   862  	for namespacedAddEvent < 2 {
   863  		select {
   864  		case watchEvent := <-namespacedwatch.ResultChan():
   865  			// Check that the namespaced watch only has one result
   866  			if namespacedAddEvent > 0 {
   867  				t.Fatalf("extra watch event")
   868  			}
   869  			if e, a := watch.Added, watchEvent.Type; e != a {
   870  				t.Fatalf("expected %v, got %v", e, a)
   871  			}
   872  			createdObjectMeta, err := meta.Accessor(watchEvent.Object)
   873  			if err != nil {
   874  				t.Fatal(err)
   875  			}
   876  			if e, a := ns, createdObjectMeta.GetNamespace(); e != a {
   877  				t.Errorf("expected %v, got %v", e, a)
   878  			}
   879  		case <-time.After(5 * time.Second):
   880  			if namespacedAddEvent != 1 {
   881  				t.Fatalf("missing watch event")
   882  			}
   883  		}
   884  		namespacedAddEvent++
   885  	}
   886  }
   887  
   888  func TestNameConflict(t *testing.T) {
   889  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   890  	if err != nil {
   891  		t.Fatal(err)
   892  	}
   893  	defer tearDown()
   894  
   895  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   896  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   897  	if err != nil {
   898  		t.Fatal(err)
   899  	}
   900  
   901  	noxu2Definition := fixtures.NewNoxu2CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   902  	_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), noxu2Definition, metav1.CreateOptions{})
   903  	if err != nil {
   904  		t.Fatal(err)
   905  	}
   906  
   907  	// A NameConflict occurs
   908  	err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
   909  		crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), noxu2Definition.Name, metav1.GetOptions{})
   910  		if err != nil {
   911  			return false, err
   912  		}
   913  
   914  		for _, condition := range crd.Status.Conditions {
   915  			if condition.Type == apiextensionsv1.NamesAccepted && condition.Status == apiextensionsv1.ConditionFalse {
   916  				return true, nil
   917  			}
   918  		}
   919  		return false, nil
   920  	})
   921  	if err != nil {
   922  		t.Fatal(err)
   923  	}
   924  
   925  	err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
   926  	if err != nil {
   927  		t.Fatal(err)
   928  	}
   929  
   930  	// Names are now accepted
   931  	err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
   932  		crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), noxu2Definition.Name, metav1.GetOptions{})
   933  		if err != nil {
   934  			return false, err
   935  		}
   936  
   937  		for _, condition := range crd.Status.Conditions {
   938  			if condition.Type == apiextensionsv1.NamesAccepted && condition.Status == apiextensionsv1.ConditionTrue {
   939  				return true, nil
   940  			}
   941  		}
   942  		return false, nil
   943  	})
   944  	if err != nil {
   945  		t.Fatal(err)
   946  	}
   947  }
   948  
   949  func TestStatusGetAndPatch(t *testing.T) {
   950  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   951  	if err != nil {
   952  		t.Fatal(err)
   953  	}
   954  	defer tearDown()
   955  
   956  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   957  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   958  	if err != nil {
   959  		t.Fatal(err)
   960  	}
   961  
   962  	// make sure we don't get 405 Method Not Allowed from Getting CRD/status subresource
   963  	result := &apiextensionsv1.CustomResourceDefinition{}
   964  	err = apiExtensionClient.ApiextensionsV1().RESTClient().Get().
   965  		Resource("customresourcedefinitions").
   966  		Name(noxuDefinition.Name).
   967  		SubResource("status").
   968  		Do(context.TODO()).
   969  		Into(result)
   970  	if err != nil {
   971  		t.Fatal(err)
   972  	}
   973  
   974  	// make sure we don't get 405 Method Not Allowed from Patching CRD/status subresource
   975  	_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().
   976  		Patch(context.TODO(), noxuDefinition.Name, types.StrategicMergePatchType,
   977  			[]byte(fmt.Sprintf(`{"labels":{"test-label":"dummy"}}`)), metav1.PatchOptions{},
   978  			"status")
   979  	if err != nil {
   980  		t.Fatal(err)
   981  	}
   982  }
   983  
   984  func expectInt64(t *testing.T, obj map[string]interface{}, value int64, pth ...string) {
   985  	if v, found, err := unstructured.NestedInt64(obj, pth...); err != nil {
   986  		t.Fatalf("failed to access .%s: %v", strings.Join(pth, "."), err)
   987  	} else if !found {
   988  		t.Fatalf("failed to find .%s", strings.Join(pth, "."))
   989  	} else if v != value {
   990  		t.Fatalf("wanted %d at .%s, got %d", value, strings.Join(pth, "."), v)
   991  	}
   992  }
   993  func expectString(t *testing.T, obj map[string]interface{}, value string, pth ...string) {
   994  	if v, found, err := unstructured.NestedString(obj, pth...); err != nil {
   995  		t.Fatalf("failed to access .%s: %v", strings.Join(pth, "."), err)
   996  	} else if !found {
   997  		t.Fatalf("failed to find .%s", strings.Join(pth, "."))
   998  	} else if v != value {
   999  		t.Fatalf("wanted %q at .%s, got %q", value, strings.Join(pth, "."), v)
  1000  	}
  1001  }
  1002  

View as plain text