mirror of https://github.com/k3s-io/k3s
Merge pull request #46200 from nikhita/crd-test-name-collision
Automatic merge from submit-queue (batch tested with PRs 45699, 46200, 46335, 46599) apiextensions: add integration test for name conflicts **What this PR does / why we need it**: Add integration test for name conflicts. Create 2 CRDs with name conflict. The controller sets the NameConflict condition on the second one. Delete the first one. The second CRD should now get accepted. Update: - [x] Add integration test for name conflicts - [x] Fix naming controller so that when a resource is deleted, other resources in the same group are added to the queue - [x] Add integration test for self link (cluster scoped resources) - [x] Add bigger poll interval - [x] Fix DeleteCustomResourceDefinition poll condition **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: for #45511 **Special notes for your reviewer**: **Release note**: ``` NONE ``` @stttspull/6/head
commit
313dc6b5a5
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -19,6 +19,7 @@ package status
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -230,6 +231,11 @@ func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.S
|
|||
func (c *NamingConditionController) sync(key string) error {
|
||||
inCustomResourceDefinition, err := c.crdLister.Get(key)
|
||||
if apierrors.IsNotFound(err) {
|
||||
// CRD was deleted and has freed its names.
|
||||
// Reconsider all other CRDs in the same group.
|
||||
if err := c.requeueAllOtherGroupCRDs(key); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -264,14 +270,8 @@ func (c *NamingConditionController) sync(key string) error {
|
|||
|
||||
// we updated our status, so we may be releasing a name. When this happens, we need to rekick everything in our group
|
||||
// if we fail to rekick, just return as normal. We'll get everything on a resync
|
||||
list, err := c.crdLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, curr := range list {
|
||||
if curr.Spec.Group == crd.Spec.Group {
|
||||
c.queue.Add(curr.Name)
|
||||
}
|
||||
if err := c.requeueAllOtherGroupCRDs(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -358,3 +358,17 @@ func (c *NamingConditionController) deleteCustomResourceDefinition(obj interface
|
|||
glog.V(4).Infof("Deleting %q", castObj.Name)
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) requeueAllOtherGroupCRDs(name string) error {
|
||||
pluralGroup := strings.SplitN(name, ".", 2)
|
||||
list, err := c.crdLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, curr := range list {
|
||||
if curr.Spec.Group == pluralGroup[1] && curr.Name != name {
|
||||
c.queue.Add(curr.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ go_test(
|
|||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library",
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
apiextensionsv1alpha1 "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1"
|
||||
|
@ -309,6 +310,7 @@ func TestSelfLink(t *testing.T) {
|
|||
}
|
||||
defer close(stopCh)
|
||||
|
||||
// namespace scoped
|
||||
noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1alpha1.NamespaceScoped)
|
||||
noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
|
||||
if err != nil {
|
||||
|
@ -318,7 +320,7 @@ func TestSelfLink(t *testing.T) {
|
|||
ns := "not-the-default"
|
||||
noxuNamespacedResourceClient := noxuVersionClient.Resource(&metav1.APIResource{
|
||||
Name: noxuDefinition.Spec.Names.Plural,
|
||||
Namespaced: true,
|
||||
Namespaced: noxuDefinition.Spec.Scope == apiextensionsv1alpha1.NamespaceScoped,
|
||||
}, ns)
|
||||
|
||||
noxuInstanceToCreate := testserver.NewNoxuInstance(ns, "foo")
|
||||
|
@ -331,8 +333,27 @@ func TestSelfLink(t *testing.T) {
|
|||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
// TODO add test for cluster scoped self-link when its available
|
||||
// cluster scoped
|
||||
curletDefinition := testserver.NewCurletCustomResourceDefinition(apiextensionsv1alpha1.ClusterScoped)
|
||||
curletVersionClient, err := testserver.CreateNewCustomResourceDefinition(curletDefinition, apiExtensionClient, clientPool)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curletResourceClient := curletVersionClient.Resource(&metav1.APIResource{
|
||||
Name: curletDefinition.Spec.Names.Plural,
|
||||
Namespaced: curletDefinition.Spec.Scope == apiextensionsv1alpha1.NamespaceScoped,
|
||||
}, ns)
|
||||
|
||||
curletInstanceToCreate := testserver.NewCurletInstance(ns, "foo")
|
||||
createdCurletInstance, err := curletResourceClient.Create(curletInstanceToCreate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e, a := "/apis/mygroup.example.com/v1alpha1/foo", createdCurletInstance.GetSelfLink(); e != a {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreserveInt(t *testing.T) {
|
||||
|
@ -515,3 +536,64 @@ func checkNamespacesWatchHelper(t *testing.T, ns string, namespacedwatch watch.I
|
|||
namespacedAddEvent++
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameConflict(t *testing.T) {
|
||||
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer close(stopCh)
|
||||
|
||||
noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1alpha1.NamespaceScoped)
|
||||
_, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
noxu2Definition := testserver.NewNoxu2CustomResourceDefinition(apiextensionsv1alpha1.NamespaceScoped)
|
||||
_, err = apiExtensionClient.Apiextensions().CustomResourceDefinitions().Create(noxu2Definition)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// A NameConflict occurs
|
||||
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
crd, err := testserver.GetCustomResourceDefinition(noxu2Definition, apiExtensionClient)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, condition := range crd.Status.Conditions {
|
||||
if condition.Type == apiextensionsv1alpha1.NamesAccepted && condition.Status == apiextensionsv1alpha1.ConditionFalse {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = testserver.DeleteCustomResourceDefinition(noxuDefinition, apiExtensionClient)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Names are now accepted
|
||||
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
crd, err := testserver.GetCustomResourceDefinition(noxu2Definition, apiExtensionClient)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, condition := range crd.Status.Conditions {
|
||||
if condition.Type == apiextensionsv1alpha1.NamesAccepted && condition.Status == apiextensionsv1alpha1.ConditionTrue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,24 @@ func NewNoxuInstance(namespace, name string) *unstructured.Unstructured {
|
|||
}
|
||||
}
|
||||
|
||||
func NewNoxu2CustomResourceDefinition(scope apiextensionsv1alpha1.ResourceScope) *apiextensionsv1alpha1.CustomResourceDefinition {
|
||||
return &apiextensionsv1alpha1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "noxus2.mygroup.example.com"},
|
||||
Spec: apiextensionsv1alpha1.CustomResourceDefinitionSpec{
|
||||
Group: "mygroup.example.com",
|
||||
Version: "v1alpha1",
|
||||
Names: apiextensionsv1alpha1.CustomResourceDefinitionNames{
|
||||
Plural: "noxus2",
|
||||
Singular: "nonenglishnoxu2",
|
||||
Kind: "WishIHadChosenNoxu2",
|
||||
ShortNames: []string{"foo", "bar", "abc", "def"},
|
||||
ListKind: "Noxu2ItemList",
|
||||
},
|
||||
Scope: scope,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewCurletCustomResourceDefinition(scope apiextensionsv1alpha1.ResourceScope) *apiextensionsv1alpha1.CustomResourceDefinition {
|
||||
return &apiextensionsv1alpha1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "curlets.mygroup.example.com"},
|
||||
|
@ -100,20 +118,20 @@ func NewCurletInstance(namespace, name string) *unstructured.Unstructured {
|
|||
}
|
||||
}
|
||||
|
||||
func CreateNewCustomResourceDefinition(customResourceDefinition *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (*dynamic.Client, error) {
|
||||
_, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(customResourceDefinition)
|
||||
func CreateNewCustomResourceDefinition(crd *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (*dynamic.Client, error) {
|
||||
_, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(crd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait until the resource appears in discovery
|
||||
err = wait.PollImmediate(30*time.Millisecond, 30*time.Second, func() (bool, error) {
|
||||
resourceList, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(customResourceDefinition.Spec.Group + "/" + customResourceDefinition.Spec.Version)
|
||||
err = wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) {
|
||||
resourceList, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + crd.Spec.Version)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
for _, resource := range resourceList.APIResources {
|
||||
if resource.Name == customResourceDefinition.Spec.Names.Plural {
|
||||
if resource.Name == crd.Spec.Names.Plural {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -123,29 +141,36 @@ func CreateNewCustomResourceDefinition(customResourceDefinition *apiextensionsv1
|
|||
return nil, err
|
||||
}
|
||||
|
||||
dynamicClient, err := clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: customResourceDefinition.Spec.Group, Version: customResourceDefinition.Spec.Version, Resource: customResourceDefinition.Spec.Names.Plural})
|
||||
dynamicClient, err := clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dynamicClient, nil
|
||||
}
|
||||
|
||||
func DeleteCustomResourceDefinition(customResource *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) error {
|
||||
if err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Delete(customResource.Name, nil); err != nil {
|
||||
func DeleteCustomResourceDefinition(crd *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) error {
|
||||
if err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Delete(crd.Name, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
err := wait.PollImmediate(30*time.Millisecond, 30*time.Second, func() (bool, error) {
|
||||
if _, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(customResource.Spec.Group + "/" + customResource.Spec.Version); err != nil {
|
||||
err := wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) {
|
||||
groupResource, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + crd.Spec.Version)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
for _, g := range groupResource.APIResources {
|
||||
if g.Name == crd.Spec.Names.Plural {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func GetCustomResourceDefinition(customResource *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) (*apiextensionsv1alpha1.CustomResourceDefinition, error) {
|
||||
return apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Get(customResource.Name, metav1.GetOptions{})
|
||||
func GetCustomResourceDefinition(crd *apiextensionsv1alpha1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) (*apiextensionsv1alpha1.CustomResourceDefinition, error) {
|
||||
return apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Get(crd.Name, metav1.GetOptions{})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue