1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603 |
- /*
- Copyright 2018 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"
- "flag"
- "fmt"
- "net/http"
- "net/http/httptest"
- "reflect"
- "strings"
- "testing"
- "time"
- "sigs.k8s.io/yaml"
- v1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/api/meta"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/types"
- genericfeatures "k8s.io/apiserver/pkg/features"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/client-go/kubernetes"
- clientset "k8s.io/client-go/kubernetes"
- restclient "k8s.io/client-go/rest"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- "k8s.io/kubernetes/pkg/master"
- "k8s.io/kubernetes/test/integration/framework"
- )
- func setup(t testing.TB, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
- opts := framework.MasterConfigOptions{EtcdOptions: framework.DefaultEtcdOptions()}
- opts.EtcdOptions.DefaultStorageMediaType = "application/vnd.kubernetes.protobuf"
- masterConfig := framework.NewIntegrationTestMasterConfigWithOptions(&opts)
- if len(groupVersions) > 0 {
- resourceConfig := master.DefaultAPIResourceConfigSource()
- resourceConfig.EnableVersions(groupVersions...)
- masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
- }
- masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
- _, s, closeFn := framework.RunAMaster(masterConfig)
- clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL, QPS: -1})
- if err != nil {
- t.Fatalf("Error in create clientset: %v", err)
- }
- return s, clientSet, closeFn
- }
- // TestApplyAlsoCreates makes sure that PATCH requests with the apply content type
- // will create the object if it doesn't already exist
- // TODO: make a set of test cases in an easy-to-consume place (separate package?) so it's easy to test in both integration and e2e.
- func TestApplyAlsoCreates(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- testCases := []struct {
- resource string
- name string
- body string
- }{
- {
- resource: "pods",
- name: "test-pod",
- body: `{
- "apiVersion": "v1",
- "kind": "Pod",
- "metadata": {
- "name": "test-pod"
- },
- "spec": {
- "containers": [{
- "name": "test-container",
- "image": "test-image"
- }]
- }
- }`,
- }, {
- resource: "services",
- name: "test-svc",
- body: `{
- "apiVersion": "v1",
- "kind": "Service",
- "metadata": {
- "name": "test-svc"
- },
- "spec": {
- "ports": [{
- "port": 8080,
- "protocol": "UDP"
- }]
- }
- }`,
- },
- }
- for _, tc := range testCases {
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource(tc.resource).
- Name(tc.name).
- Param("fieldManager", "apply_test").
- Body([]byte(tc.body)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Get().Namespace("default").Resource(tc.resource).Name(tc.name).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- // Test that we can re apply with a different field manager and don't get conflicts
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource(tc.resource).
- Name(tc.name).
- Param("fieldManager", "apply_test_2").
- Body([]byte(tc.body)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to re-apply object using Apply patch: %v", err)
- }
- }
- }
- // TestNoOpUpdateSameResourceVersion makes sure that PUT requests which change nothing
- // will not change the resource version (no write to etcd is done)
- func TestNoOpUpdateSameResourceVersion(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- podName := "no-op"
- podResource := "pods"
- podBytes := []byte(`{
- "apiVersion": "v1",
- "kind": "Pod",
- "metadata": {
- "name": "` + podName + `",
- "labels": {
- "a": "one",
- "c": "two",
- "b": "three"
- }
- },
- "spec": {
- "containers": [{
- "name": "test-container-a",
- "image": "test-image-one"
- },{
- "name": "test-container-c",
- "image": "test-image-two"
- },{
- "name": "test-container-b",
- "image": "test-image-three"
- }]
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Param("fieldManager", "apply_test").
- Resource(podResource).
- Name(podName).
- Body(podBytes).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object: %v", err)
- }
- // Sleep for one second to make sure that the times of each update operation is different.
- time.Sleep(1 * time.Second)
- createdObject, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource(podResource).Name(podName).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve created object: %v", err)
- }
- createdAccessor, err := meta.Accessor(createdObject)
- if err != nil {
- t.Fatalf("Failed to get meta accessor for created object: %v", err)
- }
- createdBytes, err := json.MarshalIndent(createdObject, "\t", "\t")
- if err != nil {
- t.Fatalf("Failed to marshal created object: %v", err)
- }
- // Test that we can put the same object and don't change the RV
- _, err = client.CoreV1().RESTClient().Put().
- Namespace("default").
- Resource(podResource).
- Name(podName).
- Body(createdBytes).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to apply no-op update: %v", err)
- }
- updatedObject, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource(podResource).Name(podName).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve updated object: %v", err)
- }
- updatedAccessor, err := meta.Accessor(updatedObject)
- if err != nil {
- t.Fatalf("Failed to get meta accessor for updated object: %v", err)
- }
- updatedBytes, err := json.MarshalIndent(updatedObject, "\t", "\t")
- if err != nil {
- t.Fatalf("Failed to marshal updated object: %v", err)
- }
- if createdAccessor.GetResourceVersion() != updatedAccessor.GetResourceVersion() {
- t.Fatalf("Expected same resource version to be %v but got: %v\nold object:\n%v\nnew object:\n%v",
- createdAccessor.GetResourceVersion(),
- updatedAccessor.GetResourceVersion(),
- string(createdBytes),
- string(updatedBytes),
- )
- }
- }
- // TestCreateOnApplyFailsWithUID makes sure that PATCH requests with the apply content type
- // will not create the object if it doesn't already exist and it specifies a UID
- func TestCreateOnApplyFailsWithUID(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("pods").
- Name("test-pod-uid").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "Pod",
- "metadata": {
- "name": "test-pod-uid",
- "uid": "88e00824-7f0e-11e8-94a1-c8d3ffb15800"
- },
- "spec": {
- "containers": [{
- "name": "test-container",
- "image": "test-image"
- }]
- }
- }`)).
- Do(context.TODO()).
- Get()
- if !apierrors.IsConflict(err) {
- t.Fatalf("Expected conflict error but got: %v", err)
- }
- }
- func TestApplyUpdateApplyConflictForced(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "replicas": 3,
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Body([]byte(`{"spec":{"replicas": 5}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body([]byte(obj)).Do(context.TODO()).Get()
- if err == nil {
- t.Fatalf("Expecting to get conflicts when applying object")
- }
- 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 at least one conflict when applying object, got: %v", status.Status().Details.Causes)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("force", "true").
- Param("fieldManager", "apply_test").
- Body([]byte(obj)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to apply object with force: %v", err)
- }
- }
- // TestApplyGroupsManySeparateUpdates tests that when many different managers update the same object,
- // the number of managedFields entries will only grow to a certain size.
- func TestApplyGroupsManySeparateUpdates(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "admissionregistration.k8s.io/v1",
- "kind": "ValidatingWebhookConfiguration",
- "metadata": {
- "name": "webhook",
- "labels": {"applier":"true"},
- },
- }`)
- object, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/admissionregistration.k8s.io/v1").
- Resource("validatingwebhookconfigurations").
- Name("webhook").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- for i := 0; i < 20; i++ {
- unique := fmt.Sprintf("updater%v", i)
- version := "v1"
- if i%2 == 0 {
- version = "v1beta1"
- }
- object, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
- AbsPath("/apis/admissionregistration.k8s.io/"+version).
- Resource("validatingwebhookconfigurations").
- Name("webhook").
- Param("fieldManager", unique).
- Body([]byte(`{"metadata":{"labels":{"` + unique + `":"new"}}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- // Expect 11 entries, because the cap for update entries is 10, and 1 apply entry
- if actual, expected := len(accessor.GetManagedFields()), 11; actual != expected {
- if b, err := json.MarshalIndent(object, "\t", "\t"); err == nil {
- t.Fatalf("Object expected to contain %v entries in managedFields, but got %v:\n%v", expected, actual, string(b))
- } else {
- t.Fatalf("Object expected to contain %v entries in managedFields, but got %v: error marshalling object: %v", expected, actual, err)
- }
- }
- // Expect the first entry to have the manager name "apply_test"
- if actual, expected := accessor.GetManagedFields()[0].Manager, "apply_test"; actual != expected {
- t.Fatalf("Expected first manager to be named %v but got %v", expected, actual)
- }
- // Expect the second entry to have the manager name "ancient-changes"
- if actual, expected := accessor.GetManagedFields()[1].Manager, "ancient-changes"; actual != expected {
- t.Fatalf("Expected first manager to be named %v but got %v", expected, actual)
- }
- }
- // TestApplyManagedFields makes sure that managedFields api does not change
- func TestApplyManagedFields(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "updater").
- Body([]byte(`{"data":{"new-key": "value"}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- // Sleep for one second to make sure that the times of each update operation is different.
- // This will let us check that update entries with the same manager name are grouped together,
- // and that the most recent update time is recorded in the grouped entry.
- time.Sleep(1 * time.Second)
- _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "updater").
- Body([]byte(`{"data":{"key": "new value"}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- actual, err := json.MarshalIndent(object, "\t", "\t")
- if err != nil {
- t.Fatalf("Failed to marshal object: %v", err)
- }
- expected := []byte(`{
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "selfLink": "` + accessor.GetSelfLink() + `",
- "uid": "` + string(accessor.GetUID()) + `",
- "resourceVersion": "` + accessor.GetResourceVersion() + `",
- "creationTimestamp": "` + accessor.GetCreationTimestamp().UTC().Format(time.RFC3339) + `",
- "labels": {
- "test-label": "test"
- },
- "managedFields": [
- {
- "manager": "apply_test",
- "operation": "Apply",
- "apiVersion": "v1",
- "time": "` + accessor.GetManagedFields()[0].Time.UTC().Format(time.RFC3339) + `",
- "fieldsType": "FieldsV1",
- "fieldsV1": {
- "f:metadata": {
- "f:labels": {
- "f:test-label": {}
- }
- }
- }
- },
- {
- "manager": "updater",
- "operation": "Update",
- "apiVersion": "v1",
- "time": "` + accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) + `",
- "fieldsType": "FieldsV1",
- "fieldsV1": {
- "f:data": {
- "f:key": {},
- "f:new-key": {}
- }
- }
- }
- ]
- },
- "data": {
- "key": "new value",
- "new-key": "value"
- }
- }`)
- if string(expected) != string(actual) {
- t.Fatalf("Expected:\n%v\nGot:\n%v", string(expected), string(actual))
- }
- if accessor.GetManagedFields()[0].Time.UTC().Format(time.RFC3339) == accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) {
- t.Fatalf("Expected times to be different but got:\n%v", string(actual))
- }
- }
- // TestApplyRemovesEmptyManagedFields there are no empty managers in managedFields
- func TestApplyRemovesEmptyManagedFields(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default"
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body(obj).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- if managed := accessor.GetManagedFields(); managed != nil {
- t.Fatalf("Object contains unexpected managedFields: %v", managed)
- }
- }
- func TestApplyRequiresFieldManager(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default"
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Body(obj).
- Do(context.TODO()).
- Get()
- if err == nil {
- t.Fatalf("Apply should fail to create without fieldManager")
- }
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body(obj).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Apply failed to create with fieldManager: %v", err)
- }
- }
- // TestApplyRemoveContainerPort removes a container port from a deployment
- func TestApplyRemoveContainerPort(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "replicas": 3,
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest",
- "ports": [{
- "containerPort": 80,
- "protocol": "TCP"
- }]
- }]
- }
- }
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- obj = []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "replicas": 3,
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to remove container port using Apply patch: %v", err)
- }
- deployment, err := client.AppsV1().Deployments("default").Get(context.TODO(), "deployment", metav1.GetOptions{})
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- if len(deployment.Spec.Template.Spec.Containers[0].Ports) > 0 {
- t.Fatalf("Expected no container ports but got: %v, object: \n%#v", deployment.Spec.Template.Spec.Containers[0].Ports, deployment)
- }
- }
- // TestApplyFailsWithVersionMismatch ensures that a version mismatch between the
- // patch object and the live object will error
- func TestApplyFailsWithVersionMismatch(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "replicas": 3,
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- obj = []byte(`{
- "apiVersion": "extensions/v1beta",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "replicas": 100,
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "apply_test").
- Body([]byte(obj)).Do(context.TODO()).Get()
- if err == nil {
- t.Fatalf("Expecting to get version mismatch when applying object")
- }
- status, ok := err.(*apierrors.StatusError)
- if !ok {
- t.Fatalf("Expecting to get version mismatch as API error")
- }
- if status.Status().Code != http.StatusBadRequest {
- t.Fatalf("expected status code to be %d but was %d", http.StatusBadRequest, status.Status().Code)
- }
- }
- // TestApplyConvertsManagedFieldsVersion checks that the apply
- // converts the API group-version in the field manager
- func TestApplyConvertsManagedFieldsVersion(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"},
- "managedFields": [
- {
- "manager": "sidecar_controller",
- "operation": "Apply",
- "apiVersion": "extensions/v1beta1",
- "fieldsV1": {
- "f:metadata": {
- "f:labels": {
- "f:sidecar_version": {}
- }
- },
- "f:spec": {
- "f:template": {
- "f: spec": {
- "f:containers": {
- "k:{\"name\":\"sidecar\"}": {
- ".": {},
- "f:image": {}
- }
- }
- }
- }
- }
- }
- }
- ]
- },
- "spec": {
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- _, err := client.CoreV1().RESTClient().Post().
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object: %v", err)
- }
- obj = []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"sidecar_version": "release"}
- },
- "spec": {
- "template": {
- "spec": {
- "containers": [{
- "name": "sidecar",
- "image": "sidecar:latest"
- }]
- }
- }
- }
- }`)
- _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Param("fieldManager", "sidecar_controller").
- Body([]byte(obj)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to apply object: %v", err)
- }
- object, err := client.AppsV1().Deployments("default").Get(context.TODO(), "deployment", metav1.GetOptions{})
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- managed := accessor.GetManagedFields()
- if len(managed) != 2 {
- t.Fatalf("Expected 2 field managers, but got managed fields: %v", managed)
- }
- var actual *metav1.ManagedFieldsEntry
- for i := range managed {
- entry := &managed[i]
- if entry.Manager == "sidecar_controller" && entry.APIVersion == "apps/v1" {
- actual = entry
- }
- }
- if actual == nil {
- t.Fatalf("Expected managed fields to contain entry with manager '%v' with converted api version '%v', but got managed fields:\n%v", "sidecar_controller", "apps/v1", managed)
- }
- expected := &metav1.ManagedFieldsEntry{
- Manager: "sidecar_controller",
- Operation: metav1.ManagedFieldsOperationApply,
- APIVersion: "apps/v1",
- Time: actual.Time,
- FieldsType: "FieldsV1",
- FieldsV1: &metav1.FieldsV1{
- Raw: []byte(`{"f:metadata":{"f:labels":{"f:sidecar_version":{}}},"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"sidecar\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`),
- },
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("expected:\n%v\nbut got:\n%v", expected, actual)
- }
- }
- // TestClearManagedFieldsWithMergePatch verifies it's possible to clear the managedFields
- func TestClearManagedFieldsWithMergePatch(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Body([]byte(`{"metadata":{"managedFields": [{}]}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 {
- t.Fatalf("Failed to clear managedFields, got: %v", managedFields)
- }
- }
- // TestClearManagedFieldsWithStrategicMergePatch verifies it's possible to clear the managedFields
- func TestClearManagedFieldsWithStrategicMergePatch(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Body([]byte(`{"metadata":{"managedFields": [{}]}}`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 {
- t.Fatalf("Failed to clear managedFields, got: %v", managedFields)
- }
- if labels := accessor.GetLabels(); len(labels) < 1 {
- t.Fatalf("Expected other fields to stay untouched, got: %v", object)
- }
- }
- // TestClearManagedFieldsWithJSONPatch verifies it's possible to clear the managedFields
- func TestClearManagedFieldsWithJSONPatch(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Patch(types.JSONPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Body([]byte(`[{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}]`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 {
- t.Fatalf("Failed to clear managedFields, got: %v", managedFields)
- }
- }
- // TestClearManagedFieldsWithUpdate verifies it's possible to clear the managedFields
- func TestClearManagedFieldsWithUpdate(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(t)
- defer closeFn()
- _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Param("fieldManager", "apply_test").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).
- Do(context.TODO()).
- Get()
- if err != nil {
- t.Fatalf("Failed to create object using Apply patch: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Put().
- Namespace("default").
- Resource("configmaps").
- Name("test-cm").
- Body([]byte(`{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": {
- "name": "test-cm",
- "namespace": "default",
- "managedFields": [{}],
- "labels": {
- "test-label": "test"
- }
- },
- "data": {
- "key": "value"
- }
- }`)).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to patch object: %v", err)
- }
- object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to retrieve object: %v", err)
- }
- accessor, err := meta.Accessor(object)
- if err != nil {
- t.Fatalf("Failed to get meta accessor: %v", err)
- }
- if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 {
- t.Fatalf("Failed to clear managedFields, got: %v", managedFields)
- }
- if labels := accessor.GetLabels(); len(labels) < 1 {
- t.Fatalf("Expected other fields to stay untouched, got: %v", object)
- }
- }
- var podBytes = []byte(`
- apiVersion: v1
- kind: Pod
- metadata:
- labels:
- app: some-app
- plugin1: some-value
- plugin2: some-value
- plugin3: some-value
- plugin4: some-value
- name: some-name
- namespace: default
- ownerReferences:
- - apiVersion: apps/v1
- blockOwnerDeletion: true
- controller: true
- kind: ReplicaSet
- name: some-name
- uid: 0a9d2b9e-779e-11e7-b422-42010a8001be
- spec:
- containers:
- - args:
- - one
- - two
- - three
- - four
- - five
- - six
- - seven
- - eight
- - nine
- env:
- - name: VAR_3
- valueFrom:
- secretKeyRef:
- key: some-other-key
- name: some-oher-name
- - name: VAR_2
- valueFrom:
- secretKeyRef:
- key: other-key
- name: other-name
- - name: VAR_1
- valueFrom:
- secretKeyRef:
- key: some-key
- name: some-name
- image: some-image-name
- imagePullPolicy: IfNotPresent
- name: some-name
- resources:
- requests:
- cpu: "0"
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- volumeMounts:
- - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
- name: default-token-hu5jz
- readOnly: true
- dnsPolicy: ClusterFirst
- nodeName: node-name
- priority: 0
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- serviceAccount: default
- serviceAccountName: default
- terminationGracePeriodSeconds: 30
- tolerations:
- - effect: NoExecute
- key: node.kubernetes.io/not-ready
- operator: Exists
- tolerationSeconds: 300
- - effect: NoExecute
- key: node.kubernetes.io/unreachable
- operator: Exists
- tolerationSeconds: 300
- volumes:
- - name: default-token-hu5jz
- secret:
- defaultMode: 420
- secretName: default-token-hu5jz
- status:
- conditions:
- - lastProbeTime: null
- lastTransitionTime: "2019-07-08T09:31:18Z"
- status: "True"
- type: Initialized
- - lastProbeTime: null
- lastTransitionTime: "2019-07-08T09:41:59Z"
- status: "True"
- type: Ready
- - lastProbeTime: null
- lastTransitionTime: null
- status: "True"
- type: ContainersReady
- - lastProbeTime: null
- lastTransitionTime: "2019-07-08T09:31:18Z"
- status: "True"
- type: PodScheduled
- containerStatuses:
- - containerID: docker://885e82a1ed0b7356541bb410a0126921ac42439607c09875cd8097dd5d7b5376
- image: some-image-name
- imageID: docker-pullable://some-image-id
- lastState:
- terminated:
- containerID: docker://d57290f9e00fad626b20d2dd87a3cf69bbc22edae07985374f86a8b2b4e39565
- exitCode: 255
- finishedAt: "2019-07-08T09:39:09Z"
- reason: Error
- startedAt: "2019-07-08T09:38:54Z"
- name: name
- ready: true
- restartCount: 6
- state:
- running:
- startedAt: "2019-07-08T09:41:59Z"
- hostIP: 10.0.0.1
- phase: Running
- podIP: 10.0.0.1
- qosClass: BestEffort
- startTime: "2019-07-08T09:31:18Z"
- `)
- func decodePod(podBytes []byte) v1.Pod {
- pod := v1.Pod{}
- err := yaml.Unmarshal(podBytes, &pod)
- if err != nil {
- panic(err)
- }
- return pod
- }
- func encodePod(pod v1.Pod) []byte {
- podBytes, err := yaml.Marshal(pod)
- if err != nil {
- panic(err)
- }
- return podBytes
- }
- func BenchmarkNoServerSideApply(b *testing.B) {
- defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, false)()
- _, client, closeFn := setup(b)
- defer closeFn()
- flag.Lookup("v").Value.Set("0")
- benchAll(b, client, decodePod(podBytes))
- }
- func getPodSizeWhenEnabled(b *testing.B, pod v1.Pod) int {
- return len(getPodBytesWhenEnabled(b, pod, "application/vnd.kubernetes.protobuf"))
- }
- func getPodBytesWhenEnabled(b *testing.B, pod v1.Pod, format string) []byte {
- defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(b)
- defer closeFn()
- flag.Lookup("v").Value.Set("0")
- pod.Name = "size-pod"
- podB, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
- Name(pod.Name).
- Namespace("default").
- Param("fieldManager", "apply_test").
- Resource("pods").
- SetHeader("Accept", format).
- Body(encodePod(pod)).DoRaw(context.TODO())
- if err != nil {
- b.Fatalf("Failed to create object: %#v", err)
- }
- return podB
- }
- func BenchmarkNoServerSideApplyButSameSize(b *testing.B) {
- pod := decodePod(podBytes)
- ssaPodSize := getPodSizeWhenEnabled(b, pod)
- defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, false)()
- _, client, closeFn := setup(b)
- defer closeFn()
- flag.Lookup("v").Value.Set("0")
- pod.Name = "size-pod"
- noSSAPod, err := client.CoreV1().RESTClient().Post().
- Namespace("default").
- Resource("pods").
- SetHeader("Content-Type", "application/yaml").
- SetHeader("Accept", "application/vnd.kubernetes.protobuf").
- Body(encodePod(pod)).DoRaw(context.TODO())
- if err != nil {
- b.Fatalf("Failed to create object: %v", err)
- }
- ssaDiff := ssaPodSize - len(noSSAPod)
- fmt.Printf("Without SSA: %v bytes, With SSA: %v bytes, Difference: %v bytes\n", len(noSSAPod), ssaPodSize, ssaDiff)
- annotations := pod.GetAnnotations()
- builder := strings.Builder{}
- for i := 0; i < ssaDiff; i++ {
- builder.WriteByte('0')
- }
- if annotations == nil {
- annotations = map[string]string{}
- }
- annotations["x-ssa-difference"] = builder.String()
- pod.SetAnnotations(annotations)
- benchAll(b, client, pod)
- }
- func BenchmarkServerSideApply(b *testing.B) {
- podBytesWhenEnabled := getPodBytesWhenEnabled(b, decodePod(podBytes), "application/yaml")
- defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
- _, client, closeFn := setup(b)
- defer closeFn()
- flag.Lookup("v").Value.Set("0")
- benchAll(b, client, decodePod(podBytesWhenEnabled))
- }
- func benchAll(b *testing.B, client kubernetes.Interface, pod v1.Pod) {
- // Make sure pod is ready to post
- pod.ObjectMeta.CreationTimestamp = metav1.Time{}
- pod.ObjectMeta.ResourceVersion = ""
- pod.ObjectMeta.UID = ""
- pod.ObjectMeta.SelfLink = ""
- // Create pod for repeated-updates
- pod.Name = "repeated-pod"
- _, err := client.CoreV1().RESTClient().Post().
- Namespace("default").
- Resource("pods").
- SetHeader("Content-Type", "application/yaml").
- Body(encodePod(pod)).Do(context.TODO()).Get()
- if err != nil {
- b.Fatalf("Failed to create object: %v", err)
- }
- b.Run("List1", benchListPod(client, pod, 1))
- b.Run("List20", benchListPod(client, pod, 20))
- b.Run("List200", benchListPod(client, pod, 200))
- b.Run("List2000", benchListPod(client, pod, 2000))
- b.Run("RepeatedUpdates", benchRepeatedUpdate(client, "repeated-pod"))
- b.Run("Post1", benchPostPod(client, pod, 1))
- b.Run("Post10", benchPostPod(client, pod, 10))
- b.Run("Post50", benchPostPod(client, pod, 50))
- }
- func benchPostPod(client kubernetes.Interface, pod v1.Pod, parallel int) func(*testing.B) {
- return func(b *testing.B) {
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- c := make(chan error)
- for j := 0; j < parallel; j++ {
- j := j
- i := i
- go func(pod v1.Pod) {
- pod.Name = fmt.Sprintf("post%d-%d-%d-%d", parallel, b.N, j, i)
- _, err := client.CoreV1().RESTClient().Post().
- Namespace("default").
- Resource("pods").
- SetHeader("Content-Type", "application/yaml").
- Body(encodePod(pod)).Do(context.TODO()).Get()
- c <- err
- }(pod)
- }
- for j := 0; j < parallel; j++ {
- err := <-c
- if err != nil {
- b.Fatal(err)
- }
- }
- close(c)
- }
- }
- }
- func createNamespace(client kubernetes.Interface, name string) error {
- namespace := v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}
- namespaceBytes, err := yaml.Marshal(namespace)
- if err != nil {
- return fmt.Errorf("Failed to marshal namespace: %v", err)
- }
- _, err = client.CoreV1().RESTClient().Get().
- Resource("namespaces").
- SetHeader("Content-Type", "application/yaml").
- Body(namespaceBytes).Do(context.TODO()).Get()
- if err != nil {
- return fmt.Errorf("Failed to create namespace: %v", err)
- }
- return nil
- }
- func benchListPod(client kubernetes.Interface, pod v1.Pod, num int) func(*testing.B) {
- return func(b *testing.B) {
- namespace := fmt.Sprintf("get-%d-%d", num, b.N)
- if err := createNamespace(client, namespace); err != nil {
- b.Fatal(err)
- }
- // Create pods
- for i := 0; i < num; i++ {
- pod.Name = fmt.Sprintf("get-%d-%d", b.N, i)
- pod.Namespace = namespace
- _, err := client.CoreV1().RESTClient().Post().
- Namespace(namespace).
- Resource("pods").
- SetHeader("Content-Type", "application/yaml").
- Body(encodePod(pod)).Do(context.TODO()).Get()
- if err != nil {
- b.Fatalf("Failed to create object: %v", err)
- }
- }
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _, err := client.CoreV1().RESTClient().Get().
- Namespace(namespace).
- Resource("pods").
- SetHeader("Accept", "application/vnd.kubernetes.protobuf").
- Do(context.TODO()).Get()
- if err != nil {
- b.Fatalf("Failed to patch object: %v", err)
- }
- }
- }
- }
- func benchRepeatedUpdate(client kubernetes.Interface, podName string) func(*testing.B) {
- return func(b *testing.B) {
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _, err := client.CoreV1().RESTClient().Patch(types.JSONPatchType).
- Namespace("default").
- Resource("pods").
- Name(podName).
- Body([]byte(fmt.Sprintf(`[{"op": "replace", "path": "/spec/containers/0/image", "value": "image%d"}]`, i))).Do(context.TODO()).Get()
- if err != nil {
- b.Fatalf("Failed to patch object: %v", err)
- }
- }
- }
- }
|