From 6a86e5249b17693233800e7f735bafc144690799 Mon Sep 17 00:00:00 2001 From: LiPing Gao Date: Fri, 6 Apr 2018 13:15:47 +0200 Subject: [PATCH] fix(helm): add log of failing test pod to 'helm test' When 'helm test' fails, this will print the log of the failing test pods Closes #1957 --- pkg/kube/client.go | 34 ++++++++++++++++++++++ pkg/releasetesting/environment.go | 6 ++++ pkg/releasetesting/environment_test.go | 13 +++++++++ pkg/releasetesting/test_suite.go | 7 ++++- pkg/releasetesting/test_suite_test.go | 8 +++++ pkg/tiller/environment/environment.go | 9 ++++++ pkg/tiller/environment/environment_test.go | 4 +++ pkg/tiller/release_server_test.go | 4 +++ 8 files changed, 84 insertions(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index e4e2dc1bb..7b3e52e5d 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -47,12 +47,14 @@ import ( "k8s.io/client-go/tools/clientcmd" batchinternal "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/core" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/conditions" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/printers" + "os" ) const ( @@ -678,6 +680,38 @@ func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, return status, nil } +//GetPodLogs print the log of a pod to standard output +func (c *Client) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + infos, err := c.Build(namespace, reader) + if err != nil { + return err + } + info := infos[0] + + kind := info.Mapping.GroupVersionKind.Kind + if kind != "Pod" { + return fmt.Errorf("%s is not a Pod", info.Name) + } + + pod := info.Object.(*core.Pod) + + cl, err := c.ClientSet() + if err != nil { + return err + } + + log := cl.Core().Pods(namespace).GetLogs(pod.Name, &api.PodLogOptions{}) + + readCloser, err := log.Stream() + if err != nil { + return err + } + defer readCloser.Close() + + _, err = io.Copy(os.Stdout, readCloser) + return err +} + func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) if err != nil { diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index 3b3d07933..1cf54e8b2 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -62,6 +62,12 @@ func (env *Environment) getTestPodStatus(test *test) (core.PodPhase, error) { return status, err } +func (env *Environment) getTestPodsLogs(test *test) error { + b := bytes.NewBufferString(test.manifest) + err := env.KubeClient.GetPodLogs(env.Namespace, b, time.Duration(env.Timeout)*time.Second) + return err +} + func (env *Environment) streamResult(r *release.TestRun) error { switch r.Status { case release.TestRun_SUCCESS: diff --git a/pkg/releasetesting/environment_test.go b/pkg/releasetesting/environment_test.go index 0199b74eb..e38053926 100644 --- a/pkg/releasetesting/environment_test.go +++ b/pkg/releasetesting/environment_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" tillerEnv "k8s.io/helm/pkg/tiller/environment" + "time" ) func TestCreateTestPodSuccess(t *testing.T) { @@ -143,6 +144,10 @@ type getFailingKubeClient struct { tillerEnv.PrintingKubeClient } +func (p *getFailingKubeClient) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + return nil +} + func newGetFailingKubeClient() *getFailingKubeClient { return &getFailingKubeClient{ PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, @@ -157,6 +162,10 @@ type deleteFailingKubeClient struct { tillerEnv.PrintingKubeClient } +func (p *deleteFailingKubeClient) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + return nil +} + func newDeleteFailingKubeClient() *deleteFailingKubeClient { return &deleteFailingKubeClient{ PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, @@ -171,6 +180,10 @@ type createFailingKubeClient struct { tillerEnv.PrintingKubeClient } +func (p *createFailingKubeClient) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + return nil +} + func newCreateFailingKubeClient() *createFailingKubeClient { return &createFailingKubeClient{ PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index 2e42400ce..2f56ae544 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -95,7 +95,12 @@ func (ts *TestSuite) Run(env *Environment) error { status, err = env.getTestPodStatus(test) if err != nil { resourceCleanExit = false - if streamErr := env.streamError(test.result.Info); streamErr != nil { + streamErr := env.streamError(test.result.Info) + logErr := env.getTestPodsLogs(test) + if logErr != nil { + fmt.Println("Fail to get log for the failing test") + } + if streamErr != nil { return streamErr } } diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go index e6cc8bcf5..e38c78bc1 100644 --- a/pkg/releasetesting/test_suite_test.go +++ b/pkg/releasetesting/test_suite_test.go @@ -318,6 +318,10 @@ type podSucceededKubeClient struct { tillerEnv.PrintingKubeClient } +func (p *podSucceededKubeClient) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + return nil +} + func newPodSucceededKubeClient() *podSucceededKubeClient { return &podSucceededKubeClient{ PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, @@ -338,6 +342,10 @@ func newPodFailedKubeClient() *podFailedKubeClient { } } +func (p *podFailedKubeClient) GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error { + return nil +} + func (p *podFailedKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) { return core.PodFailed, nil } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 366fdf522..458275c34 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -143,6 +143,9 @@ type KubeClient interface { // 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) (core.PodPhase, error) + + // GetPodLogs gets the log of a pod and print it out to standard output + GetPodLogs(namespace string, reader io.Reader, timeout time.Duration) error } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -151,6 +154,12 @@ type PrintingKubeClient struct { Out io.Writer } +//GetPodLogs prints the values of what would be created with a real KubeClient. +func (p *PrintingKubeClient) GetPodLogs(namespace string, r io.Reader, timeout time.Duration) error { + _, err := io.Copy(p.Out, r) + return err +} + // Create prints the values of what would be created with a real KubeClient. func (p *PrintingKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { _, err := io.Copy(p.Out, r) diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index d8c82b901..75146623e 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -40,6 +40,10 @@ func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]s type mockKubeClient struct{} +func (k *mockKubeClient) GetPodLogs(namespace string, r io.Reader, timeout time.Duration) error { + return nil +} + func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { return nil } diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 6c4d42e04..7de8aed08 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -363,6 +363,10 @@ type mockHooksKubeClient struct { Resources map[string]*mockHooksManifest } +func (kc *mockHooksKubeClient) GetPodLogs(namespace string, r io.Reader, timeout time.Duration) error { + return nil +} + var errResourceExists = errors.New("resource already exists") func (kc *mockHooksKubeClient) makeManifest(r io.Reader) (*mockHooksManifest, error) {