diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 4ec10bc2f2a..7da41befd6b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -1054,6 +1054,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd // resetField write all version information for _, v := range crd.Spec.Versions { clusterScoped := crd.Spec.Scope == apiextensionsv1.ClusterScoped + if requestScopes[v.Name] == nil { + continue + } statusScope := *requestScopes[v.Name] statusScope.Subresource = "status" statusScope.Namer = handlers.ContextBasedNaming{ @@ -1069,6 +1072,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd if subresources != nil && subresources.Status != nil { var statusResetFields = make(map[fieldpath.APIVersion]*fieldpath.Set) for _, value := range crd.Spec.Versions { + if storages[value.Name].Status == nil { + continue + } resetField := storages[value.Name].Status.GetResetFields() for apiVersion, set := range resetField { statusResetFields[apiVersion] = set diff --git a/test/integration/apiserver/apply/reset_fields_test.go b/test/integration/apiserver/apply/reset_fields_test.go index 86a53619e5f..bee268c58ee 100644 --- a/test/integration/apiserver/apply/reset_fields_test.go +++ b/test/integration/apiserver/apply/reset_fields_test.go @@ -25,11 +25,14 @@ import ( "testing" v1 "k8s.io/api/core/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/test/integration/fixtures" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" @@ -321,6 +324,96 @@ func TestApplyResetFields(t *testing.T) { } } +// TestUpdateStatusWithOldVersion tests that apply with resetFields works as expected. +func TestUpdateStatusWithOldVersion(t *testing.T) { + server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + apiExtensionClient, err := apiextensionsclientset.NewForConfig(server.ClientConfig) + if err != nil { + t.Fatal(err) + } + dynamicClient, err := dynamic.NewForConfig(server.ClientConfig) + if err != nil { + t.Fatal(err) + } + + noxuBetaDefinition := nearlyRemovedBetaMultipleVersionNoxuCRD(apiextensionsv1beta1.ClusterScoped) + + noxuDefinition, err := fixtures.CreateCRDUsingRemovedAPI(server.EtcdClient, server.EtcdStoragePrefix, noxuBetaDefinition, apiExtensionClient, dynamicClient) + if err != nil { + t.Fatal(err) + } + kind := noxuDefinition.Spec.Names.Kind + apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[1].Name + name := "mytest" + + rest := apiExtensionClient.Discovery().RESTClient() + // create cr resource by v1beta1 version restful api + yamlBody := []byte(fmt.Sprintf(` +apiVersion: %s +kind: %s +metadata: + name: %s +spec: + replicas: 1`, apiVersion, kind, name)) + result, err := rest.Patch(types.ApplyPatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[1].Name, noxuDefinition.Spec.Names.Plural). + Name(name). + Param("fieldManager", "apply_test"). + Body(yamlBody). + DoRaw(context.TODO()) + if err != nil { + t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result)) + } + verifyReplicas(t, result, 1) + + oldManagedFields, err := getManagedFields(result) + if err != nil { + t.Fatalf("failed to get managed fields: %v", err) + } + // update sub status by v1beta2 version restful api + updateStatusBytes := []byte(`{ + "status": { + "xx": "a" + } +}`) + result, err = rest.Patch(types.MergePatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). + SubResource("status"). + Name(name). + Param("fieldManager", "subresource_test"). + Body(updateStatusBytes). + DoRaw(context.TODO()) + if err != nil { + t.Fatalf("Error updating subresource: %v ", err) + } + newManagedFields, err := getManagedFields(result) + // newManagedFields should include oldManagedFields + var applyManagerFound, subresourceManagerFound bool + for i, field := range newManagedFields { + if field.Manager == "apply_test" { + if !reflect.DeepEqual(newManagedFields[i], oldManagedFields[0]) { + t.Fatalf("Expected managed fields to not have changed when trying manually setting them via subresoures.\n\nExpected: %#v\n\nGot: %#v", oldManagedFields[0], newManagedFields[i]) + } + applyManagerFound = true + } + if field.Manager == "subresource_test" { + subresourceManagerFound = true + } + } + if !applyManagerFound { + t.Errorf("expected field manager 'apply_test' to be present in newManagedFields") + } + if !subresourceManagerFound { + t.Errorf("expected field manager 'subresource_test' to be present in newManagedFields") + } + +} + func expectConflict(objRet *unstructured.Unstructured, err error, dynamicClient dynamic.Interface, resource schema.GroupVersionResource, namespace, name string) error { if err != nil && strings.Contains(err.Error(), "conflict") { return nil