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 }