Example #1
0
func TestProjectStatus(t *testing.T) {
	testCases := map[string]struct {
		File     string
		Extra    []runtime.Object
		ErrFn    func(error) bool
		Contains []string
		Time     time.Time
	}{
		"missing project": {
			ErrFn: func(err error) bool { return err == nil },
		},
		"empty project with display name": {
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{
						Name:      "example",
						Namespace: "",
						Annotations: map[string]string{
							projectapi.ProjectDisplayName: "Test",
						},
					},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project Test (example) on server https://example.com:8443\n",
				"You have no services, deployment configs, or build configs.",
			},
		},
		"empty service": {
			File: "k8s-service-with-nothing.json",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/empty-service",
				"<initializing>:5432",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
		},
		"service with RC": {
			File: "k8s-unserviced-rc.json",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/database-rc",
				"rc/database-rc-1 runs mysql",
				"0/1 pods growing to 1",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
		},
		"rc with unmountable and missing secrets": {
			File: "bad_secret_with_just_rc.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"rc/my-rc runs centos/mysql-56-centos7",
				"0/1 pods growing to 1",
				"rc/my-rc is attempting to mount a missing secret secret/dne",
			},
		},
		"dueling rcs": {
			File: "dueling-rcs.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "dueling-rc", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"rc/rc-1 is competing for pod/conflicted-pod with rc/rc-2",
				"rc/rc-2 is competing for pod/conflicted-pod with rc/rc-1",
			},
		},
		"service with pod": {
			File: "service-with-pod.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/frontend-app",
				"pod/frontend-app-1-bjwh8 runs openshift/ruby-hello-world",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
		},
		"build chains": {
			File: "build-chains.json",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"from bc/frontend",
			},
		},
		"scheduled image stream": {
			File: "prereq-image-present-with-sched.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"import scheduled",
			},
		},
		"standalone rc": {
			File: "bare-rc.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"  rc/database-1 runs centos/mysql-56-centos7",
				"rc/frontend-rc-1 runs openshift/ruby-hello-world",
			},
		},
		"unstarted build": {
			File: "new-project-no-build.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/sinatra-example-2 - 172.30.17.48:8080",
				"deploys istag/sinatra-example-2:latest <-",
				"builds git://github.com",
				"on docker.io/centos/ruby-22-centos7:latest",
				"not built yet",
				"deployment #1 waiting on image or update",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
		},
		"unpushable build": {
			File: "unpushable-build.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"bc/ruby-hello-world is pushing to istag/ruby-hello-world:latest, but the administrator has not configured the integrated Docker registry.",
			},
		},
		"bare-bc-can-push": {
			File: "bare-bc-can-push.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				// this makes sure that status knows this can push.  If it fails, there's a "(can't push image)" next to like #8
				" hours\n  build #7",
				"on istag/fedora:23",
				"-> istag/repo-base:latest",
			},
			Time: mustParseTime("2015-12-17T20:36:15Z"),
		},
		"cyclical build": {
			File: "circular.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"Cycle detected in build configurations:",
				"on istag/ruby-22-centos7:latest",
				"-> istag/ruby-hello-world:latest",
			},
		},
		"running build": {
			File: "new-project-one-build.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/sinatra-example-1 - 172.30.17.47:8080",
				"builds git://github.com",
				"on docker.io/centos/ruby-22-centos7:latest",
				"build #1 running for about a minute",
				"deployment #1 waiting on image or update",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
			Time: mustParseTime("2015-04-06T21:20:03Z"),
		},
		"a/b test DeploymentConfig": {
			File: "new-project-two-deployment-configs.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/sinatra-app-example - 172.30.17.49:8080",
				"sinatra-app-example-a deploys",
				"sinatra-app-example-b deploys",
				"on docker.io/centos/ruby-22-centos7:latest",
				"build #1 running for about a minute",
				"- 7a4f354: Prepare v1 Template types (Roy Programmer <*****@*****.**>)",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
			Time: mustParseTime("2015-04-06T21:20:03Z"),
		},
		"with real deployments": {
			File: "new-project-deployed-app.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/database - 172.30.17.240:5434 -> 3306",
				"https://www.test.com (redirects) to pod port 8080 (svc/frontend)",
				"http://frontend-example.router.default.svc.cluster.local to pod port 8080 (!)",
				"svc/database-external (all nodes):31000 -> 3306",
				"database test deploys",
				"frontend deploys",
				"istag/origin-ruby-sample:latest <-",
				"on docker.io/centos/ruby-22-centos7:latest",
				"deployment #3 pending on image",
				"deployment #2 failed less than a second ago: unable to contact server - 0/1 pods",
				"deployment #1 deployed less than a second ago",
				"test deployment #2 running for 7 seconds - 2/1 pods",
				"test deployment #1 deployed 8 seconds ago",
				"* bc/ruby-sample-build is pushing to istag/origin-ruby-sample:latest, but the image stream for that tag does not exist.",
				"* The image trigger for dc/frontend will have no effect because is/origin-ruby-sample does not exist",
				"* route/frontend was not accepted by router \"other\":  (HostAlreadyClaimed)",
				"* dc/database has no readiness probe to verify pods are ready to accept traffic or ensure deployment is successful.",
				"View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.",
			},
			Time: mustParseTime("2015-04-07T04:12:25Z"),
		},
		"with pet sets": {
			File: "petset.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"svc/galera (headless):3306",
				"petset/mysql manages erkules/galera:basic, created less than a second ago - 3 pods",
				"* pod/mysql-1 has restarted 7 times",
			},
			Time: mustParseTime("2015-04-07T04:12:25Z"),
		},
		"restarting pod": {
			File: "restarting-pod.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				`container "ruby-helloworld" in pod/frontend-app-1-bjwh8 has restarted 8 times`,
				`pod/gitlab-ce-1-lc411 is crash-looping`,
				`oc logs -p gitlab-ce-1-lc411 -c gitlab-ce`, // verifies we print the log command
				`policycommand example default`,             // verifies that we print the help command
			},
		},
		"cross namespace reference": {
			File: "different-project-image-deployment.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				// If there was a warning we wouldn't get the following message. Since we ignore cross-namespace
				// links by default, there should be no warning here.
				`View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.`,
			},
		},
		"monopod": {
			File: "k8s-lonely-pod.json",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"pod/lonely-pod runs openshift/hello-openshift",
				"You have no services, deployment configs, or build configs.",
			},
		},
		"deploys single pod": {
			File: "simple-deployment.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"In project example on server https://example.com:8443\n",
				"dc/simple-deployment deploys docker.io/openshift/deployment-example:v1",
				`View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.`,
			},
		},
		"deployment with unavailable pods": {
			File: "available-deployment.yaml",
			Extra: []runtime.Object{
				&projectapi.Project{
					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
				},
			},
			ErrFn: func(err error) bool { return err == nil },
			Contains: []string{
				"deployment #2 running for 30 seconds - 0/1 pods\n",
				"deployment #1 deployed about a minute ago - 1/2 pods",
			},
			Time: mustParseTime("2016-04-07T04:12:25Z"),
		},
	}
	oldTimeFn := timeNowFn
	defer func() { timeNowFn = oldTimeFn }()
	for k, test := range testCases {
		timeNowFn = func() time.Time {
			if !test.Time.IsZero() {
				return test.Time
			}
			return time.Now()
		}
		objs := []runtime.Object{}
		if len(test.File) > 0 {
			// Load data from a folder dedicated to mock data, which is never loaded into the API during tests
			var err error
			objs, err = testclient.ReadObjectsFromPath("../../../../pkg/api/graph/test/"+test.File, "example", kapi.Codecs.UniversalDecoder(), kapi.Scheme)
			if err != nil {
				t.Errorf("%s: unexpected error: %v", k, err)
			}
		}
		for _, o := range test.Extra {
			objs = append(objs, o)
		}
		oc, kc, oldK := testclient.NewFixtureClients(objs...)
		d := ProjectStatusDescriber{OldK: oldK, K: kc, C: oc, Server: "https://example.com:8443", Suggest: true, CommandBaseName: "oc", LogsCommandName: "oc logs -p", SecurityPolicyCommandFormat: "policycommand %s %s"}
		out, err := d.Describe("example", "")
		if !test.ErrFn(err) {
			t.Errorf("%s: unexpected error: %v", k, err)
		}
		if err != nil {
			continue
		}
		for _, s := range test.Contains {
			if !strings.Contains(out, s) {
				t.Errorf("%s: did not have %q:\n%s\n---", k, s, out)
			}
		}
	}
}
func TestChainDescriber(t *testing.T) {
	tests := []struct {
		testName         string
		namespaces       sets.String
		output           string
		reverse          bool
		defaultNamespace string
		name             string
		tag              string
		path             string
		humanReadable    map[string]int
		dot              []string
		expectedErr      error
		includeInputImg  bool
	}{
		{
			testName:         "circular test",
			namespaces:       sets.NewString("example"),
			output:           "",
			defaultNamespace: "example",
			name:             "ruby-22-centos7",
			tag:              "latest",
			path:             "../../../api/graph/test/circular.yaml",
			humanReadable: map[string]int{
				"Cycle detected in build configurations: bc/ruby-22-centos7 -> istag/ruby-hello-world:latest -> bc/ruby-hello-world -> istag/ruby-something-else:latest -> bc/ruby-something-else -> istag/ruby-22-centos7:latest -> bc/ruby-22-centos7": 1,
			},
			expectedErr: nil,
		},
		{
			testName:         "human readable test - single namespace",
			namespaces:       sets.NewString("test"),
			output:           "",
			defaultNamespace: "test",
			name:             "ruby-22-centos7",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/single-namespace-bcs.yaml",
			humanReadable: map[string]int{
				"istag/ruby-22-centos7:latest":        1,
				"\tbc/ruby-hello-world":               1,
				"\t\tistag/ruby-hello-world:latest":   1,
				"\tbc/ruby-sample-build":              1,
				"\t\tistag/origin-ruby-sample:latest": 1,
			},
			expectedErr: nil,
		},
		{
			testName:         "dot test - single namespace",
			namespaces:       sets.NewString("test"),
			output:           "dot",
			defaultNamespace: "test",
			name:             "ruby-22-centos7",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/single-namespace-bcs.yaml",
			dot: []string{
				"digraph \"ruby-22-centos7:latest\" {",
				"// Node definitions.",
				"[label=\"BuildConfig|test/ruby-hello-world\"];",
				"[label=\"BuildConfig|test/ruby-sample-build\"];",
				"[label=\"ImageStreamTag|test/ruby-hello-world:latest\"];",
				"[label=\"ImageStreamTag|test/ruby-22-centos7:latest\"];",
				"[label=\"ImageStreamTag|test/origin-ruby-sample:latest\"];",
				"",
				"// Edge definitions.",
				"[label=\"BuildOutput\"];",
				"[label=\"BuildOutput\"];",
				"[label=\"BuildInputImage,BuildTriggerImage\"];",
				"[label=\"BuildInputImage,BuildTriggerImage\"];",
				"}",
			},
			expectedErr: nil,
		},
		{
			testName:         "human readable test - multiple namespaces",
			namespaces:       sets.NewString("test", "master", "default"),
			output:           "",
			defaultNamespace: "master",
			name:             "ruby-22-centos7",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-namespaces-bcs.yaml",
			humanReadable: map[string]int{
				"<master istag/ruby-22-centos7:latest>":         1,
				"\t<default bc/ruby-hello-world>":               1,
				"\t\t<test istag/ruby-hello-world:latest>":      1,
				"\t<test bc/ruby-sample-build>":                 1,
				"\t\t<another istag/origin-ruby-sample:latest>": 1,
			},
			expectedErr: nil,
		},
		{
			testName:         "dot test - multiple namespaces",
			namespaces:       sets.NewString("test", "master", "default"),
			output:           "dot",
			defaultNamespace: "master",
			name:             "ruby-22-centos7",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-namespaces-bcs.yaml",
			dot: []string{
				"digraph \"ruby-22-centos7:latest\" {",
				"// Node definitions.",
				"[label=\"BuildConfig|default/ruby-hello-world\"];",
				"[label=\"BuildConfig|test/ruby-sample-build\"];",
				"[label=\"ImageStreamTag|test/ruby-hello-world:latest\"];",
				"[label=\"ImageStreamTag|master/ruby-22-centos7:latest\"];",
				"[label=\"ImageStreamTag|another/origin-ruby-sample:latest\"];",
				"",
				"// Edge definitions.",
				"[label=\"BuildOutput\"];",
				"[label=\"BuildOutput\"];",
				"[label=\"BuildInputImage,BuildTriggerImage\"];",
				"[label=\"BuildInputImage,BuildTriggerImage\"];",
				"}",
			},
			expectedErr: nil,
		},
		{
			testName:         "human readable - multiple triggers - triggeronly",
			name:             "ruby-22-centos7",
			defaultNamespace: "test",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml",
			namespaces:       sets.NewString("test"),
			humanReadable: map[string]int{
				"istag/ruby-22-centos7:latest":   1,
				"\tbc/parent1":                   1,
				"\t\tistag/parent1img:latest":    1,
				"\t\t\tbc/child2":                2,
				"\t\t\t\tistag/child2img:latest": 2,
				"\tbc/parent2":                   1,
				"\t\tistag/parent2img:latest":    1,
				"\t\t\tbc/child3":                2,
				"\t\t\t\tistag/child3img:latest": 2,
				"\t\t\tbc/child1":                1,
				"\t\t\t\tistag/child1img:latest": 1,
				"\tbc/parent3":                   1,
				"\t\tistag/parent3img:latest":    1,
			},
		},
		{
			testName:         "human readable - multiple triggers - trigger+input",
			name:             "ruby-22-centos7",
			defaultNamespace: "test",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml",
			namespaces:       sets.NewString("test"),
			includeInputImg:  true,
			humanReadable: map[string]int{
				"istag/ruby-22-centos7:latest":   1,
				"\tbc/parent1":                   1,
				"\t\tistag/parent1img:latest":    1,
				"\t\t\tbc/child1":                2,
				"\t\t\t\tistag/child1img:latest": 2,
				"\t\t\tbc/child2":                2,
				"\t\t\t\tistag/child2img:latest": 2,
				"\t\t\tbc/child3":                3,
				"\t\t\t\tistag/child3img:latest": 3,
				"\tbc/parent2":                   1,
				"\t\tistag/parent2img:latest":    1,
				"\tbc/parent3":                   1,
				"\t\tistag/parent3img:latest":    1,
			},
		},
		{
			testName:         "human readable - multiple triggers - triggeronly - reverse",
			name:             "child2img",
			reverse:          true,
			defaultNamespace: "test",
			tag:              "latest",
			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml",
			namespaces:       sets.NewString("test"),
			humanReadable: map[string]int{
				"istag/child2img:latest":               1,
				"\tbc/child2":                          1,
				"\t\tistag/parent1img:latest":          1,
				"\t\t\tbc/parent1":                     1,
				"\t\t\t\tistag/ruby-22-centos7:latest": 2,
				"\t\tistag/parent3img:latest":          1,
				"\t\t\tbc/parent3":                     1,
			},
		},
	}

	for _, test := range tests {
		objs := []runtime.Object{}
		if len(test.path) > 0 {
			var err error
			objs, err = testclient.ReadObjectsFromPath(test.path, test.defaultNamespace, kapi.Codecs.UniversalDecoder(), kapi.Scheme)
			if err != nil {
				t.Fatal(err)
			}
		}
		oc, _, _ := testclient.NewFixtureClients(objs...)
		ist := imagegraph.MakeImageStreamTagObjectMeta(test.defaultNamespace, test.name, test.tag)

		desc, err := NewChainDescriber(oc, test.namespaces, test.output).Describe(ist, test.includeInputImg, test.reverse)
		t.Logf("%s: output:\n%s\n\n", test.testName, desc)
		if err != test.expectedErr {
			t.Fatalf("%s: error mismatch: expected %v, got %v", test.testName, test.expectedErr, err)
		}

		got := strings.Split(desc, "\n")

		switch test.output {
		case "dot":
			if len(test.dot) != len(got) {
				t.Fatalf("%s: expected %d lines, got %d:\n%s", test.testName, len(test.dot), len(got), desc)
			}
			for _, expected := range test.dot {
				if !strings.Contains(desc, expected) {
					t.Errorf("%s: unexpected description:\n%s\nexpected line in it:\n%s", test.testName, desc, expected)
				}
			}
		case "":
			if lenReadable(test.humanReadable) != len(got) {
				t.Fatalf("%s: expected %d lines, got %d:\n%s", test.testName, lenReadable(test.humanReadable), len(got), desc)
			}
			for _, line := range got {
				if _, ok := test.humanReadable[line]; !ok {
					t.Errorf("%s: unexpected line: %s", test.testName, line)
				}
				test.humanReadable[line]--
			}
			for line, cnt := range test.humanReadable {
				if cnt != 0 {
					t.Errorf("%s: unexpected number of lines for [%s]: %d", test.testName, line, cnt)
				}
			}
		}
	}
}