Skip to content

Commit

Permalink
Merge pull request #25699 from marun/check-ssa
Browse files Browse the repository at this point in the history
[release-4.6] Add e2e testing of server-side apply for openshift types
  • Loading branch information
openshift-merge-robot committed Dec 11, 2020
2 parents 99ad04e + 89b63be commit 9a9dcb2
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 21 deletions.
174 changes: 174 additions & 0 deletions test/extended/apiserver/apply.go
@@ -0,0 +1,174 @@
package apiserver

import (
"context"
"encoding/json"
"fmt"
"strings"

g "github.com/onsi/ginkgo"
o "github.com/onsi/gomega"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
discocache "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/restmapper"
"k8s.io/kubernetes/test/e2e/framework"

exetcd "github.com/openshift/origin/test/extended/etcd"
exutil "github.com/openshift/origin/test/extended/util"
)

var _ = g.Describe("[sig-api-machinery][Feature:ServerSideApply] Server-Side Apply", func() {
var (
// mapper is used to translate the gvr provided by etcd
// storage data to the gvk required to create correct resource
// yaml.
mapper *restmapper.DeferredDiscoveryRESTMapper
)

defer g.GinkgoRecover()

// Defer project creation to tests that require it by calling
// NewCLIWithoutNamespace instead of NewCLI.
oc := exutil.NewCLIWithoutNamespace("server-side-apply")

g.BeforeEach(func() {
// Only init once per worker
if mapper != nil {
return
}
kubeClient, err := kubernetes.NewForConfig(oc.AdminConfig())
o.Expect(err).NotTo(o.HaveOccurred())
mapper = restmapper.NewDeferredDiscoveryRESTMapper(discocache.NewMemCacheClient(kubeClient.Discovery()))
})
storageData := exetcd.OpenshiftEtcdStorageData
for key := range storageData {
gvr := key
data := storageData[gvr]

// Apply for core types is already well-tested, so skip
// openshift types that are just aliases.
aliasToCoreType := data.ExpectedGVK != nil
if aliasToCoreType {
continue
}

g.It(fmt.Sprintf("should work for %s", gvr), func() {
// Create at most one namespace only if needed.
var testNamespace string

for _, prerequisite := range data.Prerequisites {
// The etcd storage test for oauthclientauthorizations needs to
// manually create a service account secret but that is not
// necessary (or possible) when interacting with an apiserver.
// The service account secret will be created by the controller
// manager.
if gvr.Resource == "oauthclientauthorizations" && prerequisite.GvrData.Resource == "secrets" {
continue
}
resourceClient, unstructuredObj, namespace := createResource(oc, mapper, prerequisite.GvrData, prerequisite.Stub, testNamespace)
testNamespace = namespace
defer deleteResource(resourceClient, unstructuredObj.GetName())
}

resourceClient, unstructuredObj, namespace := createResource(oc, mapper, gvr, data.Stub, testNamespace)
testNamespace = namespace
defer deleteResource(resourceClient, unstructuredObj.GetName())

serializedObj, err := json.Marshal(unstructuredObj.Object)
o.Expect(err).NotTo(o.HaveOccurred())

g.By(fmt.Sprintf("updating the %s via apply", unstructuredObj.GetKind()))
obj, err := resourceClient.Patch(
context.Background(),
unstructuredObj.GetName(),
types.ApplyPatchType,
serializedObj,
metav1.PatchOptions{
FieldManager: "apply_test",
},
)
o.Expect(err).NotTo(o.HaveOccurred())

g.By(fmt.Sprintf("checking that the field managers are as expected"))
accessor, err := meta.Accessor(obj)
o.Expect(err).NotTo(o.HaveOccurred())
managedFields := accessor.GetManagedFields()
o.Expect(managedFields).NotTo(o.BeNil())
if !findManager(managedFields, "create_test") {
g.Fail(fmt.Sprintf("Couldn't find create_test: %v", managedFields))
}
if !findManager(managedFields, "apply_test") {
g.Fail(fmt.Sprintf("Couldn't find apply_test: %v", managedFields))
}
})
}
})

func findManager(managedFields []metav1.ManagedFieldsEntry, manager string) bool {
for _, entry := range managedFields {
if entry.Manager == manager {
return true
}
}
return false
}

func deleteResource(resourceClient dynamic.ResourceInterface, name string) {
err := resourceClient.Delete(context.Background(), name, metav1.DeleteOptions{})
if err != nil {
framework.Logf("Unexpected error deleting resource: %v", err)
}
}

func createResource(
oc *exutil.CLI,
mapper *restmapper.DeferredDiscoveryRESTMapper,
gvr schema.GroupVersionResource,
stub, testNamespace string) (
dynamic.ResourceInterface, *unstructured.Unstructured, string) {

// Discover the gvk from the gvr
gvk, err := mapper.KindFor(gvr)
o.Expect(err).NotTo(o.HaveOccurred())

// Supply a value for namespace if the scope requires
mapping, err := mapper.RESTMapping(gvk.GroupKind())
o.Expect(err).NotTo(o.HaveOccurred())
namespace := ""
if mapping.Scope.Name() == meta.RESTScopeNameNamespace && len(testNamespace) == 0 {
// Create the namespace on demand
namespace = oc.SetupProject()
testNamespace = namespace
}

// Ensure that any stub embedding the etcd test namespace
// is updated to use local test namespace instead.
if len(testNamespace) > 0 {
stub = strings.Replace(stub, exetcd.TestNamespace, testNamespace, -1)
}

// Create unstructured object from stub
unstructuredObj := unstructured.Unstructured{}
err = json.Unmarshal([]byte(stub), &unstructuredObj.Object)
o.Expect(err).NotTo(o.HaveOccurred())
unstructuredObj.SetGroupVersionKind(gvk)

dynamicClient, err := dynamic.NewForConfig(oc.AdminConfig())
o.Expect(err).NotTo(o.HaveOccurred())
resourceClient := dynamicClient.Resource(gvr).Namespace(namespace)

g.By(fmt.Sprintf("creating a %s", gvk.Kind))
_, err = resourceClient.Create(context.Background(), &unstructuredObj, metav1.CreateOptions{
FieldManager: "create_test",
})
o.Expect(err).NotTo(o.HaveOccurred())

return resourceClient, &unstructuredObj, testNamespace
}
38 changes: 19 additions & 19 deletions test/extended/etcd/etcd_storage_path.go
Expand Up @@ -36,8 +36,8 @@ import (
)

// Etcd data for all persisted OpenShift objects.
var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageData{
// github.com/openshift/openshift-apiserver/pkg/authorization/apis/authorization/v1
var OpenshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageData{
// github.com/openshift/api/authorization/v1
gvr("authorization.openshift.io", "v1", "roles"): {
Stub: `{"metadata": {"name": "r1b1o2"}, "rules": [{"verbs": ["create"], "apiGroups": ["authorization.k8s.io"], "resources": ["selfsubjectaccessreviews"]}]}`,
ExpectedEtcdPath: "kubernetes.io/roles/etcdstoragepathtestnamespace/r1b1o2",
Expand All @@ -60,7 +60,7 @@ var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageD
},
// --

// github.com/openshift/openshift-apiserver/pkg/build/apis/build/v1
// github.com/openshift/api/build/v1
gvr("build.openshift.io", "v1", "builds"): {
Stub: `{"metadata": {"name": "build1g"}, "spec": {"source": {"dockerfile": "Dockerfile1"}, "strategy": {"dockerStrategy": {"noCache": true}}}}`,
ExpectedEtcdPath: "openshift.io/builds/etcdstoragepathtestnamespace/build1g",
Expand All @@ -71,14 +71,14 @@ var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageD
},
// --

// github.com/openshift/openshift-apiserver/pkg/apps/apis/apps/v1
// github.com/openshift/api/apps/v1
gvr("apps.openshift.io", "v1", "deploymentconfigs"): {
Stub: `{"metadata": {"name": "dc1g"}, "spec": {"selector": {"d": "c"}, "template": {"metadata": {"labels": {"d": "c"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container2"}]}}}}`,
ExpectedEtcdPath: "openshift.io/deploymentconfigs/etcdstoragepathtestnamespace/dc1g",
},
// --

// github.com/openshift/openshift-apiserver/pkg/image/apis/image/v1
// github.com/openshift/api/image/v1
gvr("image.openshift.io", "v1", "imagestreams"): {
Stub: `{"metadata": {"name": "is1g"}, "spec": {"dockerImageRepository": "docker"}}`,
ExpectedEtcdPath: "openshift.io/imagestreams/etcdstoragepathtestnamespace/is1g",
Expand All @@ -89,7 +89,7 @@ var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageD
},
// --

// github.com/openshift/openshift-apiserver/pkg/oauth/apis/oauth/v1
// github.com/openshift/api/oauth/v1
gvr("oauth.openshift.io", "v1", "oauthclientauthorizations"): {
Stub: `{"clientName": "system:serviceaccount:etcdstoragepathtestnamespace:clientg", "metadata": {"name": "user:system:serviceaccount:etcdstoragepathtestnamespace:clientg"}, "scopes": ["user:info"], "userName": "user", "userUID": "cannot be empty"}`,
ExpectedEtcdPath: "openshift.io/oauth/clientauthorizations/user:system:serviceaccount:etcdstoragepathtestnamespace:clientg",
Expand Down Expand Up @@ -130,29 +130,29 @@ var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageD
},
// --

// github.com/openshift/openshift-apiserver/pkg/project/apis/project/v1
// github.com/openshift/api/project/v1
gvr("project.openshift.io", "v1", "projects"): {
Stub: `{"metadata": {"name": "namespace2g"}, "spec": {"finalizers": ["kubernetes", "openshift.io/origin"]}}`,
ExpectedEtcdPath: "kubernetes.io/namespaces/namespace2g",
ExpectedGVK: gvkP("", "v1", "Namespace"), // project is a proxy for namespace
},
// --

// github.com/openshift/openshift-apiserver/pkg/route/apis/route/v1
// github.com/openshift/api/route/v1
gvr("route.openshift.io", "v1", "routes"): {
Stub: `{"metadata": {"name": "route1g"}, "spec": {"host": "hostname1", "to": {"name": "service1"}}}`,
ExpectedEtcdPath: "openshift.io/routes/etcdstoragepathtestnamespace/route1g",
},
// --

// github.com/openshift/openshift-apiserver/pkg/security/apis/security/v1
// github.com/openshift/api/security/v1
gvr("security.openshift.io", "v1", "rangeallocations"): {
Stub: `{"metadata": {"name": "scc2"}}`,
Stub: `{"metadata": {"name": "scc2"}, "range": "", "data": ""}`,
ExpectedEtcdPath: "openshift.io/rangeallocations/scc2",
},
// --

// github.com/openshift/openshift-apiserver/pkg/template/apis/template/v1
// github.com/openshift/api/template/v1
gvr("template.openshift.io", "v1", "templates"): {
Stub: `{"message": "Jenkins template", "metadata": {"name": "template1g"}}`,
ExpectedEtcdPath: "openshift.io/templates/etcdstoragepathtestnamespace/template1g",
Expand All @@ -167,7 +167,7 @@ var openshiftEtcdStorageData = map[schema.GroupVersionResource]etcddata.StorageD
},
// --

// github.com/openshift/openshift-apiserver/pkg/user/apis/user/v1
// github.com/openshift/api/user/v1
gvr("user.openshift.io", "v1", "groups"): {
Stub: `{"metadata": {"name": "groupg"}, "users": ["user1", "user2"]}`,
ExpectedEtcdPath: "openshift.io/groups/groupg",
Expand Down Expand Up @@ -197,7 +197,7 @@ var kindWhiteList = sets.NewString(
)

// namespace used for all tests, do not change this
const testNamespace = "etcdstoragepathtestnamespace"
const TestNamespace = "etcdstoragepathtestnamespace"

type helperT struct {
g.GinkgoTInterface
Expand Down Expand Up @@ -259,16 +259,16 @@ func testEtcd3StoragePath(t g.GinkgoTInterface, kubeConfig *restclient.Config, e

client := &allClient{dynamicClient: dynamic.NewForConfigOrDie(kubeConfig)}

if _, err := kubeClient.CoreV1().Namespaces().Create(context.Background(), &kapiv1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}, metav1.CreateOptions{}); err != nil {
if _, err := kubeClient.CoreV1().Namespaces().Create(context.Background(), &kapiv1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: TestNamespace}}, metav1.CreateOptions{}); err != nil {
t.Fatalf("error creating test namespace: %#v", err)
}
defer func() {
if err := kubeClient.CoreV1().Namespaces().Delete(context.Background(), testNamespace, metav1.DeleteOptions{}); err != nil {
if err := kubeClient.CoreV1().Namespaces().Delete(context.Background(), TestNamespace, metav1.DeleteOptions{}); err != nil {
t.Fatalf("error deleting test namespace: %#v", err)
}
}()

if err := exutil.WaitForServiceAccount(kubeClient.CoreV1().ServiceAccounts(testNamespace), "default"); err != nil {
if err := exutil.WaitForServiceAccount(kubeClient.CoreV1().ServiceAccounts(TestNamespace), "default"); err != nil {
t.Fatalf("error waiting for the default service account: %v", err)
}

Expand Down Expand Up @@ -346,7 +346,7 @@ func testEtcd3StoragePath(t g.GinkgoTInterface, kubeConfig *restclient.Config, e
}

// add openshift specific data
for gvr, data := range openshiftEtcdStorageData {
for gvr, data := range OpenshiftEtcdStorageData {
if _, ok := etcdStorageData[gvr]; ok {
t.Errorf("%s exists in both Kube and OpenShift ETCD data, data=%#v", gvr.String(), data)
}
Expand Down Expand Up @@ -424,13 +424,13 @@ func testEtcd3StoragePath(t g.GinkgoTInterface, kubeConfig *restclient.Config, e
}
}()

if err := client.createPrerequisites(mapper, testNamespace, testData.Prerequisites, all); err != nil {
if err := client.createPrerequisites(mapper, TestNamespace, testData.Prerequisites, all); err != nil {
t.Errorf("failed to create prerequisites for %v: %#v", gvk, err)
return
}

if shouldCreate { // do not try to create items with no stub
if err := client.create(testData.Stub, testNamespace, mapping, all); err != nil {
if err := client.create(testData.Stub, TestNamespace, mapping, all); err != nil {
t.Errorf("failed to create stub for %v: %#v", gvk, err)
return
}
Expand Down
34 changes: 34 additions & 0 deletions test/extended/util/annotate/generated/zz_generated.annotations.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions test/extended/util/client.go
Expand Up @@ -108,7 +108,7 @@ func NewCLIWithFramework(kubeFramework *framework.Framework) *CLI {
func NewCLI(project string) *CLI {
cli := NewCLIWithoutNamespace(project)
// create our own project
g.BeforeEach(cli.SetupProject)
g.BeforeEach(func() { _ = cli.SetupProject() })
return cli
}

Expand Down Expand Up @@ -198,7 +198,8 @@ func (c CLI) WithoutNamespace() *CLI {

// SetupProject creates a new project and assign a random user to the project.
// All resources will be then created within this project.
func (c *CLI) SetupProject() {
// Returns the name of the new project.
func (c *CLI) SetupProject() string {
requiresTestStart()
newNamespace := names.SimpleNameGenerator.GenerateName(fmt.Sprintf("e2e-test-%s-", c.kubeFramework.BaseName))
c.SetNamespace(newNamespace).ChangeUser(fmt.Sprintf("%s-user", newNamespace))
Expand Down Expand Up @@ -279,6 +280,7 @@ func (c *CLI) SetupProject() {
WaitForNamespaceSCCAnnotations(c.ProjectClient().ProjectV1(), newNamespace)

framework.Logf("Project %q has been fully provisioned.", newNamespace)
return newNamespace
}

func (c *CLI) setupSelfProvisionerRoleBinding() error {
Expand Down

0 comments on commit 9a9dcb2

Please sign in to comment.