mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
apiextensions: add Established condition
This commit is contained in:
parent
1153ef19ce
commit
653258f1d5
@ -70,6 +70,10 @@ const (
|
|||||||
type CustomResourceDefinitionConditionType string
|
type CustomResourceDefinitionConditionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Established means that the resource has become active. A resource is established when all names are
|
||||||
|
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||||
|
// a later NameConflict due to changed names. Note that not all names can be changed.
|
||||||
|
Established CustomResourceDefinitionConditionType = "Established"
|
||||||
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
||||||
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
||||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||||
|
@ -70,6 +70,10 @@ const (
|
|||||||
type CustomResourceDefinitionConditionType string
|
type CustomResourceDefinitionConditionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Established means that the resource has become active. A resource is established when all names are
|
||||||
|
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||||
|
// a later NameConflict due to changed names. Note that not all names can be changed.
|
||||||
|
Established CustomResourceDefinitionConditionType = "Established"
|
||||||
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
||||||
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
||||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||||
|
@ -85,8 +85,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
|
|||||||
foundVersion := false
|
foundVersion := false
|
||||||
foundGroup := false
|
foundGroup := false
|
||||||
for _, crd := range crds {
|
for _, crd := range crds {
|
||||||
// if we can't definitively determine that our names are good, don't serve it
|
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +143,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
r.delegate.ServeHTTP(w, req)
|
r.delegate.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if we can't definitively determine that our names are good, delegate
|
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
|
||||||
r.delegate.ServeHTTP(w, req)
|
r.delegate.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
if len(requestInfo.Subresource) > 0 {
|
if len(requestInfo.Subresource) > 0 {
|
||||||
|
@ -127,38 +127,55 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible for a naming conflict to have removed this resource from the API after instances were created.
|
// Now we can start deleting items. We should use the REST API to ensure that all normal admission runs.
|
||||||
// For now we will cowardly stop finalizing. If we don't go through the REST API, weird things may happen:
|
// Since we control the endpoints, we know that delete collection works. No need to delete if not established.
|
||||||
// no audit trail, no admission checks or side effects, finalization would probably still work but defaulting
|
if apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
// would be missed. It would be a mess.
|
cond, deleteErr := c.deleteInstances(crd)
|
||||||
// This requires human intervention to solve, update status so they have a reason.
|
apiextensions.SetCRDCondition(crd, *cond)
|
||||||
// TODO split coreNamesAccepted from extendedNamesAccepted. If coreNames were accepted, then we have something to cleanup
|
if deleteErr != nil {
|
||||||
// and the endpoint is serviceable. if they aren't, then there's nothing to cleanup.
|
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
if err != nil {
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
}
|
||||||
|
return deleteErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: "InstanceDeletionStuck",
|
Reason: "NeverEstablished",
|
||||||
Message: fmt.Sprintf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict),
|
Message: "resource was never established",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict)
|
|
||||||
|
// 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
|
// 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
|
// 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.
|
// directly to the storage instead. Since we control the storage, we know that delete collection works.
|
||||||
crClient := c.crClientGetter.GetCustomResourceListerCollectionDeleter(crd.UID)
|
crClient := c.crClientGetter.GetCustomResourceListerCollectionDeleter(crd.UID)
|
||||||
if crClient == nil {
|
if crClient == nil {
|
||||||
return fmt.Errorf("unable to find a custom resource client for %s.%s", crd.Status.AcceptedNames.Plural, crd.Spec.Group)
|
return nil, fmt.Errorf("unable to find a custom resource client for %s.%s", crd.Status.AcceptedNames.Plural, crd.Spec.Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := genericapirequest.NewContext()
|
ctx := genericapirequest.NewContext()
|
||||||
allResources, err := crClient.List(ctx, nil)
|
allResources, err := crClient.List(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Terminating,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InstanceDeletionFailed",
|
||||||
|
Message: fmt.Sprintf("could not list instances: %v", err),
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedNamespaces := sets.String{}
|
deletedNamespaces := sets.String{}
|
||||||
@ -181,23 +198,18 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil {
|
if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionTrue,
|
||||||
Reason: "InstanceDeletionFailed",
|
Reason: "InstanceDeletionFailed",
|
||||||
Message: fmt.Sprintf("could not issue all deletes: %v", deleteError),
|
Message: fmt.Sprintf("could not issue all deletes: %v", deleteError),
|
||||||
})
|
}, deleteError
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
}
|
|
||||||
return deleteError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we need to wait until all the resources are deleted. Start with a simple poll before we do anything fancy.
|
// 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.
|
// 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.
|
// Once we have a mechanism for servers to indicate their states, we should check that for concurrence.
|
||||||
listErr := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
|
err = wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
|
||||||
listObj, err := crClient.List(ctx, nil)
|
listObj, err := crClient.List(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -208,34 +220,20 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
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))
|
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
|
return false, nil
|
||||||
})
|
})
|
||||||
if listErr != nil {
|
if err != nil {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionTrue,
|
||||||
Reason: "InstanceDeletionCheck",
|
Reason: "InstanceDeletionCheck",
|
||||||
Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", listErr),
|
Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", err),
|
||||||
})
|
}, err
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
}
|
}
|
||||||
return listErr
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
}
|
|
||||||
|
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionFalse,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: "InstanceDeletionCompleted",
|
Reason: "InstanceDeletionCompleted",
|
||||||
Message: "removed all instances",
|
Message: "removed all instances",
|
||||||
})
|
}, nil
|
||||||
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) Run(workers int, stopCh <-chan struct{}) {
|
func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) {
|
||||||
|
@ -28,6 +28,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/conversion"
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
@ -118,11 +119,11 @@ func (c *NamingConditionController) getAcceptedNamesForGroup(group string) (allR
|
|||||||
return allResources, allKinds
|
return allResources, allKinds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionNames, apiextensions.CustomResourceDefinitionCondition) {
|
func (c *NamingConditionController) calculateNamesAndConditions(in *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionNames, apiextensions.CustomResourceDefinitionCondition, apiextensions.CustomResourceDefinitionCondition) {
|
||||||
// Get the names that have already been claimed
|
// Get the names that have already been claimed
|
||||||
allResources, allKinds := c.getAcceptedNamesForGroup(in.Spec.Group)
|
allResources, allKinds := c.getAcceptedNamesForGroup(in.Spec.Group)
|
||||||
|
|
||||||
condition := apiextensions.CustomResourceDefinitionCondition{
|
nameConflictCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NameConflict,
|
||||||
Status: apiextensions.ConditionUnknown,
|
Status: apiextensions.ConditionUnknown,
|
||||||
}
|
}
|
||||||
@ -134,16 +135,16 @@ func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResou
|
|||||||
// Check each name for mismatches. If there's a mismatch between spec and status, then try to deconflict.
|
// 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
|
// Continue on errors so that the status is the best match possible
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Plural, acceptedNames.Plural, allResources); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Plural, acceptedNames.Plural, allResources); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
nameConflictCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "Plural"
|
nameConflictCondition.Reason = "Plural"
|
||||||
condition.Message = err.Error()
|
nameConflictCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Plural = requestedNames.Plural
|
newNames.Plural = requestedNames.Plural
|
||||||
}
|
}
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Singular, acceptedNames.Singular, allResources); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Singular, acceptedNames.Singular, allResources); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
nameConflictCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "Singular"
|
nameConflictCondition.Reason = "Singular"
|
||||||
condition.Message = err.Error()
|
nameConflictCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Singular = requestedNames.Singular
|
newNames.Singular = requestedNames.Singular
|
||||||
}
|
}
|
||||||
@ -161,37 +162,58 @@ func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResou
|
|||||||
|
|
||||||
}
|
}
|
||||||
if err := utilerrors.NewAggregate(errs); err != nil {
|
if err := utilerrors.NewAggregate(errs); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
nameConflictCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "ShortNames"
|
nameConflictCondition.Reason = "ShortNames"
|
||||||
condition.Message = err.Error()
|
nameConflictCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.ShortNames = requestedNames.ShortNames
|
newNames.ShortNames = requestedNames.ShortNames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Kind, acceptedNames.Kind, allKinds); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Kind, acceptedNames.Kind, allKinds); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
nameConflictCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "Kind"
|
nameConflictCondition.Reason = "Kind"
|
||||||
condition.Message = err.Error()
|
nameConflictCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Kind = requestedNames.Kind
|
newNames.Kind = requestedNames.Kind
|
||||||
}
|
}
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.ListKind, acceptedNames.ListKind, allKinds); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.ListKind, acceptedNames.ListKind, allKinds); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
nameConflictCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "ListKind"
|
nameConflictCondition.Reason = "ListKind"
|
||||||
condition.Message = err.Error()
|
nameConflictCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.ListKind = requestedNames.ListKind
|
newNames.ListKind = requestedNames.ListKind
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we haven't changed the condition, then our names must be good.
|
// if we haven't changed the condition, then our names must be good.
|
||||||
if condition.Status == apiextensions.ConditionUnknown {
|
if nameConflictCondition.Status == apiextensions.ConditionUnknown {
|
||||||
condition.Status = apiextensions.ConditionFalse
|
nameConflictCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "NoConflicts"
|
nameConflictCondition.Reason = "NoConflicts"
|
||||||
condition.Message = "no conflicts found"
|
nameConflictCondition.Message = "no conflicts found"
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNames, condition
|
// set EstablishedCondition to true if all names are accepted. Never set it back to false.
|
||||||
|
establishedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionFalse,
|
||||||
|
Reason: "NotAccepted",
|
||||||
|
Message: "not all names are accepted",
|
||||||
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||||
|
}
|
||||||
|
if old := apiextensions.FindCRDCondition(in, apiextensions.Established); old != nil {
|
||||||
|
establishedCondition = *old
|
||||||
|
}
|
||||||
|
if establishedCondition.Status != apiextensions.ConditionTrue && nameConflictCondition.Status == apiextensions.ConditionFalse {
|
||||||
|
establishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InitialNamesAccepted",
|
||||||
|
Message: "the initial names have been accepted",
|
||||||
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNames, nameConflictCondition, establishedCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.String) error {
|
func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.String) error {
|
||||||
@ -214,12 +236,12 @@ func (c *NamingConditionController) sync(key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptedNames, namingCondition := c.calculateNames(inCustomResourceDefinition)
|
acceptedNames, namingCondition, establishedCondition := c.calculateNamesAndConditions(inCustomResourceDefinition)
|
||||||
|
|
||||||
// nothing to do if accepted names and NameConflict condition didn't change
|
// nothing to do if accepted names and NameConflict condition didn't change
|
||||||
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) &&
|
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) &&
|
||||||
apiextensions.IsCRDConditionEquivalent(
|
apiextensions.IsCRDConditionEquivalent(&namingCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NameConflict)) &&
|
||||||
&namingCondition,
|
apiextensions.IsCRDConditionEquivalent(&establishedCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.Established)) {
|
||||||
apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NameConflict)) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +252,7 @@ func (c *NamingConditionController) sync(key string) error {
|
|||||||
|
|
||||||
crd.Status.AcceptedNames = acceptedNames
|
crd.Status.AcceptedNames = acceptedNames
|
||||||
apiextensions.SetCRDCondition(crd, namingCondition)
|
apiextensions.SetCRDCondition(crd, namingCondition)
|
||||||
|
apiextensions.SetCRDCondition(crd, establishedCondition)
|
||||||
|
|
||||||
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,6 +67,12 @@ func (b *crdBuilder) StatusNames(plural, singular, kind, listKind string, shortN
|
|||||||
return b
|
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 {
|
func names(plural, singular, kind, listKind string, shortNames ...string) apiextensions.CustomResourceDefinitionNames {
|
||||||
ret := apiextensions.CustomResourceDefinitionNames{
|
ret := apiextensions.CustomResourceDefinitionNames{
|
||||||
Plural: plural,
|
Plural: plural,
|
||||||
@ -82,14 +88,14 @@ func (b *crdBuilder) NewOrDie() *apiextensions.CustomResourceDefinition {
|
|||||||
return &b.curr
|
return &b.curr
|
||||||
}
|
}
|
||||||
|
|
||||||
var goodCondition = apiextensions.CustomResourceDefinitionCondition{
|
var acceptedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NameConflict,
|
||||||
Status: apiextensions.ConditionFalse,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: "NoConflicts",
|
Reason: "NoConflicts",
|
||||||
Message: "no conflicts found",
|
Message: "no conflicts found",
|
||||||
}
|
}
|
||||||
|
|
||||||
func badCondition(reason, message string) apiextensions.CustomResourceDefinitionCondition {
|
func nameConflictCondition(reason, message string) apiextensions.CustomResourceDefinitionCondition {
|
||||||
return apiextensions.CustomResourceDefinitionCondition{
|
return apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NameConflict,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionTrue,
|
||||||
@ -98,6 +104,20 @@ func badCondition(reason, message string) apiextensions.CustomResourceDefinition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var establishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InitialNamesAccepted",
|
||||||
|
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 TestSync(t *testing.T) {
|
func TestSync(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -105,7 +125,8 @@ func TestSync(t *testing.T) {
|
|||||||
in *apiextensions.CustomResourceDefinition
|
in *apiextensions.CustomResourceDefinition
|
||||||
existing []*apiextensions.CustomResourceDefinition
|
existing []*apiextensions.CustomResourceDefinition
|
||||||
expectedNames apiextensions.CustomResourceDefinitionNames
|
expectedNames apiextensions.CustomResourceDefinitionNames
|
||||||
expectedCondition apiextensions.CustomResourceDefinitionCondition
|
expectedNameConflictCondition apiextensions.CustomResourceDefinitionCondition
|
||||||
|
expectedEstablishedCondition apiextensions.CustomResourceDefinitionCondition
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "first resource",
|
name: "first resource",
|
||||||
@ -114,7 +135,8 @@ func TestSync(t *testing.T) {
|
|||||||
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
Plural: "alfa",
|
Plural: "alfa",
|
||||||
},
|
},
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "different groups",
|
name: "different groups",
|
||||||
@ -123,7 +145,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("alfa.charlie.com").StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
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"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict plural to singular",
|
name: "conflict plural to singular",
|
||||||
@ -132,7 +155,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Plural", `"alfa" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("Plural", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict singular to shortName",
|
name: "conflict singular to shortName",
|
||||||
@ -141,7 +165,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "delta-singular").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "delta-singular").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Singular", `"delta-singular" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("Singular", `"delta-singular" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on shortName to shortName",
|
name: "conflict on shortName to shortName",
|
||||||
@ -150,7 +175,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "hotel-shortname-2").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "hotel-shortname-2").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind"),
|
||||||
expectedCondition: badCondition("ShortNames", `"hotel-shortname-2" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ShortNames", `"hotel-shortname-2" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on kind to listkind",
|
name: "conflict on kind to listkind",
|
||||||
@ -159,7 +185,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "echo-kind").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "echo-kind").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Kind", `"echo-kind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("Kind", `"echo-kind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on listkind to kind",
|
name: "conflict on listkind to kind",
|
||||||
@ -168,7 +195,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflict on resource and kind",
|
name: "no conflict on resource and kind",
|
||||||
@ -177,7 +205,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "echo-kind", "", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "echo-kind", "", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "merge on conflicts",
|
name: "merge on conflicts",
|
||||||
@ -189,7 +218,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular").NewOrDie(),
|
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"),
|
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "merge on conflicts shortNames as one",
|
name: "merge on conflicts shortNames as one",
|
||||||
@ -201,7 +231,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular", "golf-shortname-1").NewOrDie(),
|
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"),
|
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflicts on self",
|
name: "no conflicts on self",
|
||||||
@ -216,7 +247,8 @@ func TestSync(t *testing.T) {
|
|||||||
NewOrDie(),
|
NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflicts on self, remove shortname",
|
name: "no conflicts on self, remove shortname",
|
||||||
@ -231,7 +263,52 @@ func TestSync(t *testing.T) {
|
|||||||
NewOrDie(),
|
NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "established before with true condition",
|
||||||
|
in: newCRD("alfa.bravo.com").Condition(establishedCondition).NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{},
|
||||||
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "alfa",
|
||||||
|
},
|
||||||
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not established before with false condition",
|
||||||
|
in: newCRD("alfa.bravo.com").Condition(notEstablishedCondition).NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{},
|
||||||
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "alfa",
|
||||||
|
},
|
||||||
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conflicting, established before with true condition",
|
||||||
|
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||||
|
Condition(establishedCondition).
|
||||||
|
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("Plural", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conflicting, not established before with false condition",
|
||||||
|
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||||
|
Condition(notEstablishedCondition).
|
||||||
|
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("Plural", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,12 +322,15 @@ func TestSync(t *testing.T) {
|
|||||||
crdLister: listers.NewCustomResourceDefinitionLister(crdIndexer),
|
crdLister: listers.NewCustomResourceDefinitionLister(crdIndexer),
|
||||||
crdMutationCache: cache.NewIntegerResourceVersionMutationCache(crdIndexer, crdIndexer, 60*time.Second, false),
|
crdMutationCache: cache.NewIntegerResourceVersionMutationCache(crdIndexer, crdIndexer, 60*time.Second, false),
|
||||||
}
|
}
|
||||||
actualNames, actualCondition := c.calculateNames(tc.in)
|
actualNames, actualNameConflictCondition, actualEstablishedCondition := c.calculateNamesAndConditions(tc.in)
|
||||||
|
|
||||||
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
|
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
|
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
|
||||||
}
|
}
|
||||||
if e, a := tc.expectedCondition, actualCondition; !apiextensions.IsCRDConditionEquivalent(&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, actualEstablishedCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
||||||
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user