diff --git a/doc/test-framework/writing-e2e-tests.md b/doc/test-framework/writing-e2e-tests.md index 411e59b66eb..3de7046167c 100644 --- a/doc/test-framework/writing-e2e-tests.md +++ b/doc/test-framework/writing-e2e-tests.md @@ -106,7 +106,7 @@ defer ctx.Cleanup() ``` Now that there is a `TestCtx`, the test's Kubernetes resources (specifically the test namespace, -Service Account, RBAC, and Operator deployment in `local` testing; just the Operator deployment +Service Account, Role, Role Binding, and Operator deployment in `local` testing; just the Operator deployment in `cluster` testing) can be initialized: ```go @@ -226,9 +226,9 @@ functions will automatically be run since they were deferred when the TestCtx wa ## Running the Tests To make running the tests simpler, the `operator-sdk` CLI tool has a `test` subcommand that can configure -default test settings, such as locations of your global resource manifest file (by default -`deploy/crd.yaml`) and your namespaced resource manifest file (by default `deploy/service_account.yaml` concatenated with -`deploy/rbac.yaml` and `deploy/operator.yaml`), and allows the user to configure runtime options. There are 2 ways to use the +default test settings, such as locations of your global resource manifest file (by default all CRDs in +`deploy/crds`) and your namespaced resource manifest file (by default `deploy/service_account.yaml` concatenated with +`deploy/role.yaml`, `deploy/role_binding.yaml`, and `deploy/operator.yaml`), and allows the user to configure runtime options. There are 2 ways to use the subcommand: local and cluster. ### Local @@ -295,7 +295,9 @@ will result in undefined behavior. This is an example `go test` equivalent to th # Combine service_account, rbac, operator manifest into namespaced manifest $ cp deploy/service_account.yaml deploy/namespace-init.yaml $ echo -e "\n---\n" >> deploy/namespace-init.yaml -$ cat deploy/rbac.yaml >> deploy/namespace-init.yaml +$ cat deploy/role.yaml >> deploy/namespace-init.yaml +$ echo -e "\n---\n" >> deploy/namespace-init.yaml +$ cat deploy/role_binding.yaml >> deploy/namespace-init.yaml $ echo -e "\n---\n" >> deploy/namespace-init.yaml $ cat deploy/operator.yaml >> deploy/namespace-init.yaml # Run tests @@ -352,24 +354,24 @@ in your cluster. You can do this with `kubectl`: $ kubectl get namespaces Example Output: -NAME STATUS AGE -default Active 2h -kube-public Active 2h -kube-system Active 2h -main-1534287036 Active 23s -memcached-memcached-group-cluster-1534287037 Active 22s -memcached-memcached-group-cluster2-1534287037 Active 22s +NAME STATUS AGE +default Active 2h +kube-public Active 2h +kube-system Active 2h +memcached-memcached-group-cluster-1552500058464380681 Active 5s +memcached-memcached-group-cluster2-1552500058464409898 Active 5s +operator-sdk-1552500057336429125 Active 6s ``` -The names of the namespaces will be either start with `main` or with the name of the tests and the suffix will -be a Unix timestamp (number of seconds since January 1, 1970 00:00 UTC). Kubectl can be used to delete these +The names of the namespaces will be either start with `operator-sdk` or with the name of the tests and the suffix will +be a Unix nanosecond timestamp (number of nanoseconds since January 1, 1970 00:00 UTC). Kubectl can be used to delete these namespaces and the resources in those namespaces: ```shell -$ kubectl delete namespace main-153428703 +$ kubectl delete namespace operator-sdk-1552500057336429125 ``` -Since the CRD is not namespaced, it must be deleted separately. Clean up the CRD created by the tests using the CRD manifest `deploy/crd.yaml`: +Since the CRD is not namespaced, it must be deleted separately. Clean up the CRD created by the tests using the CRD manifest(s) in `deploy/crds`: ```shell $ kubectl delete -f deploy/crds/cache_v1alpha1_memcached_crd.yaml diff --git a/pkg/test/client.go b/pkg/test/client.go index 931dce46240..339e4ac1fd3 100644 --- a/pkg/test/client.go +++ b/pkg/test/client.go @@ -30,6 +30,8 @@ type frameworkClient struct { var _ FrameworkClient = &frameworkClient{} +// FrameworkClient is a wrapper for the controller-runtime client with a modified Create function +// that automatically adds a cleanup function for the created resource. type FrameworkClient interface { Get(gCtx goctx.Context, key dynclient.ObjectKey, obj runtime.Object) error List(gCtx goctx.Context, opts *dynclient.ListOptions, list runtime.Object) error @@ -89,18 +91,22 @@ func (f *frameworkClient) Create(gCtx goctx.Context, obj runtime.Object, cleanup return nil } +// Get is a simple wrapper for the controller-runtime client's Get function. func (f *frameworkClient) Get(gCtx goctx.Context, key dynclient.ObjectKey, obj runtime.Object) error { return f.Client.Get(gCtx, key, obj) } +// List is a simple wrapper for the controller-runtime client's List function. func (f *frameworkClient) List(gCtx goctx.Context, opts *dynclient.ListOptions, list runtime.Object) error { return f.Client.List(gCtx, opts, list) } +// Delete is a simple wrapper for the controller-runtime client's Delete function. func (f *frameworkClient) Delete(gCtx goctx.Context, obj runtime.Object, opts ...dynclient.DeleteOptionFunc) error { return f.Client.Delete(gCtx, obj, opts...) } +// Update is a simple wrapper for the controller-runtime client's Update function. func (f *frameworkClient) Update(gCtx goctx.Context, obj runtime.Object) error { return f.Client.Update(gCtx, obj) } diff --git a/pkg/test/context.go b/pkg/test/context.go index 9427faf2457..5991c4729db 100644 --- a/pkg/test/context.go +++ b/pkg/test/context.go @@ -23,6 +23,7 @@ import ( log "github.com/sirupsen/logrus" ) +// TestCtx contains the state of a test, which includes ID, namespace, and cleanup functions. type TestCtx struct { id string cleanupFns []cleanupFn @@ -30,6 +31,7 @@ type TestCtx struct { t *testing.T } +// CleanupOptions allows for configuration of resource cleanup functions. type CleanupOptions struct { TestContext *TestCtx Timeout time.Duration @@ -38,9 +40,11 @@ type CleanupOptions struct { type cleanupFn func() error +// NewTestCtx returns a new TestCtx object. func NewTestCtx(t *testing.T) *TestCtx { var prefix string if t != nil { + // Use the name of the test as the prefix // TestCtx is used among others for namespace names where '/' is forbidden prefix = strings.TrimPrefix( strings.Replace( @@ -52,26 +56,27 @@ func NewTestCtx(t *testing.T) *TestCtx { "test", ) } else { - prefix = "main" + prefix = "operator-sdk" } - id := prefix + "-" + strconv.FormatInt(time.Now().Unix(), 10) + // add a creation timestamp to the ID + id := prefix + "-" + strconv.FormatInt(time.Now().UnixNano(), 10) return &TestCtx{ id: id, t: t, } } +// GetID returns the ID of the TestCtx. func (ctx *TestCtx) GetID() string { return ctx.id } +// Cleanup runs all the TestCtx's cleanup function in reverse order of their insertion. func (ctx *TestCtx) Cleanup() { - failed := false for i := len(ctx.cleanupFns) - 1; i >= 0; i-- { err := ctx.cleanupFns[i]() if err != nil { - failed = true if ctx.t != nil { ctx.t.Errorf("A cleanup function failed with error: (%v)\n", err) } else { @@ -79,11 +84,9 @@ func (ctx *TestCtx) Cleanup() { } } } - if ctx.t == nil && failed { - log.Fatal("A cleanup function failed") - } } +// AddCleanupFn adds a new cleanup function to the TestCtx. func (ctx *TestCtx) AddCleanupFn(fn cleanupFn) { ctx.cleanupFns = append(ctx.cleanupFns, fn) } diff --git a/pkg/test/e2eutil/wait_util.go b/pkg/test/e2eutil/wait_util.go index 9b155bd930b..7d98032364c 100644 --- a/pkg/test/e2eutil/wait_util.go +++ b/pkg/test/e2eutil/wait_util.go @@ -38,7 +38,7 @@ func WaitForDeployment(t *testing.T, kubeclient kubernetes.Interface, namespace, } // WaitForOperatorDeployment has the same functionality as WaitForDeployment but will no wait for the deployment if the -// test was run with a locally run operator (--up-local flag) +// test was run with a locally run operator (--up-local flag). func WaitForOperatorDeployment(t *testing.T, kubeclient kubernetes.Interface, namespace, name string, replicas int, retryInterval, timeout time.Duration) error { return waitForDeployment(t, kubeclient, namespace, name, replicas, retryInterval, timeout, true) } @@ -71,6 +71,7 @@ func waitForDeployment(t *testing.T, kubeclient kubernetes.Interface, namespace, return nil } +// WaitForDeletion waits for a given object to be fully deleted according to the apiserver before returning. func WaitForDeletion(t *testing.T, dynclient client.Client, obj runtime.Object, retryInterval, timeout time.Duration) error { key, err := client.ObjectKeyFromObject(obj) if err != nil { diff --git a/pkg/test/framework.go b/pkg/test/framework.go index fb5c9793c18..0cb796cf87c 100755 --- a/pkg/test/framework.go +++ b/pkg/test/framework.go @@ -40,36 +40,45 @@ import ( ) var ( - // Global framework struct + // Global is a global framework struct that the test can use. Global *Framework // mutex for AddToFrameworkScheme mutex = sync.Mutex{} - // whether to run tests in a single namespace - singleNamespace *bool + // singleNamespaceInternal determines whether tests are to be run in a single namespace or not. + singleNamespaceInternal bool // decoder used by createFromYaml dynamicDecoder runtime.Decoder // restMapper for the dynamic client restMapper *restmapper.DeferredDiscoveryRESTMapper ) +// Framework contains all relevant variables needed for running tests with the operator-sdk. type Framework struct { Client *frameworkClient KubeConfig *rest.Config KubeClient kubernetes.Interface Scheme *runtime.Scheme - NamespacedManPath *string + NamespacedManPath string Namespace string LocalOperator bool } -func setup(kubeconfigPath, namespacedManPath *string, localOperator bool) error { - namespace := "" - if *singleNamespace { - namespace = os.Getenv(TestNamespaceEnv) +// Setup initializes the Global.Framework variable and its fields. +func Setup(kubeconfigPath, namespacedManPath, namespace string, singleNamespace, localOperator bool) error { + if namespace != "" && !singleNamespace { + return fmt.Errorf("oneNamespace must be set to true if namespace is set") } + singleNamespaceInternal = singleNamespace var err error var kubeconfig *rest.Config - if *kubeconfigPath == "incluster" { + if kubeconfigPath == "incluster" { + // when running with an InCluster config, we don't have permission to create new namespaces, so we must be in single namespace mode + if singleNamespaceInternal != true { + return fmt.Errorf("singleNamespace must be set to true for in cluster testing mode") + } + if len(namespace) == 0 { + return fmt.Errorf("namespace must be set for in cluster testing mode") + } // Work around https://github.com/kubernetes/kubernetes/issues/40973 if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 { addrs, err := net.LookupHost("kubernetes.default.svc") @@ -86,15 +95,10 @@ func setup(kubeconfigPath, namespacedManPath *string, localOperator bool) error } } kubeconfig, err = rest.InClusterConfig() - *singleNamespace = true - namespace = os.Getenv(TestNamespaceEnv) - if len(namespace) == 0 { - return fmt.Errorf("test namespace env not set") - } } else { var kcNamespace string - kubeconfig, kcNamespace, err = k8sInternal.GetKubeconfigAndNamespace(*kubeconfigPath) - if *singleNamespace && namespace == "" { + kubeconfig, kcNamespace, err = k8sInternal.GetKubeconfigAndNamespace(kubeconfigPath) + if singleNamespaceInternal && namespace == "" { namespace = kcNamespace } } @@ -147,34 +151,29 @@ type addToSchemeFunc func(*runtime.Scheme) error // } // The List object is needed because the CRD has not always been fully registered // by the time this function is called. If the CRD takes more than 5 seconds to -// become ready, this function throws an error +// become ready, this function throws an error. func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error { mutex.Lock() defer mutex.Unlock() err := addToScheme(Global.Scheme) if err != nil { - return err + return fmt.Errorf("failed to update global scheme: %v", err) } restMapper.Reset() - dynClient, err := dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: restMapper}) - if err != nil { - return fmt.Errorf("failed to initialize new dynamic client: (%v)", err) - } err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) { - if *singleNamespace { - err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj) + if singleNamespaceInternal { + err = Global.Client.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj) } else { - err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) + err = Global.Client.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) } if err != nil { restMapper.Reset() return false, nil } - Global.Client = &frameworkClient{Client: dynClient} return true, nil }) if err != nil { - return fmt.Errorf("failed to build the dynamic client: %v", err) + return fmt.Errorf("failed to update the client restmapper: %v", err) } dynamicDecoder = serializer.NewCodecFactory(Global.Scheme).UniversalDeserializer() return nil diff --git a/pkg/test/main_entry.go b/pkg/test/main_entry.go index 940f45f57f7..1b4aa3f6757 100644 --- a/pkg/test/main_entry.go +++ b/pkg/test/main_entry.go @@ -44,20 +44,28 @@ const ( LocalOperatorFlag = "localOperator" ) +// MainEntry parses the flags set by the operator-sdk test command, configures the testing environment, and then +// runs the tests. func MainEntry(m *testing.M) { projRoot := flag.String(ProjRootFlag, "", "path to project root") kubeconfigPath := flag.String(KubeConfigFlag, "", "path to kubeconfig") globalManPath := flag.String(GlobalManPathFlag, "", "path to operator manifest") namespacedManPath := flag.String(NamespacedManPathFlag, "", "path to rbac manifest") - singleNamespace = flag.Bool(SingleNamespaceFlag, false, "enable single namespace mode") localOperator := flag.Bool(LocalOperatorFlag, false, "enable if operator is running locally (not in cluster)") + flag.BoolVar(&singleNamespaceInternal, SingleNamespaceFlag, false, "enable single namespace mode") flag.Parse() // go test always runs from the test directory; change to project root err := os.Chdir(*projRoot) if err != nil { log.Fatalf("Failed to change directory to project root: %v", err) } - if err := setup(kubeconfigPath, namespacedManPath, *localOperator); err != nil { + namespace := "" + if singleNamespaceInternal || *kubeconfigPath == "incluster" { + namespace = os.Getenv(TestNamespaceEnv) + // if kubeconfig is set to incluster, make sure single namespace mode is set + singleNamespaceInternal = true + } + if err := Setup(*kubeconfigPath, *namespacedManPath, namespace, singleNamespaceInternal, *localOperator); err != nil { log.Fatalf("Failed to set up framework: %v", err) } // setup local operator command, but don't start it yet @@ -126,7 +134,7 @@ func MainEntry(m *testing.M) { if err != nil { log.Fatalf("Failed to read global resource manifest: %v", err) } - err = ctx.createFromYAML(globalYAML, true, &CleanupOptions{TestContext: ctx}) + err = ctx.CreateFromYAML(globalYAML, true, &CleanupOptions{TestContext: ctx}) if err != nil { log.Fatalf("Failed to create resource(s) in global resource manifest: %v", err) } diff --git a/pkg/test/resource_creator.go b/pkg/test/resource_creator.go index bf840145405..484e0c0b37b 100644 --- a/pkg/test/resource_creator.go +++ b/pkg/test/resource_creator.go @@ -29,11 +29,13 @@ import ( "k8s.io/apimachinery/pkg/util/wait" ) +// GetNamespace returns the namespace for the current context, creating a new namespace +// if one does not exist using the context's ID as the new namespace's name. func (ctx *TestCtx) GetNamespace() (string, error) { if ctx.namespace != "" { return ctx.namespace, nil } - if *singleNamespace { + if singleNamespaceInternal { ctx.namespace = Global.Namespace return ctx.namespace, nil } @@ -52,7 +54,28 @@ func (ctx *TestCtx) GetNamespace() (string, error) { return ctx.namespace, nil } -func (ctx *TestCtx) createFromYAML(yamlFile []byte, skipIfExists bool, cleanupOptions *CleanupOptions) error { +// SetNamespace sets a static namespace for the current context. If the specifed namespace does not exist, +// it will be created and a cleanup function for the new namespace will be added to the context. +func (ctx *TestCtx) SetNamespace(namespace string) error { + ctx.namespace = namespace + namespaceObj := &core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ctx.namespace}} + _, err := Global.KubeClient.CoreV1().Namespaces().Create(namespaceObj) + // do not add a cleanup function if the namespace already exists + if apierrors.IsAlreadyExists(err) { + return nil + } + if err != nil { + return err + } + ctx.AddCleanupFn(func() error { + return Global.KubeClient.CoreV1().Namespaces().Delete(ctx.namespace, metav1.NewDeleteOptions(0)) + }) + return nil +} + +// CreateFromYAML takes a raw YAML file and creates the resource(s) in the cluster and adds cleanup functions +// for the created resource(s). +func (ctx *TestCtx) CreateFromYAML(yamlFile []byte, skipIfExists bool, cleanupOptions *CleanupOptions) error { namespace, err := ctx.GetNamespace() if err != nil { return err @@ -105,11 +128,13 @@ func (ctx *TestCtx) createFromYAML(yamlFile []byte, skipIfExists bool, cleanupOp return nil } +// InitializeClusterResources creates all resources in the namespaced manifest file +// using the CreateFromYAML function. func (ctx *TestCtx) InitializeClusterResources(cleanupOptions *CleanupOptions) error { // create namespaced resources - namespacedYAML, err := ioutil.ReadFile(*Global.NamespacedManPath) + namespacedYAML, err := ioutil.ReadFile(Global.NamespacedManPath) if err != nil { return fmt.Errorf("failed to read namespaced manifest: %v", err) } - return ctx.createFromYAML(namespacedYAML, false, cleanupOptions) + return ctx.CreateFromYAML(namespacedYAML, false, cleanupOptions) } diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 92324e030d6..468c3ecfc96 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -28,7 +28,6 @@ import ( "testing" "time" - "github.com/ghodss/yaml" "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" "github.com/operator-framework/operator-sdk/internal/util/fileutil" "github.com/operator-framework/operator-sdk/internal/util/projutil" @@ -36,8 +35,9 @@ import ( framework "github.com/operator-framework/operator-sdk/pkg/test" "github.com/operator-framework/operator-sdk/pkg/test/e2eutil" + "github.com/ghodss/yaml" "github.com/prometheus/prometheus/util/promlint" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" @@ -254,13 +254,13 @@ func TestMemcached(t *testing.T) { if err != nil { t.Fatal(err) } - // hacky way to use createFromYAML without exposing the method - // create crd - filename := file.Name() - framework.Global.NamespacedManPath = &filename - err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + globalYAML, err := ioutil.ReadFile(file.Name()) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to read global resource manifest: %v", err) + } + err = ctx.CreateFromYAML(globalYAML, false, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + t.Fatalf("Failed to create global resources: %v", err) } t.Log("Created global resources") @@ -333,7 +333,7 @@ func verifyLeader(t *testing.T, namespace string, f *framework.Framework, labels return true, nil }) if err != nil { - return nil, fmt.Errorf("error getting leader lock configmap: %v\n", err) + return nil, fmt.Errorf("error getting leader lock configmap: %v", err) } t.Logf("Found leader lock configmap %s\n", lockName) @@ -372,21 +372,8 @@ func verifyLeader(t *testing.T, namespace string, f *framework.Framework, labels } func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error { - // create example-memcached yaml file - filename := "deploy/cr.yaml" - err := ioutil.WriteFile(filename, - []byte(crYAML), - fileutil.DefaultFileMode) - if err != nil { - return err - } - // create memcached custom resource - framework.Global.NamespacedManPath = &filename - err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) - if err != nil { - return err - } + err := ctx.CreateFromYAML([]byte(crYAML), false, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) t.Log("Created cr") namespace, err := ctx.GetNamespace() @@ -521,11 +508,13 @@ func MemcachedCluster(t *testing.T) { t.Fatal(err) } // create namespaced resources - filename := file.Name() - framework.Global.NamespacedManPath = &filename - err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + namespacedYAML, err := ioutil.ReadFile(file.Name()) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to read namespaced resource manifest: %v", err) + } + err = ctx.CreateFromYAML(namespacedYAML, false, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + t.Fatalf("Failed to create namespaced resources: %v", err) } t.Log("Created namespaced resources") @@ -556,30 +545,38 @@ func MemcachedClusterTest(t *testing.T) { // get global framework variables ctx := framework.NewTestCtx(t) defer ctx.Cleanup() + cleanupOptions := &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval} - // create sa - filename := "deploy/service_account.yaml" - framework.Global.NamespacedManPath = &filename - err := ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + // create service account + serviceAccountYAML, err := ioutil.ReadFile("deploy/service_account.yaml") if err != nil { - t.Fatal(err) + t.Fatalf("Failed to read service account manifest: %v", err) + } + err = ctx.CreateFromYAML(serviceAccountYAML, false, cleanupOptions) + if err != nil { + t.Fatalf("Failed to create service account: %v", err) } - t.Log("Created sa") + t.Log("Created service account") - // create rbac - filename = "deploy/role.yaml" - framework.Global.NamespacedManPath = &filename - err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + // create role + roleYAML, err := ioutil.ReadFile("deploy/role.yaml") if err != nil { - t.Fatal(err) + t.Fatalf("Failed to read role manifest: %v", err) + } + err = ctx.CreateFromYAML(roleYAML, false, cleanupOptions) + if err != nil { + t.Fatalf("Failed to create role: %v", err) } t.Log("Created role") - filename = "deploy/role_binding.yaml" - framework.Global.NamespacedManPath = &filename - err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + // create role binding + roleBindingYAML, err := ioutil.ReadFile("deploy/role_binding.yaml") if err != nil { - t.Fatal(err) + t.Fatalf("Failed to read role binding manifest: %v", err) + } + err = ctx.CreateFromYAML(roleBindingYAML, false, cleanupOptions) + if err != nil { + t.Fatalf("Failed to create role binding: %v", err) } t.Log("Created role_binding") diff --git a/test/test-framework/deploy/namespace-init.yaml b/test/test-framework/deploy/namespace-init.yaml index 749ed548a36..fc9b93f90d5 100644 --- a/test/test-framework/deploy/namespace-init.yaml +++ b/test/test-framework/deploy/namespace-init.yaml @@ -65,6 +65,7 @@ roleRef: apiVersion: apps/v1 kind: Deployment metadata: + creationTimestamp: null name: memcached-operator spec: replicas: 1 @@ -74,6 +75,7 @@ spec: strategy: {} template: metadata: + creationTimestamp: null labels: name: memcached-operator spec: