123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723 |
- /*
- Copyright 2019 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package apiserver
- import (
- "context"
- "encoding/json"
- "fmt"
- "reflect"
- "testing"
- apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
- "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
- "k8s.io/apiextensions-apiserver/test/integration/fixtures"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/types"
- genericfeatures "k8s.io/apiserver/pkg/features"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/client-go/dynamic"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
- "k8s.io/kubernetes/test/integration/framework"
- )
- // TestApplyCRDNoSchema tests that CRDs and CRs can both be applied to with a PATCH request with the apply content type
- // when there is no validation field provided.
- func TestApplyCRDNoSchema(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
- if err != nil {
- t.Fatal(err)
- }
- defer server.TearDownFn()
- config := server.ClientConfig
- apiExtensionClient, err := clientset.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition := fixtures.NewMultipleVersionNoxuCRD(apiextensionsv1beta1.ClusterScoped)
- noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- kind := noxuDefinition.Spec.Names.Kind
- apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version
- name := "mytest"
- rest := apiExtensionClient.Discovery().RESTClient()
- 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.Version, 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)
- // Patch object to change the number of replicas
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"spec":{"replicas": 5}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 5)
- // Re-apply, we should get conflicts now, since the number of replicas was changed.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err == nil {
- t.Fatalf("Expecting to get conflicts when applying object after updating replicas, got no error: %s", result)
- }
- status, ok := err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get conflicts as API error")
- }
- if len(status.Status().Details.Causes) != 1 {
- t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
- }
- // Re-apply with force, should work fine.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("force", "true").
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 1)
- }
- // TestApplyCRDStructuralSchema tests that when a CRD has a structural schema in its validation field,
- // it will be used to construct the CR schema used by apply.
- func TestApplyCRDStructuralSchema(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
- if err != nil {
- t.Fatal(err)
- }
- defer server.TearDownFn()
- config := server.ClientConfig
- apiExtensionClient, err := clientset.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition := fixtures.NewMultipleVersionNoxuCRD(apiextensionsv1beta1.ClusterScoped)
- var c apiextensionsv1beta1.CustomResourceValidation
- err = json.Unmarshal([]byte(`{
- "openAPIV3Schema": {
- "type": "object",
- "properties": {
- "spec": {
- "type": "object",
- "x-kubernetes-preserve-unknown-fields": true,
- "properties": {
- "cronSpec": {
- "type": "string",
- "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
- },
- "ports": {
- "type": "array",
- "x-kubernetes-list-map-keys": [
- "containerPort",
- "protocol"
- ],
- "x-kubernetes-list-type": "map",
- "items": {
- "properties": {
- "containerPort": {
- "format": "int32",
- "type": "integer"
- },
- "hostIP": {
- "type": "string"
- },
- "hostPort": {
- "format": "int32",
- "type": "integer"
- },
- "name": {
- "type": "string"
- },
- "protocol": {
- "type": "string",
- "nullable": true
- }
- },
- "required": [
- "containerPort"
- ],
- "type": "object"
- }
- }
- }
- }
- }
- }
- }`), &c)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition.Spec.Validation = &c
- falseBool := false
- noxuDefinition.Spec.PreserveUnknownFields = &falseBool
- noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- kind := noxuDefinition.Spec.Names.Kind
- apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version
- name := "mytest"
- rest := apiExtensionClient.Discovery().RESTClient()
- yamlBody := []byte(fmt.Sprintf(`
- apiVersion: %s
- kind: %s
- metadata:
- name: %s
- finalizers:
- - test-finalizer
- spec:
- cronSpec: "* * * * */5"
- replicas: 1
- ports:
- - name: x
- containerPort: 80
- protocol: TCP`, apiVersion, kind, name))
- result, err := rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, 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))
- }
- verifyNumFinalizers(t, result, 1)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyReplicas(t, result, 1)
- verifyNumPorts(t, result, 1)
- // Patch object to add another finalizer to the finalizers list
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"metadata":{"finalizers":["test-finalizer","another-one"]}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
- }
- verifyNumFinalizers(t, result, 2)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyFinalizersIncludes(t, result, "another-one")
- // Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- SetHeader("Accept", "application/json").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
- }
- verifyNumFinalizers(t, result, 2)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyFinalizersIncludes(t, result, "another-one")
- // Patch object to change the number of replicas
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"spec":{"replicas": 5}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 5)
- // Re-apply, we should get conflicts now, since the number of replicas was changed.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err == nil {
- t.Fatalf("Expecting to get conflicts when applying object after updating replicas, got no error: %s", result)
- }
- status, ok := err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get conflicts as API error")
- }
- if len(status.Status().Details.Causes) != 1 {
- t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
- }
- // Re-apply with force, should work fine.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("force", "true").
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 1)
- // New applier tries to edit an existing list item, we should get conflicts.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test_2").
- Body([]byte(fmt.Sprintf(`
- apiVersion: %s
- kind: %s
- metadata:
- name: %s
- spec:
- ports:
- - name: "y"
- containerPort: 80
- protocol: TCP`, apiVersion, kind, name))).
- DoRaw(context.TODO())
- if err == nil {
- t.Fatalf("Expecting to get conflicts when a different applier updates existing list item, got no error: %s", result)
- }
- status, ok = err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get conflicts as API error")
- }
- if len(status.Status().Details.Causes) != 1 {
- t.Fatalf("Expecting to get one conflict when a different applier updates existing list item, got: %v", status.Status().Details.Causes)
- }
- // New applier tries to add a new list item, should work fine.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test_2").
- Body([]byte(fmt.Sprintf(`
- apiVersion: %s
- kind: %s
- metadata:
- name: %s
- spec:
- ports:
- - name: "y"
- containerPort: 8080
- protocol: TCP`, apiVersion, kind, name))).
- SetHeader("Accept", "application/json").
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to add a new list item to the object as a different applier: %v:\n%v", err, string(result))
- }
- verifyNumPorts(t, result, 2)
- }
- // TestApplyCRDNonStructuralSchema tests that when a CRD has a non-structural schema in its validation field,
- // it will be used to construct the CR schema used by apply, but any non-structural parts of the schema will be treated as
- // nested maps (same as a CRD without a schema)
- func TestApplyCRDNonStructuralSchema(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
- if err != nil {
- t.Fatal(err)
- }
- defer server.TearDownFn()
- config := server.ClientConfig
- apiExtensionClient, err := clientset.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
- var c apiextensionsv1beta1.CustomResourceValidation
- err = json.Unmarshal([]byte(`{
- "openAPIV3Schema": {
- "type": "object",
- "properties": {
- "spec": {
- "anyOf": [
- {
- "type": "object",
- "properties": {
- "cronSpec": {
- "type": "string",
- "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
- }
- }
- }, {
- "type": "string"
- }
- ]
- }
- }
- }
- }`), &c)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition.Spec.Validation = &c
- noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- kind := noxuDefinition.Spec.Names.Kind
- apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version
- name := "mytest"
- rest := apiExtensionClient.Discovery().RESTClient()
- yamlBody := []byte(fmt.Sprintf(`
- apiVersion: %s
- kind: %s
- metadata:
- name: %s
- finalizers:
- - test-finalizer
- spec:
- cronSpec: "* * * * */5"
- replicas: 1`, apiVersion, kind, name))
- result, err := rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, 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))
- }
- verifyNumFinalizers(t, result, 1)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyReplicas(t, result, 1.0)
- // Patch object to add another finalizer to the finalizers list
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"metadata":{"finalizers":["test-finalizer","another-one"]}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
- }
- verifyNumFinalizers(t, result, 2)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyFinalizersIncludes(t, result, "another-one")
- // Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- SetHeader("Accept", "application/json").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
- }
- verifyNumFinalizers(t, result, 2)
- verifyFinalizersIncludes(t, result, "test-finalizer")
- verifyFinalizersIncludes(t, result, "another-one")
- // Patch object to change the number of replicas
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"spec":{"replicas": 5}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 5.0)
- // Re-apply, we should get conflicts now, since the number of replicas was changed.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err == nil {
- t.Fatalf("Expecting to get conflicts when applying object after updating replicas, got no error: %s", result)
- }
- status, ok := err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get conflicts as API error")
- }
- if len(status.Status().Details.Causes) != 1 {
- t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
- }
- // Re-apply with force, should work fine.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("force", "true").
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 1.0)
- }
- // verifyNumFinalizers checks that len(.metadata.finalizers) == n
- func verifyNumFinalizers(t *testing.T, b []byte, n int) {
- obj := unstructured.Unstructured{}
- err := obj.UnmarshalJSON(b)
- if err != nil {
- t.Fatalf("failed to unmarshal response: %v", err)
- }
- if actual, expected := len(obj.GetFinalizers()), n; actual != expected {
- t.Fatalf("expected %v finalizers but got %v:\n%v", expected, actual, string(b))
- }
- }
- // verifyFinalizersIncludes checks that .metadata.finalizers includes e
- func verifyFinalizersIncludes(t *testing.T, b []byte, e string) {
- obj := unstructured.Unstructured{}
- err := obj.UnmarshalJSON(b)
- if err != nil {
- t.Fatalf("failed to unmarshal response: %v", err)
- }
- for _, a := range obj.GetFinalizers() {
- if a == e {
- return
- }
- }
- t.Fatalf("expected finalizers to include %q but got: %v", e, obj.GetFinalizers())
- }
- // verifyReplicas checks that .spec.replicas == r
- func verifyReplicas(t *testing.T, b []byte, r int) {
- obj := unstructured.Unstructured{}
- err := obj.UnmarshalJSON(b)
- if err != nil {
- t.Fatalf("failed to find replicas number in response: %v:\n%v", err, string(b))
- }
- spec, ok := obj.Object["spec"]
- if !ok {
- t.Fatalf("failed to find replicas number in response:\n%v", string(b))
- }
- specMap, ok := spec.(map[string]interface{})
- if !ok {
- t.Fatalf("failed to find replicas number in response:\n%v", string(b))
- }
- replicas, ok := specMap["replicas"]
- if !ok {
- t.Fatalf("failed to find replicas number in response:\n%v", string(b))
- }
- replicasNumber, ok := replicas.(int64)
- if !ok {
- t.Fatalf("failed to find replicas number in response: expected int64 but got: %v", reflect.TypeOf(replicas))
- }
- if actual, expected := replicasNumber, int64(r); actual != expected {
- t.Fatalf("expected %v ports but got %v:\n%v", expected, actual, string(b))
- }
- }
- // verifyNumPorts checks that len(.spec.ports) == n
- func verifyNumPorts(t *testing.T, b []byte, n int) {
- obj := unstructured.Unstructured{}
- err := obj.UnmarshalJSON(b)
- if err != nil {
- t.Fatalf("failed to find ports list in response: %v:\n%v", err, string(b))
- }
- spec, ok := obj.Object["spec"]
- if !ok {
- t.Fatalf("failed to find ports list in response:\n%v", string(b))
- }
- specMap, ok := spec.(map[string]interface{})
- if !ok {
- t.Fatalf("failed to find ports list in response:\n%v", string(b))
- }
- ports, ok := specMap["ports"]
- if !ok {
- t.Fatalf("failed to find ports list in response:\n%v", string(b))
- }
- portsList, ok := ports.([]interface{})
- if !ok {
- t.Fatalf("failed to find ports list in response: expected array but got: %v", reflect.TypeOf(ports))
- }
- if actual, expected := len(portsList), n; actual != expected {
- t.Fatalf("expected %v ports but got %v:\n%v", expected, actual, string(b))
- }
- }
- // TestApplyCRDUnhandledSchema tests that when a CRD has a schema that kube-openapi ToProtoModels cannot handle correctly,
- // apply falls back to non-schema behavior
- func TestApplyCRDUnhandledSchema(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
- if err != nil {
- t.Fatal(err)
- }
- defer server.TearDownFn()
- config := server.ClientConfig
- apiExtensionClient, err := clientset.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
- // This is a schema that kube-openapi ToProtoModels does not handle correctly.
- // https://github.com/kubernetes/kubernetes/blob/38752f7f99869ed65fb44378360a517649dc2f83/vendor/k8s.io/kube-openapi/pkg/util/proto/document.go#L184
- var c apiextensionsv1beta1.CustomResourceValidation
- err = json.Unmarshal([]byte(`{
- "openAPIV3Schema": {
- "properties": {
- "TypeFooBar": {
- "type": "array"
- }
- }
- }
- }`), &c)
- if err != nil {
- t.Fatal(err)
- }
- noxuDefinition.Spec.Validation = &c
- noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- kind := noxuDefinition.Spec.Names.Kind
- apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version
- name := "mytest"
- rest := apiExtensionClient.Discovery().RESTClient()
- 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.Version, 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)
- // Patch object to change the number of replicas
- result, err = rest.Patch(types.MergePatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Body([]byte(`{"spec":{"replicas": 5}}`)).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 5)
- // Re-apply, we should get conflicts now, since the number of replicas was changed.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err == nil {
- t.Fatalf("Expecting to get conflicts when applying object after updating replicas, got no error: %s", result)
- }
- status, ok := err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get conflicts as API error")
- }
- if len(status.Status().Details.Causes) != 1 {
- t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
- }
- // Re-apply with force, should work fine.
- result, err = rest.Patch(types.ApplyPatchType).
- AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
- Name(name).
- Param("force", "true").
- Param("fieldManager", "apply_test").
- Body(yamlBody).
- DoRaw(context.TODO())
- if err != nil {
- t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
- }
- verifyReplicas(t, result, 1)
- }
|