diff --git a/_proto/hapi/release/test_run.proto b/_proto/hapi/release/test_run.proto index 60734ae03..52707236b 100644 --- a/_proto/hapi/release/test_run.proto +++ b/_proto/hapi/release/test_run.proto @@ -34,4 +34,5 @@ message TestRun { string info = 3; google.protobuf.Timestamp started_at = 4; google.protobuf.Timestamp completed_at = 5; + string log = 6; } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index cd5227dd7..0f3191351 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -686,6 +686,37 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf return err } +//GetPodLogs get the log of a pod and return the log in string format +func (c *Client) GetPodLogs(namespace string, reader io.Reader) (string, 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, &core.PodLogOptions{}) + + logData, err := log.DoRaw() + + if err != nil { + return "", err + } + + return string(logData), nil +} + //get an kubernetes resources's relation pods // kubernetes resource used select labels to relate pods func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) { diff --git a/pkg/proto/hapi/release/test_run.pb.go b/pkg/proto/hapi/release/test_run.pb.go index 4d39d17c2..a317e5a60 100644 --- a/pkg/proto/hapi/release/test_run.pb.go +++ b/pkg/proto/hapi/release/test_run.pb.go @@ -1,6 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: hapi/release/test_run.proto +// source: test_run.proto +/* +Package release is a generated protocol buffer package. + +It is generated from these files: + test_run.proto + +It has these top-level messages: + TestRun +*/ package release import proto "github.com/golang/protobuf/proto" @@ -13,6 +22,12 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + type TestRun_Status int32 const ( @@ -38,7 +53,7 @@ var TestRun_Status_value = map[string]int32{ func (x TestRun_Status) String() string { return proto.EnumName(TestRun_Status_name, int32(x)) } -func (TestRun_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} } +func (TestRun_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } type TestRun struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` @@ -46,12 +61,13 @@ type TestRun struct { Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"` StartedAt *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=started_at,json=startedAt" json:"started_at,omitempty"` CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"` + Log string `protobuf:"bytes,6,opt,name=log" json:"log,omitempty"` } func (m *TestRun) Reset() { *m = TestRun{} } func (m *TestRun) String() string { return proto.CompactTextString(m) } func (*TestRun) ProtoMessage() {} -func (*TestRun) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} } +func (*TestRun) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *TestRun) GetName() string { if m != nil { @@ -88,31 +104,38 @@ func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp { return nil } +func (m *TestRun) GetLog() string { + if m != nil { + return m.Log + } + return "" +} + func init() { proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun") proto.RegisterEnum("hapi.release.TestRun_Status", TestRun_Status_name, TestRun_Status_value) } -func init() { proto.RegisterFile("hapi/release/test_run.proto", fileDescriptor4) } +func init() { proto.RegisterFile("test_run.proto", fileDescriptor4) } var fileDescriptor4 = []byte{ - // 274 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4b, 0xfb, 0x30, - 0x1c, 0xc5, 0x7f, 0xe9, 0xf6, 0x6b, 0x69, 0x3a, 0xa4, 0xe4, 0x54, 0xa6, 0x60, 0xd9, 0xa9, 0xa7, - 0x14, 0xa6, 0x17, 0x41, 0x0f, 0x75, 0x4c, 0x19, 0x4a, 0x84, 0x74, 0x45, 0xf0, 0x32, 0x32, 0xcd, - 0x66, 0xa1, 0x6d, 0x4a, 0xf3, 0xed, 0xdf, 0xe3, 0xbf, 0x2a, 0x69, 0x33, 0xf1, 0xe6, 0xed, 0xfb, - 0x78, 0x9f, 0xf7, 0xf2, 0x82, 0xcf, 0x3f, 0x45, 0x5b, 0xa6, 0x9d, 0xac, 0xa4, 0xd0, 0x32, 0x05, - 0xa9, 0x61, 0xd7, 0xf5, 0x0d, 0x6d, 0x3b, 0x05, 0x8a, 0xcc, 0x8c, 0x49, 0xad, 0x39, 0xbf, 0x3c, - 0x2a, 0x75, 0xac, 0x64, 0x3a, 0x78, 0xfb, 0xfe, 0x90, 0x42, 0x59, 0x4b, 0x0d, 0xa2, 0x6e, 0x47, - 0x7c, 0xf1, 0xe5, 0x60, 0x6f, 0x2b, 0x35, 0xf0, 0xbe, 0x21, 0x04, 0x4f, 0x1b, 0x51, 0xcb, 0x08, - 0xc5, 0x28, 0xf1, 0xf9, 0x70, 0x93, 0x6b, 0xec, 0x6a, 0x10, 0xd0, 0xeb, 0xc8, 0x89, 0x51, 0x72, - 0xb6, 0xbc, 0xa0, 0xbf, 0xfb, 0xa9, 0x8d, 0xd2, 0x7c, 0x60, 0xb8, 0x65, 0x4d, 0x53, 0xd9, 0x1c, - 0x54, 0x34, 0x19, 0x9b, 0xcc, 0x4d, 0x6e, 0x30, 0xd6, 0x20, 0x3a, 0x90, 0x1f, 0x3b, 0x01, 0xd1, - 0x34, 0x46, 0x49, 0xb0, 0x9c, 0xd3, 0x71, 0x1f, 0x3d, 0xed, 0xa3, 0xdb, 0xd3, 0x3e, 0xee, 0x5b, - 0x3a, 0x03, 0x72, 0x87, 0x67, 0xef, 0xaa, 0x6e, 0x2b, 0x69, 0xc3, 0xff, 0xff, 0x0c, 0x07, 0x3f, - 0x7c, 0x06, 0x8b, 0x5b, 0xec, 0x8e, 0xfb, 0x48, 0x80, 0xbd, 0x82, 0x3d, 0xb1, 0x97, 0x57, 0x16, - 0xfe, 0x33, 0x22, 0x2f, 0x56, 0xab, 0x75, 0x9e, 0x87, 0xc8, 0x88, 0x87, 0x6c, 0xf3, 0x5c, 0xf0, - 0x75, 0xe8, 0x18, 0xc1, 0x0b, 0xc6, 0x36, 0xec, 0x31, 0x9c, 0xdc, 0xfb, 0x6f, 0x9e, 0xfd, 0xed, - 0xde, 0x1d, 0x5e, 0xba, 0xfa, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x31, 0x86, 0x46, 0xdb, 0x81, 0x01, - 0x00, 0x00, + // 278 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xc3, 0x30, + 0x1c, 0xc5, 0x6d, 0x3b, 0x5b, 0x9a, 0x8e, 0x51, 0x72, 0x2a, 0x43, 0xb0, 0xec, 0xd4, 0x53, 0x06, + 0xd3, 0x8b, 0xa0, 0x87, 0x3a, 0xa6, 0x0c, 0x25, 0x42, 0xba, 0x22, 0x78, 0x19, 0x99, 0x66, 0xb5, + 0xd0, 0x36, 0xa5, 0xf9, 0xf7, 0x83, 0xf9, 0x0d, 0x25, 0x6d, 0x26, 0xde, 0x76, 0x7b, 0x8f, 0xbc, + 0xf7, 0xf2, 0xfb, 0xa3, 0x19, 0x08, 0x05, 0xfb, 0xae, 0x6f, 0x48, 0xdb, 0x49, 0x90, 0x78, 0xfa, + 0xcd, 0xdb, 0x92, 0x74, 0xa2, 0x12, 0x5c, 0x89, 0xf9, 0x75, 0x21, 0x65, 0x51, 0x89, 0xe5, 0xf0, + 0x76, 0xe8, 0x8f, 0x4b, 0x28, 0x6b, 0xa1, 0x80, 0xd7, 0xed, 0x18, 0x5f, 0xfc, 0xd8, 0xc8, 0xdb, + 0x09, 0x05, 0xac, 0x6f, 0x30, 0x46, 0x93, 0x86, 0xd7, 0x22, 0xb2, 0x62, 0x2b, 0xf1, 0xd9, 0xa0, + 0xf1, 0x2d, 0x72, 0x15, 0x70, 0xe8, 0x55, 0x64, 0xc7, 0x56, 0x32, 0x5b, 0x5d, 0x91, 0xff, 0xfb, + 0xc4, 0x54, 0x49, 0x36, 0x64, 0x98, 0xc9, 0xea, 0xa5, 0xb2, 0x39, 0xca, 0xc8, 0x19, 0x97, 0xb4, + 0xc6, 0x77, 0x08, 0x29, 0xe0, 0x1d, 0x88, 0xaf, 0x3d, 0x87, 0x68, 0x12, 0x5b, 0x49, 0xb0, 0x9a, + 0x93, 0x91, 0x8f, 0x9c, 0xf8, 0xc8, 0xee, 0xc4, 0xc7, 0x7c, 0x93, 0x4e, 0x01, 0x3f, 0xa0, 0xe9, + 0xa7, 0xac, 0xdb, 0x4a, 0x98, 0xf2, 0xe5, 0xd9, 0x72, 0xf0, 0x97, 0x4f, 0x01, 0x87, 0xc8, 0xa9, + 0x64, 0x11, 0xb9, 0x03, 0x8c, 0x96, 0x8b, 0x7b, 0xe4, 0x8e, 0xc4, 0x38, 0x40, 0x5e, 0x4e, 0x5f, + 0xe8, 0xdb, 0x3b, 0x0d, 0x2f, 0xb4, 0xc9, 0xf2, 0xf5, 0x7a, 0x93, 0x65, 0xa1, 0xa5, 0xcd, 0x53, + 0xba, 0x7d, 0xcd, 0xd9, 0x26, 0xb4, 0xb5, 0x61, 0x39, 0xa5, 0x5b, 0xfa, 0x1c, 0x3a, 0x8f, 0xfe, + 0x87, 0x67, 0xee, 0x3f, 0xb8, 0xc3, 0xdf, 0x37, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x3d, + 0x93, 0x66, 0x86, 0x01, 0x00, 0x00, } diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index 3b3d07933..515f5a422 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -62,14 +62,24 @@ func (env *Environment) getTestPodStatus(test *test) (core.PodPhase, error) { return status, err } +func (env *Environment) getTestPodsLogs(test *test) (string, error) { + b := bytes.NewBufferString(test.manifest) + log, err := env.KubeClient.GetPodLogs(env.Namespace, b) + if err != nil { + return "", err + } + + return log, nil +} + func (env *Environment) streamResult(r *release.TestRun) error { switch r.Status { case release.TestRun_SUCCESS: - if err := env.streamSuccess(r.Name); err != nil { + if err := env.streamSuccess(r.Name, r.Log); err != nil { return err } case release.TestRun_FAILURE: - if err := env.streamFailed(r.Name); err != nil { + if err := env.streamFailed(r.Name, r.Log); err != nil { return err } @@ -91,13 +101,13 @@ func (env *Environment) streamError(info string) error { return env.streamMessage(msg, release.TestRun_FAILURE) } -func (env *Environment) streamFailed(name string) error { - msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, env.Namespace) +func (env *Environment) streamFailed(name, podLog string) error { + msg := fmt.Sprintf("FAILED: %s\n%s", name, podLog) return env.streamMessage(msg, release.TestRun_FAILURE) } -func (env *Environment) streamSuccess(name string) error { - msg := fmt.Sprintf("PASSED: %s", name) +func (env *Environment) streamSuccess(name, podLog string) error { + msg := fmt.Sprintf("PASSED: %s\n %s", name, podLog) return env.streamMessage(msg, release.TestRun_SUCCESS) } diff --git a/pkg/releasetesting/environment_test.go b/pkg/releasetesting/environment_test.go index 29ca93d09..eba6097e3 100644 --- a/pkg/releasetesting/environment_test.go +++ b/pkg/releasetesting/environment_test.go @@ -153,6 +153,10 @@ func (p *getFailingKubeClient) Get(ns string, r io.Reader) (string, error) { return "", errors.New("in the end, they did not find Nemo") } +func (p *getFailingKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) { + return "", nil +} + type deleteFailingKubeClient struct { tillerEnv.PrintingKubeClient } @@ -167,6 +171,10 @@ func (p *deleteFailingKubeClient) Delete(ns string, r io.Reader) error { return errors.New("delete failed") } +func (p *deleteFailingKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) { + return "", nil +} + type createFailingKubeClient struct { tillerEnv.PrintingKubeClient } @@ -177,6 +185,10 @@ func newCreateFailingKubeClient() *createFailingKubeClient { } } +func (p *createFailingKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) { + return "", nil +} + func (p *createFailingKubeClient) Create(ns string, r io.Reader, t int64, shouldWait bool) error { return errors.New("We ran out of budget and couldn't create finding-nemo") } diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index 2e42400ce..0a943ae38 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -102,10 +102,21 @@ func (ts *TestSuite) Run(env *Environment) error { } if resourceCreated && resourceCleanExit { + if err := test.assignTestResult(status); err != nil { return err } + log, err := env.getTestPodsLogs(test) + if err != nil { + if streamErr := env.streamError("Fail to get testing pod log"); streamErr != nil { + return streamErr + } + return err + } + + test.assignTestLog(log) + if err := env.streamResult(test.result); err != nil { return err } @@ -140,6 +151,10 @@ func (t *test) assignTestResult(podStatus core.PodPhase) error { return nil } +func (t *test) assignTestLog(log string) { + t.result.Log = log +} + func expectedSuccess(hookTypes []string) (bool, error) { for _, hookType := range hookTypes { hookType = strings.ToLower(strings.TrimSpace(hookType)) diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go index d83cd2666..9a7ad42d4 100644 --- a/pkg/releasetesting/test_suite_test.go +++ b/pkg/releasetesting/test_suite_test.go @@ -120,6 +120,10 @@ func TestRun(t *testing.T) { t.Errorf("Expected test result to be successful, got: %v", result.Status) } + if result.Log != "pod with succeed kube client" { + t.Errorf("Expected test log to be pod with succeed kube client. Got: %v", result.Log) + } + result2 := ts.Results[1] if result2.StartedAt == nil { t.Errorf("Expected test StartedAt to not be nil. Got: %v", result2.StartedAt) @@ -137,6 +141,9 @@ func TestRun(t *testing.T) { t.Errorf("Expected test result to be successful, got: %v", result2.Status) } + if result2.Log != "pod with succeed kube client" { + t.Errorf("Expected test log to be pod with succeed kube client. Got: %v", result.Log) + } } func TestRunEmptyTestSuite(t *testing.T) { @@ -206,6 +213,10 @@ func TestRunSuccessWithTestFailureHook(t *testing.T) { if result.Status != release.TestRun_SUCCESS { t.Errorf("Expected test result to be successful, got: %v", result.Status) } + + if result.Log != "pod with failed kube client" { + t.Errorf("Expected test log to be pod with succeed kube client. Got: %v", result.Log) + } } func TestExtractTestManifestsFromHooks(t *testing.T) { @@ -324,6 +335,10 @@ func newPodSucceededKubeClient() *podSucceededKubeClient { } } +func (p *podSucceededKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) { + return "pod with succeed kube client", nil +} + func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) { return core.PodSucceeded, nil } @@ -338,6 +353,10 @@ func newPodFailedKubeClient() *podFailedKubeClient { } } +func (p *podFailedKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) { + return "pod with failed kube client", 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..d134ee5f9 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 get the log of a pod and return the log in string format + GetPodLogs(namespace string, reader io.Reader) (string, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -177,6 +180,12 @@ func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int return err } +//GetPodLogs prints the values of what would be created with a real KubeClient. +func (p *PrintingKubeClient) GetPodLogs(namespace string, r io.Reader) (string, error) { + _, err := io.Copy(p.Out, r) + return "", err +} + // Update implements KubeClient Update. func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { _, err := io.Copy(p.Out, modifiedReader) diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index d8c82b901..86c5d1329 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -55,6 +55,9 @@ func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Read func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { return nil } +func (k *mockKubeClient) GetPodLogs(namespace string, r io.Reader) (string, error) { + return "", nil +} func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { return []*resource.Info{}, nil }