Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
142
vendor/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go
generated
vendored
Normal file
142
vendor/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package establish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
|
||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||
)
|
||||
|
||||
// EstablishingController controls how and when CRD is established.
|
||||
type EstablishingController struct {
|
||||
crdClient client.CustomResourceDefinitionsGetter
|
||||
crdLister listers.CustomResourceDefinitionLister
|
||||
crdSynced cache.InformerSynced
|
||||
|
||||
// To allow injection for testing.
|
||||
syncFn func(key string) error
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
// NewEstablishingController creates new EstablishingController.
|
||||
func NewEstablishingController(crdInformer informers.CustomResourceDefinitionInformer,
|
||||
crdClient client.CustomResourceDefinitionsGetter) *EstablishingController {
|
||||
ec := &EstablishingController{
|
||||
crdClient: crdClient,
|
||||
crdLister: crdInformer.Lister(),
|
||||
crdSynced: crdInformer.Informer().HasSynced,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "crdEstablishing"),
|
||||
}
|
||||
|
||||
ec.syncFn = ec.sync
|
||||
|
||||
return ec
|
||||
}
|
||||
|
||||
// QueueCRD adds CRD into the establishing queue.
|
||||
func (ec *EstablishingController) QueueCRD(key string, timeout time.Duration) {
|
||||
ec.queue.AddAfter(key, timeout)
|
||||
}
|
||||
|
||||
// Run starts the EstablishingController.
|
||||
func (ec *EstablishingController) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer ec.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting EstablishingController")
|
||||
defer glog.Infof("Shutting down EstablishingController")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, ec.crdSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
// only start one worker thread since its a slow moving API
|
||||
go wait.Until(ec.runWorker, time.Second, stopCh)
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (ec *EstablishingController) runWorker() {
|
||||
for ec.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
// processNextWorkItem deals with one key off the queue.
|
||||
// It returns false when it's time to quit.
|
||||
func (ec *EstablishingController) processNextWorkItem() bool {
|
||||
key, quit := ec.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer ec.queue.Done(key)
|
||||
|
||||
err := ec.syncFn(key.(string))
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err))
|
||||
ec.queue.AddRateLimited(key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// sync is used to turn CRDs into the Established state.
|
||||
func (ec *EstablishingController) sync(key string) error {
|
||||
cachedCRD, err := ec.crdLister.Get(key)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.NamesAccepted) ||
|
||||
apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.Established) {
|
||||
return nil
|
||||
}
|
||||
|
||||
crd := cachedCRD.DeepCopy()
|
||||
establishedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Established,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InitialNamesAccepted",
|
||||
Message: "the initial names have been accepted",
|
||||
}
|
||||
apiextensions.SetCRDCondition(crd, establishedCondition)
|
||||
|
||||
// Update server with new CRD condition.
|
||||
_, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
327
vendor/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go
generated
vendored
Normal file
327
vendor/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package finalizer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
|
||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||
)
|
||||
|
||||
// CRDFinalizer is a controller that finalizes the CRD by deleting all the CRs associated with it.
|
||||
type CRDFinalizer struct {
|
||||
crdClient client.CustomResourceDefinitionsGetter
|
||||
crClientGetter CRClientGetter
|
||||
|
||||
crdLister listers.CustomResourceDefinitionLister
|
||||
crdSynced cache.InformerSynced
|
||||
|
||||
// To allow injection for testing.
|
||||
syncFn func(key string) error
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
// ListerCollectionDeleter combines rest.Lister and rest.CollectionDeleter.
|
||||
type ListerCollectionDeleter interface {
|
||||
rest.Lister
|
||||
rest.CollectionDeleter
|
||||
}
|
||||
|
||||
// CRClientGetter knows how to get a ListerCollectionDeleter for a given CRD UID.
|
||||
type CRClientGetter interface {
|
||||
// GetCustomResourceListerCollectionDeleter gets the ListerCollectionDeleter for the given CRD
|
||||
// UID.
|
||||
GetCustomResourceListerCollectionDeleter(crd *apiextensions.CustomResourceDefinition) (ListerCollectionDeleter, error)
|
||||
}
|
||||
|
||||
// NewCRDFinalizer creates a new CRDFinalizer.
|
||||
func NewCRDFinalizer(
|
||||
crdInformer informers.CustomResourceDefinitionInformer,
|
||||
crdClient client.CustomResourceDefinitionsGetter,
|
||||
crClientGetter CRClientGetter,
|
||||
) *CRDFinalizer {
|
||||
c := &CRDFinalizer{
|
||||
crdClient: crdClient,
|
||||
crdLister: crdInformer.Lister(),
|
||||
crdSynced: crdInformer.Informer().HasSynced,
|
||||
crClientGetter: crClientGetter,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "CustomResourceDefinition-CRDFinalizer"),
|
||||
}
|
||||
|
||||
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: c.addCustomResourceDefinition,
|
||||
UpdateFunc: c.updateCustomResourceDefinition,
|
||||
})
|
||||
|
||||
c.syncFn = c.sync
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) sync(key string) error {
|
||||
cachedCRD, err := c.crdLister.Get(key)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// no work to do
|
||||
if cachedCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
crd := cachedCRD.DeepCopy()
|
||||
|
||||
// update the status condition. This cleanup could take a while.
|
||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionInProgress",
|
||||
Message: "CustomResource deletion is in progress",
|
||||
})
|
||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now we can start deleting items. We should use the REST API to ensure that all normal admission runs.
|
||||
// Since we control the endpoints, we know that delete collection works. No need to delete if not established.
|
||||
if apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||
cond, deleteErr := c.deleteInstances(crd)
|
||||
apiextensions.SetCRDCondition(crd, cond)
|
||||
if deleteErr != nil {
|
||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
return deleteErr
|
||||
}
|
||||
} else {
|
||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "NeverEstablished",
|
||||
Message: "resource was never established",
|
||||
})
|
||||
}
|
||||
|
||||
apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
|
||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// and now issue another delete, which should clean it all up if no finalizers remain or no-op if they do
|
||||
return c.crdClient.CustomResourceDefinitions().Delete(crd.Name, nil)
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) deleteInstances(crd *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionCondition, error) {
|
||||
// Now we can start deleting items. While it would be ideal to use a REST API client, doing so
|
||||
// could incorrectly delete a ThirdPartyResource with the same URL as the CustomResource, so we go
|
||||
// directly to the storage instead. Since we control the storage, we know that delete collection works.
|
||||
crClient, err := c.crClientGetter.GetCustomResourceListerCollectionDeleter(crd)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to find a custom resource client for %s.%s: %v", crd.Status.AcceptedNames.Plural, crd.Spec.Group, err)
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionFailed",
|
||||
Message: fmt.Sprintf("could not list instances: %v", err),
|
||||
}, err
|
||||
}
|
||||
|
||||
ctx := genericapirequest.NewContext()
|
||||
allResources, err := crClient.List(ctx, nil)
|
||||
if err != nil {
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionFailed",
|
||||
Message: fmt.Sprintf("could not list instances: %v", err),
|
||||
}, err
|
||||
}
|
||||
|
||||
deletedNamespaces := sets.String{}
|
||||
deleteErrors := []error{}
|
||||
for _, item := range allResources.(*unstructured.UnstructuredList).Items {
|
||||
metadata, err := meta.Accessor(&item)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
continue
|
||||
}
|
||||
if deletedNamespaces.Has(metadata.GetNamespace()) {
|
||||
continue
|
||||
}
|
||||
// don't retry deleting the same namespace
|
||||
deletedNamespaces.Insert(metadata.GetNamespace())
|
||||
nsCtx := genericapirequest.WithNamespace(ctx, metadata.GetNamespace())
|
||||
if _, err := crClient.DeleteCollection(nsCtx, nil, nil); err != nil {
|
||||
deleteErrors = append(deleteErrors, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil {
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionFailed",
|
||||
Message: fmt.Sprintf("could not issue all deletes: %v", deleteError),
|
||||
}, deleteError
|
||||
}
|
||||
|
||||
// now we need to wait until all the resources are deleted. Start with a simple poll before we do anything fancy.
|
||||
// TODO not all servers are synchronized on caches. It is possible for a stale one to still be creating things.
|
||||
// Once we have a mechanism for servers to indicate their states, we should check that for concurrence.
|
||||
err = wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
|
||||
listObj, err := crClient.List(ctx, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(listObj.(*unstructured.UnstructuredList).Items) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
glog.V(2).Infof("%s.%s waiting for %d items to be removed", crd.Status.AcceptedNames.Plural, crd.Spec.Group, len(listObj.(*unstructured.UnstructuredList).Items))
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionCheck",
|
||||
Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", err),
|
||||
}, err
|
||||
}
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "InstanceDeletionCompleted",
|
||||
Message: "removed all instances",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting CRDFinalizer")
|
||||
defer glog.Infof("Shutting down CRDFinalizer")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, c.crdSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
|
||||
func (c *CRDFinalizer) processNextWorkItem() bool {
|
||||
key, quit := c.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(key)
|
||||
|
||||
err := c.syncFn(key.(string))
|
||||
if err == nil {
|
||||
c.queue.Forget(key)
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err))
|
||||
c.queue.AddRateLimited(key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) enqueue(obj *apiextensions.CustomResourceDefinition) {
|
||||
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", obj, err))
|
||||
return
|
||||
}
|
||||
|
||||
c.queue.Add(key)
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) addCustomResourceDefinition(obj interface{}) {
|
||||
castObj := obj.(*apiextensions.CustomResourceDefinition)
|
||||
// only queue deleted things
|
||||
if !castObj.DeletionTimestamp.IsZero() && apiextensions.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) {
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}) {
|
||||
oldCRD := oldObj.(*apiextensions.CustomResourceDefinition)
|
||||
newCRD := newObj.(*apiextensions.CustomResourceDefinition)
|
||||
// only queue deleted things that haven't been finalized by us
|
||||
if newCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) {
|
||||
return
|
||||
}
|
||||
|
||||
// always requeue resyncs just in case
|
||||
if oldCRD.ResourceVersion == newCRD.ResourceVersion {
|
||||
c.enqueue(newCRD)
|
||||
return
|
||||
}
|
||||
|
||||
// If the only difference is in the terminating condition, then there's no reason to requeue here. This controller
|
||||
// is likely to be the originator, so requeuing would hot-loop us. Failures are requeued by the workqueue directly.
|
||||
// This is a low traffic and scale resource, so the copy is terrible. It's not good, so better ideas
|
||||
// are welcome.
|
||||
oldCopy := oldCRD.DeepCopy()
|
||||
newCopy := newCRD.DeepCopy()
|
||||
oldCopy.ResourceVersion = ""
|
||||
newCopy.ResourceVersion = ""
|
||||
apiextensions.RemoveCRDCondition(oldCopy, apiextensions.Terminating)
|
||||
apiextensions.RemoveCRDCondition(newCopy, apiextensions.Terminating)
|
||||
|
||||
if !reflect.DeepEqual(oldCopy, newCopy) {
|
||||
c.enqueue(newCRD)
|
||||
}
|
||||
}
|
374
vendor/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go
generated
vendored
Normal file
374
vendor/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
|
||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||
)
|
||||
|
||||
// This controller is reserving names. To avoid conflicts, be sure to run only one instance of the worker at a time.
|
||||
// This could eventually be lifted, but starting simple.
|
||||
type NamingConditionController struct {
|
||||
crdClient client.CustomResourceDefinitionsGetter
|
||||
|
||||
crdLister listers.CustomResourceDefinitionLister
|
||||
crdSynced cache.InformerSynced
|
||||
// crdMutationCache backs our lister and keeps track of committed updates to avoid racy
|
||||
// write/lookup cycles. It's got 100 slots by default, so it unlikely to overrun
|
||||
// TODO to revisit this if naming conflicts are found to occur in the wild
|
||||
crdMutationCache cache.MutationCache
|
||||
|
||||
// To allow injection for testing.
|
||||
syncFn func(key string) error
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
func NewNamingConditionController(
|
||||
crdInformer informers.CustomResourceDefinitionInformer,
|
||||
crdClient client.CustomResourceDefinitionsGetter,
|
||||
) *NamingConditionController {
|
||||
c := &NamingConditionController{
|
||||
crdClient: crdClient,
|
||||
crdLister: crdInformer.Lister(),
|
||||
crdSynced: crdInformer.Informer().HasSynced,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "CustomResourceDefinition-NamingConditionController"),
|
||||
}
|
||||
|
||||
informerIndexer := crdInformer.Informer().GetIndexer()
|
||||
c.crdMutationCache = cache.NewIntegerResourceVersionMutationCache(informerIndexer, informerIndexer, 60*time.Second, false)
|
||||
|
||||
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: c.addCustomResourceDefinition,
|
||||
UpdateFunc: c.updateCustomResourceDefinition,
|
||||
DeleteFunc: c.deleteCustomResourceDefinition,
|
||||
})
|
||||
|
||||
c.syncFn = c.sync
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) getAcceptedNamesForGroup(group string) (allResources sets.String, allKinds sets.String) {
|
||||
allResources = sets.String{}
|
||||
allKinds = sets.String{}
|
||||
|
||||
list, err := c.crdLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, curr := range list {
|
||||
if curr.Spec.Group != group {
|
||||
continue
|
||||
}
|
||||
|
||||
// for each item here, see if we have a mutation cache entry that is more recent
|
||||
// this makes sure that if we tight loop on update and run, our mutation cache will show
|
||||
// us the version of the objects we just updated to.
|
||||
item := curr
|
||||
obj, exists, err := c.crdMutationCache.GetByKey(curr.Name)
|
||||
if exists && err == nil {
|
||||
item = obj.(*apiextensions.CustomResourceDefinition)
|
||||
}
|
||||
|
||||
allResources.Insert(item.Status.AcceptedNames.Plural)
|
||||
allResources.Insert(item.Status.AcceptedNames.Singular)
|
||||
allResources.Insert(item.Status.AcceptedNames.ShortNames...)
|
||||
|
||||
allKinds.Insert(item.Status.AcceptedNames.Kind)
|
||||
allKinds.Insert(item.Status.AcceptedNames.ListKind)
|
||||
}
|
||||
|
||||
return allResources, allKinds
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) calculateNamesAndConditions(in *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionNames, apiextensions.CustomResourceDefinitionCondition, apiextensions.CustomResourceDefinitionCondition) {
|
||||
// Get the names that have already been claimed
|
||||
allResources, allKinds := c.getAcceptedNamesForGroup(in.Spec.Group)
|
||||
|
||||
namesAcceptedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.NamesAccepted,
|
||||
Status: apiextensions.ConditionUnknown,
|
||||
}
|
||||
|
||||
requestedNames := in.Spec.Names
|
||||
acceptedNames := in.Status.AcceptedNames
|
||||
newNames := in.Status.AcceptedNames
|
||||
|
||||
// Check each name for mismatches. If there's a mismatch between spec and status, then try to deconflict.
|
||||
// Continue on errors so that the status is the best match possible
|
||||
if err := equalToAcceptedOrFresh(requestedNames.Plural, acceptedNames.Plural, allResources); err != nil {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||
namesAcceptedCondition.Reason = "PluralConflict"
|
||||
namesAcceptedCondition.Message = err.Error()
|
||||
} else {
|
||||
newNames.Plural = requestedNames.Plural
|
||||
}
|
||||
if err := equalToAcceptedOrFresh(requestedNames.Singular, acceptedNames.Singular, allResources); err != nil {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||
namesAcceptedCondition.Reason = "SingularConflict"
|
||||
namesAcceptedCondition.Message = err.Error()
|
||||
} else {
|
||||
newNames.Singular = requestedNames.Singular
|
||||
}
|
||||
if !reflect.DeepEqual(requestedNames.ShortNames, acceptedNames.ShortNames) {
|
||||
errs := []error{}
|
||||
existingShortNames := sets.NewString(acceptedNames.ShortNames...)
|
||||
for _, shortName := range requestedNames.ShortNames {
|
||||
// if the shortname is already ours, then we're fine
|
||||
if existingShortNames.Has(shortName) {
|
||||
continue
|
||||
}
|
||||
if err := equalToAcceptedOrFresh(shortName, "", allResources); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
}
|
||||
if err := utilerrors.NewAggregate(errs); err != nil {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||
namesAcceptedCondition.Reason = "ShortNamesConflict"
|
||||
namesAcceptedCondition.Message = err.Error()
|
||||
} else {
|
||||
newNames.ShortNames = requestedNames.ShortNames
|
||||
}
|
||||
}
|
||||
|
||||
if err := equalToAcceptedOrFresh(requestedNames.Kind, acceptedNames.Kind, allKinds); err != nil {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||
namesAcceptedCondition.Reason = "KindConflict"
|
||||
namesAcceptedCondition.Message = err.Error()
|
||||
} else {
|
||||
newNames.Kind = requestedNames.Kind
|
||||
}
|
||||
if err := equalToAcceptedOrFresh(requestedNames.ListKind, acceptedNames.ListKind, allKinds); err != nil {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||
namesAcceptedCondition.Reason = "ListKindConflict"
|
||||
namesAcceptedCondition.Message = err.Error()
|
||||
} else {
|
||||
newNames.ListKind = requestedNames.ListKind
|
||||
}
|
||||
|
||||
newNames.Categories = requestedNames.Categories
|
||||
|
||||
// if we haven't changed the condition, then our names must be good.
|
||||
if namesAcceptedCondition.Status == apiextensions.ConditionUnknown {
|
||||
namesAcceptedCondition.Status = apiextensions.ConditionTrue
|
||||
namesAcceptedCondition.Reason = "NoConflicts"
|
||||
namesAcceptedCondition.Message = "no conflicts found"
|
||||
}
|
||||
|
||||
// set EstablishedCondition initially to false, then set it to true in establishing controller.
|
||||
// The Establishing Controller will see the NamesAccepted condition when it arrives through the shared informer.
|
||||
// At that time the API endpoint handler will serve the endpoint, avoiding a race
|
||||
// which we had if we set Established to true here.
|
||||
establishedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Established,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "NotAccepted",
|
||||
Message: "not all names are accepted",
|
||||
}
|
||||
if old := apiextensions.FindCRDCondition(in, apiextensions.Established); old != nil {
|
||||
establishedCondition = *old
|
||||
}
|
||||
if establishedCondition.Status != apiextensions.ConditionTrue && namesAcceptedCondition.Status == apiextensions.ConditionTrue {
|
||||
establishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Established,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "Installing",
|
||||
Message: "the initial names have been accepted",
|
||||
}
|
||||
}
|
||||
|
||||
return newNames, namesAcceptedCondition, establishedCondition
|
||||
}
|
||||
|
||||
func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.String) error {
|
||||
if requestedName == acceptedName {
|
||||
return nil
|
||||
}
|
||||
if !usedNames.Has(requestedName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%q is already in use", requestedName)
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip checking names if Spec and Status names are same.
|
||||
if equality.Semantic.DeepEqual(inCustomResourceDefinition.Spec.Names, inCustomResourceDefinition.Status.AcceptedNames) {
|
||||
return nil
|
||||
}
|
||||
|
||||
acceptedNames, namingCondition, establishedCondition := c.calculateNamesAndConditions(inCustomResourceDefinition)
|
||||
|
||||
// nothing to do if accepted names and NamesAccepted condition didn't change
|
||||
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) &&
|
||||
apiextensions.IsCRDConditionEquivalent(&namingCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NamesAccepted)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
crd := inCustomResourceDefinition.DeepCopy()
|
||||
crd.Status.AcceptedNames = acceptedNames
|
||||
apiextensions.SetCRDCondition(crd, namingCondition)
|
||||
apiextensions.SetCRDCondition(crd, establishedCondition)
|
||||
|
||||
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the update was successful, go ahead and add the entry to the mutation cache
|
||||
c.crdMutationCache.Mutation(updatedObj)
|
||||
|
||||
// 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
|
||||
if err := c.requeueAllOtherGroupCRDs(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting NamingConditionController")
|
||||
defer glog.Infof("Shutting down NamingConditionController")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, c.crdSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
// only start one worker thread since its a slow moving API and the naming conflict resolution bits aren't thread-safe
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
|
||||
func (c *NamingConditionController) processNextWorkItem() bool {
|
||||
key, quit := c.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(key)
|
||||
|
||||
err := c.syncFn(key.(string))
|
||||
if err == nil {
|
||||
c.queue.Forget(key)
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err))
|
||||
c.queue.AddRateLimited(key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) enqueue(obj *apiextensions.CustomResourceDefinition) {
|
||||
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", obj, err))
|
||||
return
|
||||
}
|
||||
|
||||
c.queue.Add(key)
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) addCustomResourceDefinition(obj interface{}) {
|
||||
castObj := obj.(*apiextensions.CustomResourceDefinition)
|
||||
glog.V(4).Infof("Adding %s", castObj.Name)
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) updateCustomResourceDefinition(obj, _ interface{}) {
|
||||
castObj := obj.(*apiextensions.CustomResourceDefinition)
|
||||
glog.V(4).Infof("Updating %s", castObj.Name)
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
|
||||
func (c *NamingConditionController) deleteCustomResourceDefinition(obj interface{}) {
|
||||
castObj, ok := obj.(*apiextensions.CustomResourceDefinition)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
glog.Errorf("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
castObj, ok = tombstone.Obj.(*apiextensions.CustomResourceDefinition)
|
||||
if !ok {
|
||||
glog.Errorf("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
344
vendor/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go
generated
vendored
Normal file
344
vendor/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type crdBuilder struct {
|
||||
curr apiextensions.CustomResourceDefinition
|
||||
}
|
||||
|
||||
func newCRD(name string) *crdBuilder {
|
||||
tokens := strings.SplitN(name, ".", 2)
|
||||
return &crdBuilder{
|
||||
curr: apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Group: tokens[1],
|
||||
Names: apiextensions.CustomResourceDefinitionNames{
|
||||
Plural: tokens[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *crdBuilder) SpecNames(plural, singular, kind, listKind string, shortNames ...string) *crdBuilder {
|
||||
b.curr.Spec.Names.Plural = plural
|
||||
b.curr.Spec.Names.Singular = singular
|
||||
b.curr.Spec.Names.Kind = kind
|
||||
b.curr.Spec.Names.ListKind = listKind
|
||||
b.curr.Spec.Names.ShortNames = shortNames
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *crdBuilder) StatusNames(plural, singular, kind, listKind string, shortNames ...string) *crdBuilder {
|
||||
b.curr.Status.AcceptedNames.Plural = plural
|
||||
b.curr.Status.AcceptedNames.Singular = singular
|
||||
b.curr.Status.AcceptedNames.Kind = kind
|
||||
b.curr.Status.AcceptedNames.ListKind = listKind
|
||||
b.curr.Status.AcceptedNames.ShortNames = shortNames
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *crdBuilder) Condition(c apiextensions.CustomResourceDefinitionCondition) *crdBuilder {
|
||||
b.curr.Status.Conditions = append(b.curr.Status.Conditions, c)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func names(plural, singular, kind, listKind string, shortNames ...string) apiextensions.CustomResourceDefinitionNames {
|
||||
ret := apiextensions.CustomResourceDefinitionNames{
|
||||
Plural: plural,
|
||||
Singular: singular,
|
||||
Kind: kind,
|
||||
ListKind: listKind,
|
||||
ShortNames: shortNames,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (b *crdBuilder) NewOrDie() *apiextensions.CustomResourceDefinition {
|
||||
return &b.curr
|
||||
}
|
||||
|
||||
var acceptedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.NamesAccepted,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
}
|
||||
|
||||
var notAcceptedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.NamesAccepted,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "NotAccepted",
|
||||
Message: "not all names are accepted",
|
||||
}
|
||||
|
||||
var installingCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Established,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "Installing",
|
||||
Message: "the initial names have been accepted",
|
||||
}
|
||||
|
||||
var notEstablishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Established,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: "NotAccepted",
|
||||
Message: "not all names are accepted",
|
||||
}
|
||||
|
||||
func nameConflictCondition(reason, message string) apiextensions.CustomResourceDefinitionCondition {
|
||||
return apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.NamesAccepted,
|
||||
Status: apiextensions.ConditionFalse,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
in *apiextensions.CustomResourceDefinition
|
||||
existing []*apiextensions.CustomResourceDefinition
|
||||
expectedNames apiextensions.CustomResourceDefinitionNames
|
||||
expectedNameConflictCondition apiextensions.CustomResourceDefinitionCondition
|
||||
expectedEstablishedCondition apiextensions.CustomResourceDefinitionCondition
|
||||
}{
|
||||
{
|
||||
name: "first resource",
|
||||
in: newCRD("alfa.bravo.com").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{},
|
||||
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||
Plural: "alfa",
|
||||
},
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "different groups",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("alfa.charlie.com").StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "conflict plural to singular",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "conflict singular to shortName",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "delta-singular").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("SingularConflict", `"delta-singular" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "conflict on shortName to shortName",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "hotel-shortname-2").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind"),
|
||||
expectedNameConflictCondition: nameConflictCondition("ShortNamesConflict", `"hotel-shortname-2" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "conflict on kind to listkind",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "echo-kind").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("KindConflict", `"echo-kind" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "conflict on listkind to kind",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "no conflict on resource and kind",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "echo-kind", "", "").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "merge on conflicts",
|
||||
in: newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
StatusNames("zulu", "yankee-singular", "xray-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2").
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "merge on conflicts shortNames as one",
|
||||
in: newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
StatusNames("zulu", "yankee-singular", "xray-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2").
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular", "golf-shortname-1").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "no conflicts on self",
|
||||
in: newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "no conflicts on self, remove shortname",
|
||||
in: newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1").
|
||||
StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("alfa.bravo.com").
|
||||
SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
NewOrDie(),
|
||||
},
|
||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1"),
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "installing before with true condition",
|
||||
in: newCRD("alfa.bravo.com").Condition(acceptedCondition).NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{},
|
||||
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||
Plural: "alfa",
|
||||
},
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "not installing before with false condition",
|
||||
in: newCRD("alfa.bravo.com").Condition(notAcceptedCondition).NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{},
|
||||
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||
Plural: "alfa",
|
||||
},
|
||||
expectedNameConflictCondition: acceptedCondition,
|
||||
expectedEstablishedCondition: installingCondition,
|
||||
},
|
||||
{
|
||||
name: "conflicting, installing before with true condition",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
Condition(acceptedCondition).
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
{
|
||||
name: "conflicting, not installing before with false condition",
|
||||
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||
Condition(notAcceptedCondition).
|
||||
NewOrDie(),
|
||||
existing: []*apiextensions.CustomResourceDefinition{
|
||||
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||
},
|
||||
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||
expectedEstablishedCondition: notEstablishedCondition,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
crdIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
for _, obj := range tc.existing {
|
||||
crdIndexer.Add(obj)
|
||||
}
|
||||
|
||||
c := NamingConditionController{
|
||||
crdLister: listers.NewCustomResourceDefinitionLister(crdIndexer),
|
||||
crdMutationCache: cache.NewIntegerResourceVersionMutationCache(crdIndexer, crdIndexer, 60*time.Second, false),
|
||||
}
|
||||
actualNames, actualNameConflictCondition, establishedCondition := c.calculateNamesAndConditions(tc.in)
|
||||
|
||||
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
|
||||
}
|
||||
if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
||||
}
|
||||
if e, a := tc.expectedEstablishedCondition, establishedCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user