From 33e95bc185f016affd210744b012e0686ebaa8e8 Mon Sep 17 00:00:00 2001 From: "Dr. Stefan Schimanski" Date: Fri, 24 May 2019 23:28:26 +0200 Subject: [PATCH] apiextensions: add conversion pruning tests --- .../test/integration/conversion/BUILD | 1 + .../integration/conversion/conversion_test.go | 96 ++++++++++++++++++- .../test/integration/storage/objectreader.go | 14 +++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/BUILD b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/BUILD index 2dff89b680f..4468e4bff43 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/BUILD @@ -28,6 +28,7 @@ go_test( "//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/component-base/featuregate/testing:go_default_library", "//vendor/github.com/google/go-cmp/cmp:go_default_library", + "//vendor/k8s.io/utils/pointer:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go index 228b7fdcd14..192f0b9aada 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go @@ -80,7 +80,7 @@ func testWebhookConverter(t *testing.T, pruning bool) { { group: "nontrivial-converter", handler: NewObjectConverterWebhookHandler(t, nontrivialConverter), - checks: checks(validateStorageVersion, validateServed, validateMixedStorageVersions("v1alpha1", "v1beta1", "v1beta2"), validateNonTrivialConverted, validateNonTrivialConvertedList), + checks: checks(validateStorageVersion, validateServed, validateMixedStorageVersions("v1alpha1", "v1beta1", "v1beta2"), validateNonTrivialConverted, validateNonTrivialConvertedList, validateStoragePruning), }, { group: "empty-response", @@ -275,10 +275,24 @@ func validateNonTrivialConverted(t *testing.T, ctc *conversionTestContext) { t.Run(fmt.Sprintf("getting objects created as %s", createVersion.Name), func(t *testing.T) { name := "converted-" + createVersion.Name client := ctc.versionedClient(ns, createVersion.Name) - if _, err := client.Create(newConversionMultiVersionFixture(ns, name, createVersion.Name), metav1.CreateOptions{}); err != nil { + + fixture := newConversionMultiVersionFixture(ns, name, createVersion.Name) + if !*ctc.crd.Spec.PreserveUnknownFields { + if err := unstructured.SetNestedField(fixture.Object, "foo", "garbage"); err != nil { + t.Fatal(err) + } + } + if _, err := client.Create(fixture, metav1.CreateOptions{}); err != nil { t.Fatal(err) } + // verify that the right, pruned version is in storage + obj, err := ctc.etcdObjectReader.GetStoredCustomResource(ns, name) + if err != nil { + t.Fatal(err) + } + verifyMultiVersionObject(t, "v1beta1", obj) + for _, getVersion := range ctc.crd.Spec.Versions { client := ctc.versionedClient(ns, getVersion.Name) obj, err := client.Get(name, metav1.GetOptions{}) @@ -298,7 +312,14 @@ func validateNonTrivialConvertedList(t *testing.T, ctc *conversionTestContext) { for _, createVersion := range ctc.crd.Spec.Versions { name := "converted-" + createVersion.Name client := ctc.versionedClient(ns, createVersion.Name) - if _, err := client.Create(newConversionMultiVersionFixture(ns, name, createVersion.Name), metav1.CreateOptions{}); err != nil { + fixture := newConversionMultiVersionFixture(ns, name, createVersion.Name) + if !*ctc.crd.Spec.PreserveUnknownFields { + if err := unstructured.SetNestedField(fixture.Object, "foo", "garbage"); err != nil { + t.Fatal(err) + } + } + _, err := client.Create(fixture, metav1.CreateOptions{}) + if err != nil { t.Fatal(err) } names.Insert(name) @@ -326,6 +347,67 @@ func validateNonTrivialConvertedList(t *testing.T, ctc *conversionTestContext) { } } +func validateStoragePruning(t *testing.T, ctc *conversionTestContext) { + if *ctc.crd.Spec.PreserveUnknownFields { + return + } + + ns := ctc.namespace + + for _, createVersion := range ctc.crd.Spec.Versions { + t.Run(fmt.Sprintf("getting objects created as %s", createVersion.Name), func(t *testing.T) { + name := "storagepruning-" + createVersion.Name + client := ctc.versionedClient(ns, createVersion.Name) + + fixture := newConversionMultiVersionFixture(ns, name, createVersion.Name) + if err := unstructured.SetNestedField(fixture.Object, "foo", "garbage"); err != nil { + t.Fatal(err) + } + _, err := client.Create(fixture, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // verify that the right, pruned version is in storage + obj, err := ctc.etcdObjectReader.GetStoredCustomResource(ns, name) + if err != nil { + t.Fatal(err) + } + verifyMultiVersionObject(t, "v1beta1", obj) + + // add garbage and set a label + if err := unstructured.SetNestedField(obj.Object, "foo", "garbage"); err != nil { + t.Fatal(err) + } + labels := obj.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels["mutated"] = "true" + obj.SetLabels(labels) + if err := ctc.etcdObjectReader.SetStoredCustomResource(ns, name, obj); err != nil { + t.Fatal(err) + } + + for _, getVersion := range ctc.crd.Spec.Versions { + client := ctc.versionedClient(ns, getVersion.Name) + obj, err := client.Get(name, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // check that the direct mutation in etcd worked + labels := obj.GetLabels() + if labels["mutated"] != "true" { + t.Errorf("expected object %s in version %s to have label 'mutated=true'", name, getVersion.Name) + } + + verifyMultiVersionObject(t, getVersion.Name, obj) + } + }) + } +} + func expectConversionFailureMessage(id, message string) func(t *testing.T, ctc *conversionTestContext) { return func(t *testing.T, ctc *conversionTestContext) { ns := ctc.namespace @@ -763,11 +845,11 @@ func verifyMultiVersionObject(t *testing.T, v string, obj *unstructured.Unstruct } delete(j, "metadata") - delete(j, "apiVersion") - delete(j, "kind") var expected = map[string]map[string]interface{}{ "v1alpha1": { + "apiVersion": "stable.example.com/v1alpha1", + "kind": "MultiVersion", "content": map[string]interface{}{ "key": "value", }, @@ -777,6 +859,8 @@ func verifyMultiVersionObject(t *testing.T, v string, obj *unstructured.Unstruct }, }, "v1beta1": { + "apiVersion": "stable.example.com/v1beta1", + "kind": "MultiVersion", "content": map[string]interface{}{ "key": "value", }, @@ -786,6 +870,8 @@ func verifyMultiVersionObject(t *testing.T, v string, obj *unstructured.Unstruct }, }, "v1beta2": { + "apiVersion": "stable.example.com/v1beta2", + "kind": "MultiVersion", "contentv2": map[string]interface{}{ "key": "value", }, diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/storage/objectreader.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/storage/objectreader.go index a6e21fd6242..d784458ce94 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/storage/objectreader.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/storage/objectreader.go @@ -84,6 +84,20 @@ func (s *EtcdObjectReader) GetStoredCustomResource(ns, name string) (*unstructur return u, nil } +// SetStoredCustomResource writes the storage representation of a custom resource to etcd. +func (s *EtcdObjectReader) SetStoredCustomResource(ns, name string, obj *unstructured.Unstructured) error { + bs, err := obj.MarshalJSON() + if err != nil { + return err + } + + key := path.Join("/", s.storagePrefix, s.crd.Spec.Group, s.crd.Spec.Names.Plural, ns, name) + if _, err := s.etcdClient.KV.Put(context.Background(), key, string(bs)); err != nil { + return fmt.Errorf("error setting storage object %s, %s from etcd at key %s: %v", ns, name, key, err) + } + return nil +} + // GetEtcdClients returns an initialized clientv3.Client and clientv3.KV. func GetEtcdClients(config storagebackend.TransportConfig) (*clientv3.Client, clientv3.KV, error) { tlsInfo := transport.TLSInfo{