From 9bd12953a91efaa0636f810b1f36cff89f9009b8 Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Tue, 17 Jan 2017 09:50:12 -0700 Subject: [PATCH] feat(*): add kube client test * for WaitAndGetCompletedPodPhase --- cmd/helm/helm_test.go | 4 + pkg/kube/client.go | 6 +- pkg/kube/client_test.go | 135 +++++++++++++++------ pkg/tiller/environment/environment.go | 11 +- pkg/tiller/environment/environment_test.go | 3 + pkg/tiller/release_testing.go | 4 +- 6 files changed, 117 insertions(+), 46 deletions(-) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index fc4c86b68..758acd12d 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -190,6 +190,10 @@ func (c *fakeReleaseClient) ReleaseHistory(rlsName string, opts ...helm.HistoryO return &rls.GetHistoryResponse{Releases: c.rels}, c.err } +func (c *fakeReleaseClient) ReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (*rls.TestReleaseResponse, error) { + return nil, nil +} + func (c *fakeReleaseClient) Option(opt ...helm.Option) helm.Interface { return c } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 89d044ff1..0f4eb58ac 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -616,14 +616,16 @@ func scrubValidationError(err error) error { return err } -func (c *Client) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { +// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase +// and returns said phase (PodSucceeded or PodFailed qualify) +func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { infos, err := c.Build(namespace, reader) if err != nil { return api.PodUnknown, err } info := infos[0] - // TODO: should we be checking kind before hand? probably yes. + // TODO: should we be checking kind beforehand? probably yes. // TODO: add validation to linter: any manifest with a test hook has to be a pod kind? kind := info.Mapping.GroupVersionKind.Kind if kind != "Pod" { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 90cf4e6d5..0c9cf788b 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -18,6 +18,7 @@ package kube import ( "bytes" + "encoding/json" "io" "io/ioutil" "net/http" @@ -37,6 +38,8 @@ import ( "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + watchjson "k8s.io/kubernetes/pkg/watch/json" ) func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { @@ -44,10 +47,18 @@ func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { } func newPod(name string) api.Pod { + return newPodWithStatus(name, api.PodStatus{}, "") +} + +func newPodWithStatus(name string, status api.PodStatus, namespace string) api.Pod { + ns := api.NamespaceDefault + if namespace != "" { + ns = namespace + } return api.Pod{ ObjectMeta: api.ObjectMeta{ Name: name, - Namespace: api.NamespaceDefault, + Namespace: ns, }, Spec: api.PodSpec{ Containers: []api.Container{{ @@ -56,6 +67,7 @@ func newPod(name string) api.Pod { Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}}, }}, }, + Status: status, } } @@ -102,6 +114,32 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e return f.reaper, nil } +func newEventResponse(code int, e *watch.Event) (*http.Response, error) { + dispatchedEvent, err := encodeAndMarshalEvent(e) + if err != nil { + return nil, err + } + + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + body := ioutil.NopCloser(bytes.NewReader(dispatchedEvent)) + return &http.Response{StatusCode: 200, Header: header, Body: body}, nil +} + +func encodeAndMarshalEvent(e *watch.Event) ([]byte, error) { + encodedEvent, err := watchjson.Object(testapi.Default.Codec(), e) + if err != nil { + return nil, err + } + + marshaledEvent, err := json.Marshal(encodedEvent) + if err != nil { + return nil, err + } + + return marshaledEvent, nil +} + func TestUpdate(t *testing.T) { listA := newPodList("starfish", "otter", "squid") listB := newPodList("starfish", "otter", "dolphin") @@ -305,48 +343,69 @@ func TestPerform(t *testing.T) { } } -func TestWaitAndGetCompletedPodStatus(t *testing.T) { - f, tf, codec, ns := cmdtesting.NewAPIFactory() - actions := make(map[string]string) - testPodList := newPodList("bestpod") +func TestWaitAndGetCompletedPodPhase(t *testing.T) { + tests := []struct { + podPhase api.PodPhase + expectedPhase api.PodPhase + err bool + errMessage string + }{ + { + podPhase: api.PodPending, + expectedPhase: api.PodUnknown, + err: true, + errMessage: "timed out waiting for the condition", + }, { + podPhase: api.PodRunning, + expectedPhase: api.PodUnknown, + err: true, + errMessage: "timed out waiting for the condition", + }, { + podPhase: api.PodSucceeded, + expectedPhase: api.PodSucceeded, + }, { + podPhase: api.PodFailed, + expectedPhase: api.PodFailed, + }, + } - tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - p, m := req.URL.Path, req.Method - actions[p] = m - count := 0 - switch { - case p == "/namespaces/test/pods/bestpod" && m == "GET": - return newResponse(200, &testPodList.Items[0]) - case p == "/watch/namespaces/test/pods/bestpod" && m == "GET": - //TODO: fix response - count = count + 1 - if count == 1 { - //returns event running - return newResponse(200, &testPodList.Items[0]) - } - if count == 2 { - //return event succeeded + for _, tt := range tests { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + actions := make(map[string]string) + + var testPodList api.PodList + testPodList.Items = append(testPodList.Items, newPodWithStatus("bestpod", api.PodStatus{Phase: tt.podPhase}, "test")) + + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + actions[p] = m + switch { + case p == "/namespaces/test/pods/bestpod" && m == "GET": return newResponse(200, &testPodList.Items[0]) + case p == "/watch/namespaces/test/pods/bestpod" && m == "GET": + event := watch.Event{Type: watch.Added, Object: &testPodList.Items[0]} + return newEventResponse(200, &event) + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - - c := &Client{Factory: f} - //stub watchUntil to return no error + }), + } - status, err := c.WaitAndGetCompletedPodStatus("test", objBody(codec, &testPodList), 30*time.Second) - if err != nil { - t.Fatal(err) - } + c := &Client{Factory: f} - if status != api.PodSucceeded { - t.Fatal("Expected %s, got %s", api.PodSucceeded, status) + phase, err := c.WaitAndGetCompletedPodPhase("test", objBody(codec, &testPodList), 1*time.Second) + if (err != nil) != tt.err { + t.Fatalf("Expected error but there was none.") + } + if err != nil && err.Error() != tt.errMessage { + t.Fatalf("Expected error %s, got %s", tt.errMessage, err.Error()) + } + if phase != tt.expectedPhase { + t.Fatalf("Expected pod phase %s, got %s", tt.expectedPhase, phase) + } } } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 010d14060..fa5d1ecab 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -138,8 +138,9 @@ type KubeClient interface { Build(namespace string, reader io.Reader) (kube.Result, error) - //TODO: insert description - WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) + // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase + // and returns said phase (PodSucceeded or PodFailed qualify) + WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -185,8 +186,10 @@ func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, er return []*resource.Info{}, nil } -func (p *PrintingKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { - return "", nil +// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase +func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + _, err := io.Copy(p.Out, reader) + return api.PodUnknown, err } // Environment provides the context for executing a client request. diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index 7544a3938..cb36de356 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -57,6 +57,9 @@ func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } +func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + return api.PodUnknown, nil +} func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { return "", nil diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go index 90379887a..ff601c697 100644 --- a/pkg/tiller/release_testing.go +++ b/pkg/tiller/release_testing.go @@ -82,7 +82,7 @@ func runReleaseTests(tests []string, rel *release.Release, kube environment.Kube if resourceCreated { b.Reset() b.WriteString(h) - status, err = kube.WaitAndGetCompletedPodStatus(rel.Namespace, b, time.Duration(timeout)*time.Second) + status, err = kube.WaitAndGetCompletedPodPhase(rel.Namespace, b, time.Duration(timeout)*time.Second) if err != nil { resourceCleanExit = false log.Printf("Error getting status for pod %s: %s", ts.Name, err) @@ -145,7 +145,7 @@ func filterTests(hooks []*release.Hook, releaseName string) ([]*release.Hook, er } //TODO: probably don't need to check found - if found == false && len(testHooks) == 0 { + if !found && len(testHooks) == 0 { return nil, notFoundErr }