From f2deb2417a6c542c54606ab17376b26ef1552b87 Mon Sep 17 00:00:00 2001 From: "Julian V. Modesto" Date: Thu, 16 Jul 2020 17:14:35 -0400 Subject: [PATCH] Add tests for managed fields tracking. - Test that client-side apply users don't encounter a conflict with server-side apply for objects that previously didn't track managedFields - Test that we stop tracking managed fields with `managedFields: []` - Test that we stop tracking managed fields when the feature is disabled --- .../fieldmanager/fieldmanager_test.go | 140 ++++++++++++++++++ .../integration/apiserver/apply/apply_test.go | 112 +++++++++++++- 2 files changed, 251 insertions(+), 1 deletion(-) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index 182b8fcfe9a..588b2c3bf14 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -111,6 +111,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind, chainFieldManager func(fie f = fieldmanager.NewStripMetaManager(f) f = fieldmanager.NewManagedFieldsUpdater(f) f = fieldmanager.NewBuildManagerInfoManager(f, gvk.GroupVersion()) + f = fieldmanager.NewProbabilisticSkipNonAppliedManager(f, &fakeObjectCreater{gvk: gvk}, gvk, fieldmanager.DefaultTrackOnCreateProbability) f = fieldmanager.NewLastAppliedManager(f, typeConverter, objectConverter, gvk.GroupVersion()) f = fieldmanager.NewLastAppliedUpdater(f) if chainFieldManager != nil { @@ -1039,6 +1040,145 @@ spec: } } +func TestNoTrackManagedFieldsForClientSideApply(t *testing.T) { + f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + // create object + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Update(newObj, "test_kubectl_create"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) == 0 { + t.Errorf("expected to have managed fields, but got: %v", m) + } + + // stop tracking managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + managedFields: [] # stop tracking managed fields + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + newObj.SetUID("nonempty") + if err := f.Update(newObj, "test_kubectl_replace"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) != 0 { + t.Errorf("expected to have stop tracking managed fields, but got: %v", m) + } + + // check that we still don't track managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := setLastAppliedFromEncoded(newObj, deployment); err != nil { + t.Errorf("failed to set last applied: %v", err) + } + if err := f.Update(newObj, "test_k_client_side_apply"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) != 0 { + t.Errorf("expected to continue to not track managed fields, but got: %v", m) + } + lastApplied, err := getLastApplied(f.liveObj) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-app") { + t.Errorf("expected last applied annotation to be set properly, but got: %q", lastApplied) + } + + // start tracking managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Apply(newObj, "test_server_side_apply_without_upgrade", false); err != nil { + t.Errorf("error applying object: %v", err) + } + if m := f.ManagedFields(); len(m) < 2 { + t.Errorf("expected to start tracking managed fields with at least 2 field managers, but got: %v", m) + } + if e, a := "test_server_side_apply_without_upgrade", f.ManagedFields()[0].Manager; e != a { + t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + if e, a := "before-first-apply", f.ManagedFields()[1].Manager; e != a { + t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + // upgrade management of the object from client-side apply to server-side apply + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app-v2 # change +spec: + replicas: 8 # change +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Apply(newObj, "kubectl", false); err != nil { + t.Errorf("error applying object: %v", err) + } + if m := f.ManagedFields(); len(m) == 0 { + t.Errorf("expected to track managed fields, but got: %v", m) + } + lastApplied, err = getLastApplied(f.liveObj) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-app-v2") { + t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) + } +} + func yamlToJSON(y []byte) (string, error) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(y, &obj.Object); err != nil { diff --git a/test/integration/apiserver/apply/apply_test.go b/test/integration/apiserver/apply/apply_test.go index 1b83a3fb4ac..f86b87d7944 100644 --- a/test/integration/apiserver/apply/apply_test.go +++ b/test/integration/apiserver/apply/apply_test.go @@ -1635,6 +1635,16 @@ func TestClearManagedFieldsWithUpdateEmptyList(t *testing.T) { t.Fatalf("Failed to patch object: %v", err) } + _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). + Namespace("default"). + Resource("configmaps"). + Name("test-cm"). + Body([]byte(`{"metadata":{"labels": { "test-label": "v1" }}}`)).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) @@ -1646,7 +1656,7 @@ func TestClearManagedFieldsWithUpdateEmptyList(t *testing.T) { } if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { - t.Fatalf("Failed to clear managedFields, got: %v", managedFields) + t.Fatalf("Failed to stop tracking managedFields, got: %v", managedFields) } if labels := accessor.GetLabels(); len(labels) < 1 { @@ -2595,3 +2605,103 @@ spec: t.Fatalf("expected to get obj with image %s, but got %s", "my-image-new", deploymentObj.Spec.Template.Spec.Containers[0].Image) } } + +func TestStopTrackingManagedFieldsOnFeatureDisabled(t *testing.T) { + sharedEtcd := framework.DefaultEtcdOptions() + masterConfig := framework.NewIntegrationTestMasterConfigWithOptions(&framework.MasterConfigOptions{ + EtcdOptions: sharedEtcd, + }) + masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() + + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + _, master, closeFn := framework.RunAMaster(masterConfig) + client, err := clientset.NewForConfig(&restclient.Config{Host: master.URL, QPS: -1}) + if err != nil { + t.Fatalf("Error in create clientset: %v", err) + } + + obj := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`) + + deployment, err := yamlutil.ToJSON(obj) + if err != nil { + t.Fatalf("Failed marshal yaml: %v", err) + } + _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("my-deployment"). + Param("fieldManager", "kubectl"). + Body(deployment). + Do(context.TODO()). + Get() + if err != nil { + t.Fatalf("Failed to apply object: %v", err) + } + + deploymentObj, err := client.AppsV1().Deployments("default").Get(context.TODO(), "my-deployment", metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get object: %v", err) + } + if managed := deploymentObj.GetManagedFields(); managed == nil { + t.Errorf("object doesn't have managedFields") + } + + // Restart server with server-side apply disabled + closeFn() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, false)() + _, master, closeFn = framework.RunAMaster(masterConfig) + client, err = clientset.NewForConfig(&restclient.Config{Host: master.URL, QPS: -1}) + if err != nil { + t.Fatalf("Error in create clientset: %v", err) + } + defer closeFn() + + _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("my-deployment"). + Param("fieldManager", "kubectl"). + Body(deployment). + Do(context.TODO()). + Get() + if err == nil { + t.Errorf("expected to fail to apply object, but succeeded") + } + + _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("my-deployment"). + Body([]byte(`{"metadata":{"labels": { "app": "v1" }}}`)).Do(context.TODO()).Get() + if err != nil { + t.Errorf("failed to update object: %v", err) + } + + deploymentObj, err = client.AppsV1().Deployments("default").Get(context.TODO(), "my-deployment", metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get object: %v", err) + } + if managed := deploymentObj.GetManagedFields(); managed != nil { + t.Errorf("object has unexpected managedFields: %v", managed) + } +}