github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/test/e2e/memcached_test.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package e2e
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/ghodss/yaml"
    32  	"github.com/operator-framework/operator-sdk/internal/pkg/scaffold"
    33  	"github.com/operator-framework/operator-sdk/internal/util/fileutil"
    34  	"github.com/operator-framework/operator-sdk/internal/util/projutil"
    35  	"github.com/operator-framework/operator-sdk/internal/util/yamlutil"
    36  	framework "github.com/operator-framework/operator-sdk/pkg/test"
    37  	"github.com/operator-framework/operator-sdk/pkg/test/e2eutil"
    38  
    39  	"github.com/prometheus/prometheus/util/promlint"
    40  	"k8s.io/api/core/v1"
    41  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    42  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    43  	"k8s.io/apimachinery/pkg/types"
    44  	"k8s.io/apimachinery/pkg/util/wait"
    45  	"k8s.io/client-go/kubernetes"
    46  	"k8s.io/client-go/rest"
    47  	"sigs.k8s.io/controller-runtime/pkg/client"
    48  )
    49  
    50  const (
    51  	crYAML               string = "apiVersion: \"cache.example.com/v1alpha1\"\nkind: \"Memcached\"\nmetadata:\n  name: \"example-memcached\"\nspec:\n  size: 3"
    52  	retryInterval               = time.Second * 5
    53  	timeout                     = time.Second * 120
    54  	cleanupRetryInterval        = time.Second * 1
    55  	cleanupTimeout              = time.Second * 10
    56  	operatorName                = "memcached-operator"
    57  )
    58  
    59  func TestMemcached(t *testing.T) {
    60  	// get global framework variables
    61  	ctx := framework.NewTestCtx(t)
    62  	defer ctx.Cleanup()
    63  	gopath, ok := os.LookupEnv(projutil.GopathEnv)
    64  	if !ok {
    65  		t.Fatalf("$GOPATH not set")
    66  	}
    67  	cd, err := os.Getwd()
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	defer func() {
    72  		if err := os.Chdir(cd); err != nil {
    73  			t.Errorf("Failed to change back to original working directory: (%v)", err)
    74  		}
    75  	}()
    76  
    77  	// Setup
    78  	absProjectPath := filepath.Join(gopath, "src/github.com/example-inc")
    79  	if err := os.MkdirAll(absProjectPath, fileutil.DefaultDirFileMode); err != nil {
    80  		t.Fatal(err)
    81  	}
    82  	if err := os.Chdir(absProjectPath); err != nil {
    83  		t.Fatal(err)
    84  	}
    85  
    86  	t.Log("Creating new operator project")
    87  	cmdOut, err := exec.Command("operator-sdk",
    88  		"new",
    89  		operatorName).CombinedOutput()
    90  	if err != nil {
    91  		// HACK: dep cannot resolve non-master branches as the base branch for PR's,
    92  		// so running `dep ensure` will fail when first running
    93  		// `operator-sdk new ...`. For now we can ignore the first solve failure.
    94  		// A permanent solution can be implemented once the following is merged:
    95  		// https://github.com/golang/dep/pull/1658
    96  		solveFailRe := regexp.MustCompile(`(?m)^[ \t]*Solving failure:.+github\.com/operator-framework/operator-sdk.+:$`)
    97  		if !solveFailRe.Match(cmdOut) {
    98  			t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
    99  		}
   100  	}
   101  	ctx.AddCleanupFn(func() error { return os.RemoveAll(absProjectPath) })
   102  
   103  	if err := os.Chdir(operatorName); err != nil {
   104  		t.Fatalf("Failed to change to %s directory: (%v)", operatorName, err)
   105  	}
   106  	repo, ok := os.LookupEnv("TRAVIS_PULL_REQUEST_SLUG")
   107  	if repo == "" {
   108  		repo, ok = os.LookupEnv("TRAVIS_REPO_SLUG")
   109  	}
   110  	if ok && repo != "" && repo != "operator-framework/operator-sdk" {
   111  		commitSha, ok := os.LookupEnv("TRAVIS_PULL_REQUEST_SHA")
   112  		if commitSha == "" {
   113  			commitSha, ok = os.LookupEnv("TRAVIS_COMMIT")
   114  		}
   115  		if ok && commitSha != "" {
   116  			gopkg, err := ioutil.ReadFile("Gopkg.toml")
   117  			if err != nil {
   118  				t.Fatal(err)
   119  			}
   120  			// Match against the '#osdk_branch_annotation' used for version substitution
   121  			// and comment out the current branch.
   122  			branchRe := regexp.MustCompile("([ ]+)(.+#osdk_branch_annotation)")
   123  			gopkg = branchRe.ReplaceAll(gopkg, []byte("$1# $2"))
   124  			versionRe := regexp.MustCompile("([ ]+)(.+#osdk_version_annotation)")
   125  			gopkg = versionRe.ReplaceAll(gopkg, []byte("$1# $2"))
   126  			// Plug in the fork to test against so `dep ensure` can resolve dependencies
   127  			// correctly.
   128  			gopkgString := string(gopkg)
   129  			gopkgLoc := strings.LastIndex(gopkgString, "\n  name = \"github.com/operator-framework/operator-sdk\"\n")
   130  			gopkgString = gopkgString[:gopkgLoc] + "\n  source = \"https://github.com/" + repo + "\"\n  revision = \"" + commitSha + "\"\n" + gopkgString[gopkgLoc+1:]
   131  			err = ioutil.WriteFile("Gopkg.toml", []byte(gopkgString), fileutil.DefaultFileMode)
   132  			if err != nil {
   133  				t.Fatalf("Failed to write updated Gopkg.toml: %v", err)
   134  			}
   135  
   136  			t.Logf("Gopkg.toml: %v", gopkgString)
   137  		} else {
   138  			t.Fatal("Could not find sha of PR")
   139  		}
   140  	}
   141  	cmdOut, err = exec.Command("dep", "ensure").CombinedOutput()
   142  	if err != nil {
   143  		t.Fatalf("Error after modifying Gopkg.toml: %v\nCommand Output: %s\n", err, string(cmdOut))
   144  	}
   145  
   146  	// Set replicas to 2 to test leader election. In production, this should
   147  	// almost always be set to 1, because there isn't generally value in having
   148  	// a hot spare operator process.
   149  	opYaml, err := ioutil.ReadFile("deploy/operator.yaml")
   150  	if err != nil {
   151  		t.Fatalf("Could not read deploy/operator.yaml: %v", err)
   152  	}
   153  	newOpYaml := bytes.Replace(opYaml, []byte("replicas: 1"), []byte("replicas: 2"), 1)
   154  	err = ioutil.WriteFile("deploy/operator.yaml", newOpYaml, 0644)
   155  	if err != nil {
   156  		t.Fatalf("Could not write deploy/operator.yaml: %v", err)
   157  	}
   158  
   159  	cmd := exec.Command("operator-sdk",
   160  		"add",
   161  		"api",
   162  		"--api-version=cache.example.com/v1alpha1",
   163  		"--kind=Memcached")
   164  	cmd.Env = os.Environ()
   165  	cmdOut, err = cmd.CombinedOutput()
   166  	if err != nil {
   167  		t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
   168  	}
   169  	cmdOut, err = exec.Command("operator-sdk",
   170  		"add",
   171  		"controller",
   172  		"--api-version=cache.example.com/v1alpha1",
   173  		"--kind=Memcached").CombinedOutput()
   174  	if err != nil {
   175  		t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
   176  	}
   177  
   178  	cmdOut, err = exec.Command("cp", "-a", filepath.Join(gopath, "src/github.com/operator-framework/operator-sdk/example/memcached-operator/memcached_controller.go.tmpl"),
   179  		"pkg/controller/memcached/memcached_controller.go").CombinedOutput()
   180  	if err != nil {
   181  		t.Fatalf("Could not copy memcached example to to pkg/controller/memcached/memcached_controller.go: %v\nCommand Output:\n%v", err, string(cmdOut))
   182  	}
   183  	memcachedTypesFile, err := ioutil.ReadFile("pkg/apis/cache/v1alpha1/memcached_types.go")
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	memcachedTypesFileLines := bytes.Split(memcachedTypesFile, []byte("\n"))
   188  	for lineNum, line := range memcachedTypesFileLines {
   189  		if strings.Contains(string(line), "type MemcachedSpec struct {") {
   190  			memcachedTypesFileLinesIntermediate := append(memcachedTypesFileLines[:lineNum+1], []byte("\tSize int32 `json:\"size\"`"))
   191  			memcachedTypesFileLines = append(memcachedTypesFileLinesIntermediate, memcachedTypesFileLines[lineNum+3:]...)
   192  			break
   193  		}
   194  	}
   195  	for lineNum, line := range memcachedTypesFileLines {
   196  		if strings.Contains(string(line), "type MemcachedStatus struct {") {
   197  			memcachedTypesFileLinesIntermediate := append(memcachedTypesFileLines[:lineNum+1], []byte("\tNodes []string `json:\"nodes\"`"))
   198  			memcachedTypesFileLines = append(memcachedTypesFileLinesIntermediate, memcachedTypesFileLines[lineNum+3:]...)
   199  			break
   200  		}
   201  	}
   202  	if err := os.Remove("pkg/apis/cache/v1alpha1/memcached_types.go"); err != nil {
   203  		t.Fatalf("Failed to remove old memcached_type.go file: (%v)", err)
   204  	}
   205  	err = ioutil.WriteFile("pkg/apis/cache/v1alpha1/memcached_types.go", bytes.Join(memcachedTypesFileLines, []byte("\n")), fileutil.DefaultFileMode)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  
   210  	t.Log("Generating k8s")
   211  	cmdOut, err = exec.Command("operator-sdk", "generate", "k8s").CombinedOutput()
   212  	if err != nil {
   213  		t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
   214  	}
   215  
   216  	t.Log("Copying test files to ./test")
   217  	if err = os.MkdirAll("./test", fileutil.DefaultDirFileMode); err != nil {
   218  		t.Fatalf("Could not create test/e2e dir: %v", err)
   219  	}
   220  	cmdOut, err = exec.Command("cp", "-a", filepath.Join(gopath, "src/github.com/operator-framework/operator-sdk/test/e2e/incluster-test-code"), "./test/e2e").CombinedOutput()
   221  	if err != nil {
   222  		t.Fatalf("Could not copy tests to test/e2e: %v\nCommand Output:\n%v", err, string(cmdOut))
   223  	}
   224  	// fix naming of files
   225  	cmdOut, err = exec.Command("mv", "test/e2e/main_test.go.tmpl", "test/e2e/main_test.go").CombinedOutput()
   226  	if err != nil {
   227  		t.Fatalf("Could not rename test/e2e/main_test.go.tmpl: %v\nCommand Output:\n%v", err, string(cmdOut))
   228  	}
   229  	cmdOut, err = exec.Command("mv", "test/e2e/memcached_test.go.tmpl", "test/e2e/memcached_test.go").CombinedOutput()
   230  	if err != nil {
   231  		t.Fatalf("Could not rename test/e2e/memcached_test.go.tmpl: %v\nCommand Output:\n%v", err, string(cmdOut))
   232  	}
   233  
   234  	t.Log("Pulling new dependencies with dep ensure")
   235  	cmdOut, err = exec.Command("dep", "ensure").CombinedOutput()
   236  	if err != nil {
   237  		t.Fatalf("Command 'dep ensure' failed: %v\nCommand Output:\n%v", err, string(cmdOut))
   238  	}
   239  	// link local sdk to vendor if not in travis
   240  	if repo == "" {
   241  		for _, dir := range []string{"pkg", "internal"} {
   242  			repoDir := filepath.Join("github.com/operator-framework/operator-sdk", dir)
   243  			vendorDir := filepath.Join("vendor", repoDir)
   244  			if err := os.RemoveAll(vendorDir); err != nil {
   245  				t.Fatalf("Failed to delete old vendor directory: (%v)", err)
   246  			}
   247  			if err := os.Symlink(filepath.Join(gopath, projutil.SrcDir, repoDir), vendorDir); err != nil {
   248  				t.Fatalf("Failed to symlink local operator-sdk project to vendor dir: (%v)", err)
   249  			}
   250  		}
   251  	}
   252  
   253  	file, err := yamlutil.GenerateCombinedGlobalManifest(scaffold.CRDsDir)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	// hacky way to use createFromYAML without exposing the method
   258  	// create crd
   259  	filename := file.Name()
   260  	framework.Global.NamespacedManPath = &filename
   261  	err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	t.Log("Created global resources")
   266  
   267  	// run subtests
   268  	t.Run("memcached-group", func(t *testing.T) {
   269  		t.Run("Cluster", MemcachedCluster)
   270  		t.Run("ClusterTest", MemcachedClusterTest)
   271  		t.Run("Local", MemcachedLocal)
   272  	})
   273  }
   274  
   275  func memcachedLeaderTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error {
   276  	namespace, err := ctx.GetNamespace()
   277  	if err != nil {
   278  		return err
   279  	}
   280  
   281  	err = e2eutil.WaitForOperatorDeployment(t, f.KubeClient, namespace, operatorName, 2, retryInterval, timeout)
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	label := map[string]string{"name": operatorName}
   287  
   288  	leader, err := verifyLeader(t, namespace, f, label)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	// delete the leader's pod so a new leader will get elected
   294  	err = f.Client.Delete(context.TODO(), leader)
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	err = e2eutil.WaitForDeletion(t, f.Client.Client, leader, retryInterval, timeout)
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	err = e2eutil.WaitForOperatorDeployment(t, f.KubeClient, namespace, operatorName, 2, retryInterval, timeout)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	newLeader, err := verifyLeader(t, namespace, f, label)
   310  	if err != nil {
   311  		return err
   312  	}
   313  	if newLeader.Name == leader.Name {
   314  		return fmt.Errorf("leader pod name did not change across pod delete")
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  func verifyLeader(t *testing.T, namespace string, f *framework.Framework, labels map[string]string) (*v1.Pod, error) {
   321  	// get configmap, which is the lock
   322  	lockName := "memcached-operator-lock"
   323  	lock := v1.ConfigMap{}
   324  	err := wait.Poll(retryInterval, timeout, func() (done bool, err error) {
   325  		err = f.Client.Get(context.TODO(), types.NamespacedName{Name: lockName, Namespace: namespace}, &lock)
   326  		if err != nil {
   327  			if apierrors.IsNotFound(err) {
   328  				t.Logf("Waiting for availability of leader lock configmap %s\n", lockName)
   329  				return false, nil
   330  			}
   331  			return false, err
   332  		}
   333  		return true, nil
   334  	})
   335  	if err != nil {
   336  		return nil, fmt.Errorf("error getting leader lock configmap: %v\n", err)
   337  	}
   338  	t.Logf("Found leader lock configmap %s\n", lockName)
   339  
   340  	owners := lock.GetOwnerReferences()
   341  	if len(owners) != 1 {
   342  		return nil, fmt.Errorf("leader lock has %d owner refs, expected 1", len(owners))
   343  	}
   344  	owner := owners[0]
   345  
   346  	// get operator pods
   347  	pods := v1.PodList{}
   348  	opts := client.ListOptions{Namespace: namespace}
   349  	for k, v := range labels {
   350  		if err := opts.SetLabelSelector(fmt.Sprintf("%s=%s", k, v)); err != nil {
   351  			return nil, fmt.Errorf("failed to set list label selector: (%v)", err)
   352  		}
   353  	}
   354  	if err := opts.SetFieldSelector("status.phase=Running"); err != nil {
   355  		t.Fatalf("Failed to set list field selector: (%v)", err)
   356  	}
   357  	err = f.Client.List(context.TODO(), &opts, &pods)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	if len(pods.Items) != 2 {
   362  		return nil, fmt.Errorf("expected 2 pods, found %d", len(pods.Items))
   363  	}
   364  
   365  	// find and return the leader
   366  	for _, pod := range pods.Items {
   367  		if pod.Name == owner.Name {
   368  			return &pod, nil
   369  		}
   370  	}
   371  	return nil, fmt.Errorf("did not find operator pod that was referenced by configmap")
   372  }
   373  
   374  func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error {
   375  	// create example-memcached yaml file
   376  	filename := "deploy/cr.yaml"
   377  	err := ioutil.WriteFile(filename,
   378  		[]byte(crYAML),
   379  		fileutil.DefaultFileMode)
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	// create memcached custom resource
   385  	framework.Global.NamespacedManPath = &filename
   386  	err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   387  	if err != nil {
   388  		return err
   389  	}
   390  	t.Log("Created cr")
   391  
   392  	namespace, err := ctx.GetNamespace()
   393  	if err != nil {
   394  		return err
   395  	}
   396  	// wait for example-memcached to reach 3 replicas
   397  	err = e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 3, retryInterval, timeout)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	// get fresh copy of memcached object as unstructured
   403  	obj := unstructured.Unstructured{}
   404  	jsonSpec, err := yaml.YAMLToJSON([]byte(crYAML))
   405  	if err != nil {
   406  		return fmt.Errorf("could not convert yaml file to json: %v", err)
   407  	}
   408  	if err := obj.UnmarshalJSON(jsonSpec); err != nil {
   409  		t.Fatalf("Failed to unmarshal memcached CR: (%v)", err)
   410  	}
   411  	obj.SetNamespace(namespace)
   412  	err = f.Client.Get(context.TODO(), types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, &obj)
   413  	if err != nil {
   414  		return fmt.Errorf("failed to get memcached object: %s", err)
   415  	}
   416  	// update memcached CR size to 4
   417  	spec, ok := obj.Object["spec"].(map[string]interface{})
   418  	if !ok {
   419  		return errors.New("memcached object missing spec field")
   420  	}
   421  	spec["size"] = 4
   422  	err = f.Client.Update(context.TODO(), &obj)
   423  	if err != nil {
   424  		return err
   425  	}
   426  
   427  	// wait for example-memcached to reach 4 replicas
   428  	return e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 4, retryInterval, timeout)
   429  }
   430  
   431  func MemcachedLocal(t *testing.T) {
   432  	// get global framework variables
   433  	ctx := framework.NewTestCtx(t)
   434  	defer ctx.Cleanup()
   435  	namespace, err := ctx.GetNamespace()
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  	cmd := exec.Command("operator-sdk", "up", "local", "--namespace="+namespace)
   440  	stderr, err := os.Create("stderr.txt")
   441  	if err != nil {
   442  		t.Fatalf("Failed to create stderr.txt: %v", err)
   443  	}
   444  	cmd.Stderr = stderr
   445  	defer func() {
   446  		if err := stderr.Close(); err != nil && !fileutil.IsClosedError(err) {
   447  			t.Errorf("Failed to close stderr: (%v)", err)
   448  		}
   449  	}()
   450  
   451  	err = cmd.Start()
   452  	if err != nil {
   453  		t.Fatalf("Error: %v", err)
   454  	}
   455  	ctx.AddCleanupFn(func() error { return cmd.Process.Signal(os.Interrupt) })
   456  
   457  	// wait for operator to start (may take a minute to compile the command...)
   458  	err = wait.Poll(time.Second*5, time.Second*100, func() (done bool, err error) {
   459  		file, err := ioutil.ReadFile("stderr.txt")
   460  		if err != nil {
   461  			return false, err
   462  		}
   463  		if len(file) == 0 {
   464  			return false, nil
   465  		}
   466  		return true, nil
   467  	})
   468  	if err != nil {
   469  		t.Fatalf("Local operator not ready after 100 seconds: %v\n", err)
   470  	}
   471  
   472  	if err = memcachedScaleTest(t, framework.Global, ctx); err != nil {
   473  		t.Fatal(err)
   474  	}
   475  }
   476  
   477  func MemcachedCluster(t *testing.T) {
   478  	// get global framework variables
   479  	ctx := framework.NewTestCtx(t)
   480  	defer ctx.Cleanup()
   481  	operatorYAML, err := ioutil.ReadFile("deploy/operator.yaml")
   482  	if err != nil {
   483  		t.Fatalf("Could not read deploy/operator.yaml: %v", err)
   484  	}
   485  	local := *e2eImageName == ""
   486  	if local {
   487  		*e2eImageName = "quay.io/example/memcached-operator:v0.0.1"
   488  		if err != nil {
   489  			t.Fatal(err)
   490  		}
   491  		operatorYAML = bytes.Replace(operatorYAML, []byte("imagePullPolicy: Always"), []byte("imagePullPolicy: Never"), 1)
   492  		err = ioutil.WriteFile("deploy/operator.yaml", operatorYAML, fileutil.DefaultFileMode)
   493  		if err != nil {
   494  			t.Fatal(err)
   495  		}
   496  	}
   497  	operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*e2eImageName), 1)
   498  	err = ioutil.WriteFile("deploy/operator.yaml", operatorYAML, os.FileMode(0644))
   499  	if err != nil {
   500  		t.Fatalf("Failed to write deploy/operator.yaml: %v", err)
   501  	}
   502  	t.Log("Building operator docker image")
   503  	cmdOut, err := exec.Command("operator-sdk", "build", *e2eImageName,
   504  		"--enable-tests",
   505  		"--test-location", "./test/e2e",
   506  		"--namespaced-manifest", "deploy/operator.yaml").CombinedOutput()
   507  	if err != nil {
   508  		t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
   509  	}
   510  
   511  	if !local {
   512  		t.Log("Pushing docker image to repo")
   513  		cmdOut, err = exec.Command("docker", "push", *e2eImageName).CombinedOutput()
   514  		if err != nil {
   515  			t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
   516  		}
   517  	}
   518  
   519  	file, err := yamlutil.GenerateCombinedNamespacedManifest(scaffold.DeployDir)
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	// create namespaced resources
   524  	filename := file.Name()
   525  	framework.Global.NamespacedManPath = &filename
   526  	err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	t.Log("Created namespaced resources")
   531  
   532  	namespace, err := ctx.GetNamespace()
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  	// wait for memcached-operator to be ready
   537  	err = e2eutil.WaitForOperatorDeployment(t, framework.Global.KubeClient, namespace, operatorName, 2, retryInterval, timeout)
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  
   542  	if err = memcachedLeaderTest(t, framework.Global, ctx); err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	if err = memcachedScaleTest(t, framework.Global, ctx); err != nil {
   547  		t.Fatal(err)
   548  	}
   549  
   550  	if err = memcachedMetricsTest(t, framework.Global, ctx); err != nil {
   551  		t.Fatal(err)
   552  	}
   553  }
   554  
   555  func MemcachedClusterTest(t *testing.T) {
   556  	// get global framework variables
   557  	ctx := framework.NewTestCtx(t)
   558  	defer ctx.Cleanup()
   559  
   560  	// create sa
   561  	filename := "deploy/service_account.yaml"
   562  	framework.Global.NamespacedManPath = &filename
   563  	err := ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   564  	if err != nil {
   565  		t.Fatal(err)
   566  	}
   567  	t.Log("Created sa")
   568  
   569  	// create rbac
   570  	filename = "deploy/role.yaml"
   571  	framework.Global.NamespacedManPath = &filename
   572  	err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  	t.Log("Created role")
   577  
   578  	filename = "deploy/role_binding.yaml"
   579  	framework.Global.NamespacedManPath = &filename
   580  	err = ctx.InitializeClusterResources(&framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval})
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  	t.Log("Created role_binding")
   585  
   586  	namespace, err := ctx.GetNamespace()
   587  	if err != nil {
   588  		t.Fatalf("Could not get namespace: %v", err)
   589  	}
   590  	cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *e2eImageName,
   591  		"--namespace", namespace,
   592  		"--image-pull-policy", "Never",
   593  		"--service-account", operatorName).CombinedOutput()
   594  	if err != nil {
   595  		t.Fatalf("In-cluster test failed: %v\nCommand Output:\n%s", err, string(cmdOut))
   596  	}
   597  }
   598  
   599  func memcachedMetricsTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error {
   600  	namespace, err := ctx.GetNamespace()
   601  	if err != nil {
   602  		return err
   603  	}
   604  
   605  	// Make sure metrics Service exists
   606  	s := v1.Service{}
   607  	err = f.Client.Get(context.TODO(), types.NamespacedName{Name: operatorName, Namespace: namespace}, &s)
   608  	if err != nil {
   609  		return fmt.Errorf("could not get metrics Service: (%v)", err)
   610  	}
   611  
   612  	// Get operator pod
   613  	pods := v1.PodList{}
   614  	opts := client.InNamespace(namespace)
   615  	if len(s.Spec.Selector) == 0 {
   616  		return fmt.Errorf("no labels found in metrics Service")
   617  	}
   618  
   619  	for k, v := range s.Spec.Selector {
   620  		if err := opts.SetLabelSelector(fmt.Sprintf("%s=%s", k, v)); err != nil {
   621  			return fmt.Errorf("failed to set list label selector: (%v)", err)
   622  		}
   623  	}
   624  
   625  	if err := opts.SetFieldSelector("status.phase=Running"); err != nil {
   626  		return fmt.Errorf("failed to set list field selector: (%v)", err)
   627  	}
   628  	err = f.Client.List(context.TODO(), opts, &pods)
   629  	if err != nil {
   630  		return fmt.Errorf("failed to get pods: (%v)", err)
   631  	}
   632  
   633  	podName := ""
   634  	numPods := len(pods.Items)
   635  	// TODO(lili): Remove below logic when we enable exposing metrics in all pods.
   636  	if numPods == 0 {
   637  		podName = pods.Items[0].Name
   638  	} else if numPods > 1 {
   639  		// If we got more than one pod, get leader pod name.
   640  		leader, err := verifyLeader(t, namespace, f, s.Spec.Selector)
   641  		if err != nil {
   642  			return err
   643  		}
   644  		podName = leader.Name
   645  	} else {
   646  		return fmt.Errorf("failed to get operator pod: could not select any pods with Service selector %v", s.Spec.Selector)
   647  	}
   648  	// Pod name must be there, otherwise we cannot read metrics data via pod proxy.
   649  	if podName == "" {
   650  		return fmt.Errorf("failed to get pod name")
   651  	}
   652  
   653  	// Get metrics data
   654  	request := proxyViaPod(f.KubeClient, namespace, podName, "8383", "/metrics")
   655  	response, err := request.DoRaw()
   656  	if err != nil {
   657  		return fmt.Errorf("failed to get response from metrics: %v", err)
   658  	}
   659  
   660  	// Make sure metrics are present
   661  	if len(response) == 0 {
   662  		return fmt.Errorf("metrics body is empty")
   663  	}
   664  
   665  	// Perform prometheus metrics lint checks
   666  	l := promlint.New(bytes.NewReader(response))
   667  	problems, err := l.Lint()
   668  	if err != nil {
   669  		return fmt.Errorf("failed to lint metrics: %v", err)
   670  	}
   671  	// TODO(lili): Change to 0, when we upgrade to 1.14.
   672  	// currently there is a problem with one of the metrics in upstream Kubernetes:
   673  	// `workqueue_longest_running_processor_microseconds`.
   674  	// This has been fixed in 1.14 release.
   675  	if len(problems) > 1 {
   676  		return fmt.Errorf("found problems with metrics: %#+v", problems)
   677  	}
   678  
   679  	return nil
   680  }
   681  
   682  func proxyViaPod(kubeClient kubernetes.Interface, namespace, podName, podPortName, path string) *rest.Request {
   683  	return kubeClient.
   684  		CoreV1().
   685  		RESTClient().
   686  		Get().
   687  		Namespace(namespace).
   688  		Resource("pods").
   689  		SubResource("proxy").
   690  		Name(fmt.Sprintf("%s:%s", podName, podPortName)).
   691  		Suffix(path)
   692  }