mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-11 01:41:54 -04:00
Standardize on exemption format
This commit is contained in:
parent
f8a4ffef40
commit
cdcbdc3c43
3 changed files with 63 additions and 45 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
|
@ -40,26 +41,26 @@ func TestFieldsWipingConsistency(t *testing.T) {
|
|||
// All new APIs should use ResetObjectMetaForStatus.
|
||||
statusDoesNotWipeMetadata := sets.New(
|
||||
// https://github.com/kubernetes/kubernetes/issues/137681
|
||||
"apiextensions.k8s.io/customresourcedefinitions",
|
||||
"customresourcedefinitions.apiextensions.k8s.io",
|
||||
|
||||
// APIs that do not use ResetObjectMetaForStatus:
|
||||
"apps/daemonsets",
|
||||
"apps/replicasets",
|
||||
"apps/statefulsets",
|
||||
"batch/cronjobs",
|
||||
"batch/jobs",
|
||||
"autoscaling/horizontalpodautoscalers",
|
||||
"networking.k8s.io/ingresses",
|
||||
"certificatesigningrequests.certificates.k8s.io",
|
||||
"cronjobs.batch",
|
||||
"daemonsets.apps",
|
||||
"horizontalpodautoscalers.autoscaling",
|
||||
"ingresses.networking.k8s.io",
|
||||
"jobs.batch",
|
||||
"namespaces",
|
||||
"nodes",
|
||||
"persistentvolumes",
|
||||
"persistentvolumeclaims",
|
||||
"persistentvolumes",
|
||||
"poddisruptionbudgets.policy",
|
||||
"pods",
|
||||
"replicasets.apps",
|
||||
"replicationcontrollers",
|
||||
"resourcequotas",
|
||||
"services",
|
||||
"policy/poddisruptionbudgets",
|
||||
"namespaces",
|
||||
"certificates.k8s.io/certificatesigningrequests",
|
||||
"statefulsets.apps",
|
||||
)
|
||||
|
||||
TestAllDefinitions(t, "reset-fields-test", func(t *testing.T, api Definition) {
|
||||
|
|
@ -171,16 +172,11 @@ func TestFieldsWipingConsistency(t *testing.T) {
|
|||
t.Errorf("Status endpoint: SSA ResetMetadata (%v) and PrepareForUpdate wipeMetadata (%v) behaviors do not match.", ssaStatusResetsMetadata, statusWipesMetadata)
|
||||
}
|
||||
|
||||
gr := api.Mapping.Resource.Resource
|
||||
if api.Mapping.Resource.Group != "" {
|
||||
gr = api.Mapping.Resource.Group + "/" + gr
|
||||
}
|
||||
|
||||
// Enforce field wiping behaviors, with allowlists for pre-existing exceptions.
|
||||
assertWiped(t, gr, "create must wipe status", createWipesStatus, createDoesNotWipeStatus)
|
||||
assertWiped(t, gr, "update must wipe status", mainWipesStatus, sets.New[string]()) // No exemptions exist for this, please do not add any in the future.
|
||||
assertWiped(t, gr, "status update must wipe spec", statusWipesSpec, sets.New[string]()) // No exemptions exist for this, please do not add any in the future.
|
||||
assertWiped(t, gr, "status update must wipe metadata", statusWipesMetadata, statusDoesNotWipeMetadata)
|
||||
assertWiped(t, api.Mapping.Resource, "create must wipe status", createWipesStatus, createDoesNotWipeStatus)
|
||||
assertWiped(t, api.Mapping.Resource, "update must wipe status", mainWipesStatus, sets.New[string]()) // No exemptions exist for this, please do not add any in the future.
|
||||
assertWiped(t, api.Mapping.Resource, "status update must wipe spec", statusWipesSpec, sets.New[string]()) // No exemptions exist for this, please do not add any in the future.
|
||||
assertWiped(t, api.Mapping.Resource, "status update must wipe metadata", statusWipesMetadata, statusDoesNotWipeMetadata)
|
||||
|
||||
if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {
|
||||
t.Logf("Failed to delete %v: %v", name, err)
|
||||
|
|
@ -223,15 +219,16 @@ func managedFieldsOwnTopLevelField(t *testing.T, fieldsV1 *metav1.FieldsV1, fiel
|
|||
}
|
||||
|
||||
// assertWiped checks that a field wiping behavior holds, with an allowlist for known exceptions.
|
||||
func assertWiped(t *testing.T, gr string, msg string, wiped bool, allowed sets.Set[string]) {
|
||||
func assertWiped(t *testing.T, gvr schema.GroupVersionResource, msg string, wiped bool, allowed sets.Set[string]) {
|
||||
t.Helper()
|
||||
if allowed.Has(gr) {
|
||||
name := ResourceString(gvr)
|
||||
if matchesException(gvr, allowed) {
|
||||
if wiped {
|
||||
t.Errorf("%s: %s unexpectedly wiped. Remove it from the exception list.", gr, msg)
|
||||
t.Errorf("%s: %s unexpectedly wiped. Remove it from the exception list.", name, msg)
|
||||
}
|
||||
} else {
|
||||
if !wiped {
|
||||
t.Errorf("%s: %s", gr, msg)
|
||||
t.Errorf("%s: %s", name, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
|
@ -36,19 +35,19 @@ func TestGenerationManagement(t *testing.T) {
|
|||
|
||||
// DO NOT ADD NEW ENTRIES HERE.
|
||||
// This tracks resources that have status but do not manage generation.
|
||||
generationExempt := sets.New[schema.GroupResource](
|
||||
schema.GroupResource{Group: "apiregistration.k8s.io", Resource: "apiservices"},
|
||||
schema.GroupResource{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
|
||||
schema.GroupResource{Group: "certificates.k8s.io", Resource: "certificatesigningrequests"},
|
||||
schema.GroupResource{Group: "networking.k8s.io", Resource: "servicecidrs"},
|
||||
schema.GroupResource{Group: "resource.k8s.io", Resource: "resourceclaims"},
|
||||
schema.GroupResource{Group: "storage.k8s.io", Resource: "volumeattachments"},
|
||||
schema.GroupResource{Group: "", Resource: "persistentvolumeclaims"},
|
||||
schema.GroupResource{Group: "", Resource: "namespaces"},
|
||||
schema.GroupResource{Group: "", Resource: "nodes"},
|
||||
schema.GroupResource{Group: "", Resource: "persistentvolumes"},
|
||||
schema.GroupResource{Group: "", Resource: "resourcequotas"},
|
||||
schema.GroupResource{Group: "", Resource: "services"},
|
||||
generationExempt := sets.New(
|
||||
"apiservices.apiregistration.k8s.io",
|
||||
"certificatesigningrequests.certificates.k8s.io",
|
||||
"horizontalpodautoscalers.autoscaling",
|
||||
"namespaces",
|
||||
"nodes",
|
||||
"persistentvolumeclaims",
|
||||
"persistentvolumes",
|
||||
"resourceclaims.resource.k8s.io",
|
||||
"resourcequotas",
|
||||
"servicecidrs.networking.k8s.io",
|
||||
"services",
|
||||
"volumeattachments.storage.k8s.io",
|
||||
)
|
||||
|
||||
TestAllDefinitions(t, "generation-namespace", func(t *testing.T, api Definition) {
|
||||
|
|
@ -80,7 +79,7 @@ func TestGenerationManagement(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify that generation initializes to 1.
|
||||
if generationExempt.Has(api.Mapping.Resource.GroupResource()) {
|
||||
if matchesException(api.Mapping.Resource, generationExempt) {
|
||||
if baseline.GetGeneration() != 0 {
|
||||
t.Errorf("Expected generation exempt resource always have generation 0, but got %v", baseline.GetGeneration())
|
||||
}
|
||||
|
|
@ -112,7 +111,7 @@ func TestGenerationManagement(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Logf("Patch to main endpoint failed: %v", err)
|
||||
} else if result.GetGeneration() <= afterStatus.GetGeneration() {
|
||||
if generationExempt.Has(api.Mapping.Resource.GroupResource()) {
|
||||
if matchesException(api.Mapping.Resource, generationExempt) {
|
||||
if result.GetGeneration() != 0 {
|
||||
t.Errorf("Expected generation exempt resource always have generation 0, but got %v", result.GetGeneration())
|
||||
}
|
||||
|
|
@ -120,9 +119,5 @@ func TestGenerationManagement(t *testing.T) {
|
|||
t.Errorf("Expected generation to monotonically increase after spec update (was %v, got %v)", afterStatus.GetGeneration(), result.GetGeneration())
|
||||
}
|
||||
}
|
||||
|
||||
if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {
|
||||
t.Logf("Failed to delete %v: %v", name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
|
@ -72,6 +73,9 @@ func (d *Definition) ResourceClient() dynamic.ResourceInterface {
|
|||
return d.DynamicClient.Resource(d.Mapping.Resource).Namespace(namespace)
|
||||
}
|
||||
|
||||
// TestAllDefinitions starts an apiserver and runs testFunc against every
|
||||
// discoverable resource. It registers a fixed set of CRDs so that a sample
|
||||
// set of custom resources discoverable and tested.
|
||||
func TestAllDefinitions(t *testing.T, testNamespace string, testFunc DefinitionTestFunc) {
|
||||
server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd())
|
||||
if err != nil {
|
||||
|
|
@ -169,6 +173,28 @@ func CreateMapping(groupVersion string, resource metav1.APIResource) (*meta.REST
|
|||
}, nil
|
||||
}
|
||||
|
||||
// ResourceString returns the kubectl-style "resource.version.group" representation
|
||||
// of a GroupVersionResource (e.g. "deployments.v1.apps", "pods.v1").
|
||||
func ResourceString(gvr schema.GroupVersionResource) string {
|
||||
if gvr.Group == "" {
|
||||
return gvr.Resource + "." + gvr.Version
|
||||
}
|
||||
return gvr.Resource + "." + gvr.Version + "." + gvr.Group
|
||||
}
|
||||
|
||||
// matchesException returns true if gvr matches any entry in exceptions.
|
||||
// Each entry must be a kubectl-style resource string in either
|
||||
// "resource.group" form (e.g. "pods", "deployments.apps",
|
||||
// "customresourcedefinitions.apiextensions.k8s.io"), which matches all
|
||||
// versions of that resource, or "resource.version.group" form (e.g.
|
||||
// "pods.v1", "deployments.v1.apps"), which matches a single version.
|
||||
func matchesException(gvr schema.GroupVersionResource, exceptions sets.Set[string]) bool {
|
||||
if exceptions.Has(gvr.GroupResource().String()) {
|
||||
return true
|
||||
}
|
||||
return exceptions.Has(ResourceString(gvr))
|
||||
}
|
||||
|
||||
// TestObj is a generic test helper that creates an Unstructured object from a creation stub
|
||||
// and explicitly sets the status from a separate JSON payload.
|
||||
func TestObj(t *testing.T, stub, status string, gvk schema.GroupVersionKind) *unstructured.Unstructured {
|
||||
|
|
|
|||
Loading…
Reference in a new issue