diff --git a/test/integration/apiserver/apidefinitions/fields_wiping_test.go b/test/integration/apiserver/apidefinitions/fields_wiping_test.go index 62a9bdef0e3..f20ae0d76a3 100644 --- a/test/integration/apiserver/apidefinitions/fields_wiping_test.go +++ b/test/integration/apiserver/apidefinitions/fields_wiping_test.go @@ -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) } } } diff --git a/test/integration/apiserver/apidefinitions/generation_test.go b/test/integration/apiserver/apidefinitions/generation_test.go index 7d08e837ac3..313bfd9731e 100644 --- a/test/integration/apiserver/apidefinitions/generation_test.go +++ b/test/integration/apiserver/apidefinitions/generation_test.go @@ -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) - } }) } diff --git a/test/integration/apiserver/apidefinitions/helper.go b/test/integration/apiserver/apidefinitions/helper.go index 3b08f877214..c8190c718cd 100644 --- a/test/integration/apiserver/apidefinitions/helper.go +++ b/test/integration/apiserver/apidefinitions/helper.go @@ -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 {