Skip to content

Skip creating storages for non-stored and non-served versions #130704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

GrigoriyMikhalkin
Copy link

@GrigoriyMikhalkin GrigoriyMikhalkin commented Mar 10, 2025

What type of PR is this?

/kind bug

What this PR does / why we need it:

This is update on #129709.

This PR removes storage creation for non-stored and non-served versions.

Tested locally scenario proposed in #129979:

v1alpha3: served: false, storage: false
v1alpha4: served: true, storage: false
v1beta1: served: true, storage: true

ListWatch is created only for v1alpha4 and v1beta1 as expected.

Which issue(s) this PR fixes:

Fixes #129979

Special notes for your reviewer:

Does this PR introduce a user-facing change?


Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:


@k8s-ci-robot
Copy link
Contributor

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. kind/bug Categorizes issue or PR as related to a bug. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Mar 10, 2025
@k8s-ci-robot
Copy link
Contributor

Welcome @GrigoriyMikhalkin!

It looks like this is your first PR to kubernetes/kubernetes 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes/kubernetes has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Mar 10, 2025
@k8s-ci-robot
Copy link
Contributor

Hi @GrigoriyMikhalkin. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added needs-priority Indicates a PR lacks a `priority/foo` label and requires one. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Mar 10, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: GrigoriyMikhalkin
Once this PR has been reviewed and has the lgtm label, please assign deads2k for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@GrigoriyMikhalkin GrigoriyMikhalkin changed the title WIP: skip creating storages for non-stored and non-served versions Skip creating storages for non-stored and non-served versions Mar 10, 2025
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 10, 2025
@aaron-prindle
Copy link
Contributor

/cc @siyuanfoundation

/triage accepted

@k8s-ci-robot k8s-ci-robot added triage/accepted Indicates an issue or PR is ready to be actively worked on. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Mar 11, 2025
@liggitt
Copy link
Member

liggitt commented May 21, 2025

cc @jpbetz

@liggitt liggitt added this to @liggitt May 21, 2025
@liggitt
Copy link
Member

liggitt commented May 21, 2025

/ok-to-test

is it possible to add a test demonstrating this fixes the spurious storage issue?

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels May 21, 2025
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Jul 28, 2025
@GrigoriyMikhalkin
Copy link
Author

@liggitt Added integration test

@k8s-ci-robot
Copy link
Contributor

@GrigoriyMikhalkin: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubernetes-e2e-kind 4102ba6 link true /test pull-kubernetes-e2e-kind
pull-kubernetes-node-e2e-containerd 4102ba6 link true /test pull-kubernetes-node-e2e-containerd
pull-kubernetes-e2e-gce 4102ba6 link true /test pull-kubernetes-e2e-gce
pull-kubernetes-integration 4102ba6 link true /test pull-kubernetes-integration

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@@ -818,6 +818,11 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
return nil, fmt.Errorf("the server could not properly serve the list kind")
}

// Do not construct storage if version is neither served nor stored
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think short-circuiting at this point in the loop is correct, but it's really hard to reason about which things in this loop have side-effects we need to preserve.

I think it is just the RegisterKindFor calls, but I'm not 100% sure.

I think it would be clearer to add a loop before the storage-constructing loop, which we do not short-circuit, that does whatever validation / equivalent kind registration we want to do for all versions:

	// ensure accepted names are valid
	if len(crd.Status.AcceptedNames.Plural) == 0 {
		utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.plural", crd.Name))
		return nil, fmt.Errorf("the server could not properly serve the resource")
	}
	if len(crd.Status.AcceptedNames.Singular) == 0 {
		utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.singular", crd.Name))
		return nil, fmt.Errorf("the server could not properly serve the resource")
	}
	if len(crd.Status.AcceptedNames.Kind) == 0 {
		utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.kind", crd.Name))
		return nil, fmt.Errorf("the server could not properly serve the kind")
	}
	if len(crd.Status.AcceptedNames.ListKind) == 0 {
		utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.listKind", crd.Name))
		return nil, fmt.Errorf("the server could not properly serve the list kind")
	}

	var (
		versionToResource         = map[string]schema.GroupVersionResource{}
		versionToSingularResource = map[string]schema.GroupVersionResource{}
		versionToKind             = map[string]schema.GroupVersionKind{}
		versionToListKind         = map[string]schema.GroupVersionKind{}
		versionToSubresources     = map[string]*apiextensionsv1.CustomResourceSubresources{}
	)
	// compute resource, singularResource, kind, subresources, and register equivalent kinds for all versions
	for _, v := range crd.Spec.Versions {
		versionToResource[v.Name] = schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural}
		versionToSingularResource[v.Name] = schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Singular}
		versionToKind[v.Name] = schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind}
		versionToListKind[v.Name] = schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.ListKind}
		versionToSubresources[v.Name], err = apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR subresources")
		}

		// Register equivalent kinds
		equivalentResourceRegistry.RegisterKindFor(versionToResource[v.Name], "", versionToKind[v.Name])
		if versionToSubresources[v.Name] != nil {
			if versionToSubresources[v.Name].Status != nil {
				equivalentResourceRegistry.RegisterKindFor(versionToResource[v.Name], "status", versionToKind[v.Name])
			}
			if versionToSubresources[v.Name].Scale != nil {
				equivalentResourceRegistry.RegisterKindFor(versionToResource[v.Name], "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale"))
			}
		}
	}

and then short-circuit this loop at the very top

+       // construct storage for served / stored versions
        for _, v := range crd.Spec.Versions {
+               if !v.Storage && !v.Served {
+                       continue
+               }
+
                // In addition to Unstructured objects (Custom Resources), we also may sometimes need to
                // decode unversioned Options objects, so we delegate to parameterScheme for such types.
                parameterScheme := runtime.NewScheme()
@@ -735,22 +787,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
                )
                parameterCodec := runtime.NewParameterCodec(parameterScheme)
 
-               resource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural}
-               if len(resource.Resource) == 0 {
-                       utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.plural", crd.Name))
-                       return nil, fmt.Errorf("the server could not properly serve the resource")
-               }
-               singularResource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Singular}
-               if len(singularResource.Resource) == 0 {
-                       utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.singular", crd.Name))
-                       return nil, fmt.Errorf("the server could not properly serve the resource")
-               }
-               kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind}
-               if len(kind.Kind) == 0 {
-                       utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.kind", crd.Name))
-                       return nil, fmt.Errorf("the server could not properly serve the kind")
-               }
-               equivalentResourceRegistry.RegisterKindFor(resource, "", kind)
+               resource := versionToResource[v.Name]
+               singularResource := versionToSingularResource[v.Name]
+               kind := versionToKind[v.Name]
 
                typer := newUnstructuredObjectTyper(parameterScheme)
                creator := unstructuredCreator{}
@@ -776,13 +815,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
 
                var statusSpec *apiextensionsinternal.CustomResourceSubresourceStatus
                var statusValidator apiservervalidation.SchemaValidator
-               subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
-               if err != nil {
-                       utilruntime.HandleError(err)
-                       return nil, fmt.Errorf("the server could not properly serve the CR subresources")
-               }
+               subresources := versionToSubresources[v.Name]
                if subresources != nil && subresources.Status != nil {
-                       equivalentResourceRegistry.RegisterKindFor(resource, "status", kind)
                        statusSpec = &apiextensionsinternal.CustomResourceSubresourceStatus{}
                        if err := apiextensionsv1.Convert_v1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(subresources.Status, statusSpec, nil); err != nil {
                                return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
@@ -800,7 +834,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
 
                var scaleSpec *apiextensionsinternal.CustomResourceSubresourceScale
                if subresources != nil && subresources.Scale != nil {
-                       equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale"))
                        scaleSpec = &apiextensionsinternal.CustomResourceSubresourceScale{}
                        if err := apiextensionsv1.Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(subresources.Scale, scaleSpec, nil); err != nil {
                                return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
@@ -817,11 +850,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
                        klog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
                }
 
-               listKind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.ListKind}
-               if len(listKind.Kind) == 0 {
-                       utilruntime.HandleError(fmt.Errorf("CustomResourceDefinition %s has unexpected empty status.acceptedNames.listKind", crd.Name))
-                       return nil, fmt.Errorf("the server could not properly serve the list kind")
-               }
+               listKind := versionToListKind[v.Name]
 
                storages[v.Name], err = customresource.NewStorage(
                        resource.GroupResource(),

Comment on lines +70 to +71
// KUBE_APISERVER_SERVE_REMOVED_APIS_FOR_ONE_RELEASE allows for APIs pending removal to not block tests
t.Setenv("KUBE_APISERVER_SERVE_REMOVED_APIS_FOR_ONE_RELEASE", "true")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need this set for this test

Comment on lines 528 to +531
for _, createVersion := range ctc.crd.Spec.Versions {
if !createVersion.Storage && !createVersion.Served {
continue
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for _, createVersion := range ctc.crd.Spec.Versions {
if !createVersion.Storage && !createVersion.Served {
continue
}
for _, createVersion := range servedVersions(ctc.crd.Spec.Versions) {

Comment on lines 566 to +569
for _, getVersion := range ctc.crd.Spec.Versions {
if !getVersion.Storage && !getVersion.Served {
continue
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for _, getVersion := range ctc.crd.Spec.Versions {
if !getVersion.Storage && !getVersion.Served {
continue
}
for _, getVersion := range servedVersions(ctc.crd.Spec.Versions) {

for _, createVersion := range ctc.crd.Spec.Versions {
servedVersions := []apiextensionsv1.CustomResourceDefinitionVersion{}
for _, version := range ctc.crd.Spec.Versions {
if !version.Storage && !version.Served {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we should only be checking version.Served when accumulating a list named servedVersions:

Suggested change
if !version.Storage && !version.Served {
if !version.Served {

maybe make this a helper function:

func servedVersions(versions []apiextensionsv1.CustomResourceDefinitionVersion) []apiextensionsv1.CustomResourceDefinitionVersion

so all the places we're iterating over versions can just wrap with a call to the helper instead of adding custom loop logic (noted a couple below, but I think ~all of them could switch to that style)

@@ -1224,6 +1348,48 @@ var multiVersionFixture = &apiextensionsv1.CustomResourceDefinition{
},
},
},
{
// same schema as v1beta1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// same schema as v1beta1
// same schema as v1beta1, but not served

}
// nolint:errcheck
defer etcdClient.Close()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to make sure that existing objects in etcd can still be successfully read and converted from a non-served, non-storage version, and can be updated

  1. create the CRD serving all versions, storing in v1alpha2
  2. server-side apply an object via the v1alpha2 API (will persist in v1alpha2)
  3. update the CRD to switch the storage version to a different version, stop serving v1alpha2, and set a conversion webhook
  4. ensure the conversion webhook is never asked to convert to v1alpha2 (as this test already does)
  5. ensure the previously created object can still be read via a different API version
  6. ensure the previously created object can still be updated via a different API version using server-side apply
  7. ensure the update does not trigger a conversion request to v1alpha2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. kind/bug Categorizes issue or PR as related to a bug. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

CRD conversion webhooks should not be called for unused apiVersions
5 participants