fix(helm): add log of test pod to 'helm test'

When 'helm test' finishes, this will print the log of the test pods

Closes #1957
pull/3907/head
LiPing Gao 8 years ago
parent a80231648a
commit 7e6303deff

@ -34,4 +34,5 @@ message TestRun {
string info = 3; string info = 3;
google.protobuf.Timestamp started_at = 4; google.protobuf.Timestamp started_at = 4;
google.protobuf.Timestamp completed_at = 5; google.protobuf.Timestamp completed_at = 5;
string log = 6;
} }

@ -686,6 +686,37 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf
return err 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 //get an kubernetes resources's relation pods
// kubernetes resource used select labels to relate 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) { func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) {

@ -1,6 +1,15 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // 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 package release
import proto "github.com/golang/protobuf/proto" import proto "github.com/golang/protobuf/proto"
@ -13,6 +22,12 @@ var _ = proto.Marshal
var _ = fmt.Errorf var _ = fmt.Errorf
var _ = math.Inf 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 type TestRun_Status int32
const ( const (
@ -38,7 +53,7 @@ var TestRun_Status_value = map[string]int32{
func (x TestRun_Status) String() string { func (x TestRun_Status) String() string {
return proto.EnumName(TestRun_Status_name, int32(x)) 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 { type TestRun struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 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"` 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"` 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"` 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) Reset() { *m = TestRun{} }
func (m *TestRun) String() string { return proto.CompactTextString(m) } func (m *TestRun) String() string { return proto.CompactTextString(m) }
func (*TestRun) ProtoMessage() {} 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 { func (m *TestRun) GetName() string {
if m != nil { if m != nil {
@ -88,31 +104,38 @@ func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp {
return nil return nil
} }
func (m *TestRun) GetLog() string {
if m != nil {
return m.Log
}
return ""
}
func init() { func init() {
proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun") proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun")
proto.RegisterEnum("hapi.release.TestRun_Status", TestRun_Status_name, TestRun_Status_value) 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{ var fileDescriptor4 = []byte{
// 274 bytes of a gzipped FileDescriptorProto // 278 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4b, 0xfb, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xc3, 0x30,
0x1c, 0xc5, 0x7f, 0xe9, 0xf6, 0x6b, 0x69, 0x3a, 0xa4, 0xe4, 0x54, 0xa6, 0x60, 0xd9, 0xa9, 0xa7, 0x1c, 0xc5, 0x6d, 0x3b, 0x5b, 0x9a, 0x8e, 0x51, 0x72, 0x2a, 0x43, 0xb0, 0xec, 0xd4, 0x53, 0x06,
0x14, 0xa6, 0x17, 0x41, 0x0f, 0x75, 0x4c, 0x19, 0x4a, 0x84, 0x74, 0x45, 0xf0, 0x32, 0x32, 0xcd, 0xd3, 0x8b, 0xa0, 0x87, 0x3a, 0xa6, 0x0c, 0x25, 0x42, 0xba, 0x22, 0x78, 0x19, 0x99, 0x66, 0xb5,
0x66, 0xa1, 0x6d, 0x4a, 0xf3, 0xed, 0xdf, 0xe3, 0xbf, 0x2a, 0x69, 0x33, 0xf1, 0xe6, 0xed, 0xfb, 0xd0, 0x36, 0xa5, 0xf9, 0xf7, 0x83, 0xf9, 0x0d, 0x25, 0x6d, 0x26, 0xde, 0x76, 0x7b, 0x8f, 0xbc,
0x78, 0x9f, 0xf7, 0xf2, 0x82, 0xcf, 0x3f, 0x45, 0x5b, 0xa6, 0x9d, 0xac, 0xa4, 0xd0, 0x32, 0x05, 0xf7, 0xf2, 0xfb, 0xa3, 0x19, 0x08, 0x05, 0xfb, 0xae, 0x6f, 0x48, 0xdb, 0x49, 0x90, 0x78, 0xfa,
0xa9, 0x61, 0xd7, 0xf5, 0x0d, 0x6d, 0x3b, 0x05, 0x8a, 0xcc, 0x8c, 0x49, 0xad, 0x39, 0xbf, 0x3c, 0xcd, 0xdb, 0x92, 0x74, 0xa2, 0x12, 0x5c, 0x89, 0xf9, 0x75, 0x21, 0x65, 0x51, 0x89, 0xe5, 0xf0,
0x2a, 0x75, 0xac, 0x64, 0x3a, 0x78, 0xfb, 0xfe, 0x90, 0x42, 0x59, 0x4b, 0x0d, 0xa2, 0x6e, 0x47, 0x76, 0xe8, 0x8f, 0x4b, 0x28, 0x6b, 0xa1, 0x80, 0xd7, 0xed, 0x18, 0x5f, 0xfc, 0xd8, 0xc8, 0xdb,
0x7c, 0xf1, 0xe5, 0x60, 0x6f, 0x2b, 0x35, 0xf0, 0xbe, 0x21, 0x04, 0x4f, 0x1b, 0x51, 0xcb, 0x08, 0x09, 0x05, 0xac, 0x6f, 0x30, 0x46, 0x93, 0x86, 0xd7, 0x22, 0xb2, 0x62, 0x2b, 0xf1, 0xd9, 0xa0,
0xc5, 0x28, 0xf1, 0xf9, 0x70, 0x93, 0x6b, 0xec, 0x6a, 0x10, 0xd0, 0xeb, 0xc8, 0x89, 0x51, 0x72, 0xf1, 0x2d, 0x72, 0x15, 0x70, 0xe8, 0x55, 0x64, 0xc7, 0x56, 0x32, 0x5b, 0x5d, 0x91, 0xff, 0xfb,
0xb6, 0xbc, 0xa0, 0xbf, 0xfb, 0xa9, 0x8d, 0xd2, 0x7c, 0x60, 0xb8, 0x65, 0x4d, 0x53, 0xd9, 0x1c, 0xc4, 0x54, 0x49, 0x36, 0x64, 0x98, 0xc9, 0xea, 0xa5, 0xb2, 0x39, 0xca, 0xc8, 0x19, 0x97, 0xb4,
0x54, 0x34, 0x19, 0x9b, 0xcc, 0x4d, 0x6e, 0x30, 0xd6, 0x20, 0x3a, 0x90, 0x1f, 0x3b, 0x01, 0xd1, 0xc6, 0x77, 0x08, 0x29, 0xe0, 0x1d, 0x88, 0xaf, 0x3d, 0x87, 0x68, 0x12, 0x5b, 0x49, 0xb0, 0x9a,
0x34, 0x46, 0x49, 0xb0, 0x9c, 0xd3, 0x71, 0x1f, 0x3d, 0xed, 0xa3, 0xdb, 0xd3, 0x3e, 0xee, 0x5b, 0x93, 0x91, 0x8f, 0x9c, 0xf8, 0xc8, 0xee, 0xc4, 0xc7, 0x7c, 0x93, 0x4e, 0x01, 0x3f, 0xa0, 0xe9,
0x3a, 0x03, 0x72, 0x87, 0x67, 0xef, 0xaa, 0x6e, 0x2b, 0x69, 0xc3, 0xff, 0xff, 0x0c, 0x07, 0x3f, 0xa7, 0xac, 0xdb, 0x4a, 0x98, 0xf2, 0xe5, 0xd9, 0x72, 0xf0, 0x97, 0x4f, 0x01, 0x87, 0xc8, 0xa9,
0x7c, 0x06, 0x8b, 0x5b, 0xec, 0x8e, 0xfb, 0x48, 0x80, 0xbd, 0x82, 0x3d, 0xb1, 0x97, 0x57, 0x16, 0x64, 0x11, 0xb9, 0x03, 0x8c, 0x96, 0x8b, 0x7b, 0xe4, 0x8e, 0xc4, 0x38, 0x40, 0x5e, 0x4e, 0x5f,
0xfe, 0x33, 0x22, 0x2f, 0x56, 0xab, 0x75, 0x9e, 0x87, 0xc8, 0x88, 0x87, 0x6c, 0xf3, 0x5c, 0xf0, 0xe8, 0xdb, 0x3b, 0x0d, 0x2f, 0xb4, 0xc9, 0xf2, 0xf5, 0x7a, 0x93, 0x65, 0xa1, 0xa5, 0xcd, 0x53,
0x75, 0xe8, 0x18, 0xc1, 0x0b, 0xc6, 0x36, 0xec, 0x31, 0x9c, 0xdc, 0xfb, 0x6f, 0x9e, 0xfd, 0xed, 0xba, 0x7d, 0xcd, 0xd9, 0x26, 0xb4, 0xb5, 0x61, 0x39, 0xa5, 0x5b, 0xfa, 0x1c, 0x3a, 0x8f, 0xfe,
0xde, 0x1d, 0x5e, 0xba, 0xfa, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x31, 0x86, 0x46, 0xdb, 0x81, 0x01, 0x87, 0x67, 0xee, 0x3f, 0xb8, 0xc3, 0xdf, 0x37, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x3d,
0x00, 0x00, 0x93, 0x66, 0x86, 0x01, 0x00, 0x00,
} }

@ -62,14 +62,24 @@ func (env *Environment) getTestPodStatus(test *test) (core.PodPhase, error) {
return status, err 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 { func (env *Environment) streamResult(r *release.TestRun) error {
switch r.Status { switch r.Status {
case release.TestRun_SUCCESS: case release.TestRun_SUCCESS:
if err := env.streamSuccess(r.Name); err != nil { if err := env.streamSuccess(r.Name, r.Log); err != nil {
return err return err
} }
case release.TestRun_FAILURE: case release.TestRun_FAILURE:
if err := env.streamFailed(r.Name); err != nil { if err := env.streamFailed(r.Name, r.Log); err != nil {
return err return err
} }
@ -91,13 +101,13 @@ func (env *Environment) streamError(info string) error {
return env.streamMessage(msg, release.TestRun_FAILURE) return env.streamMessage(msg, release.TestRun_FAILURE)
} }
func (env *Environment) streamFailed(name string) error { func (env *Environment) streamFailed(name, podLog string) error {
msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, env.Namespace) msg := fmt.Sprintf("FAILED: %s\n%s", name, podLog)
return env.streamMessage(msg, release.TestRun_FAILURE) return env.streamMessage(msg, release.TestRun_FAILURE)
} }
func (env *Environment) streamSuccess(name string) error { func (env *Environment) streamSuccess(name, podLog string) error {
msg := fmt.Sprintf("PASSED: %s", name) msg := fmt.Sprintf("PASSED: %s\n %s", name, podLog)
return env.streamMessage(msg, release.TestRun_SUCCESS) return env.streamMessage(msg, release.TestRun_SUCCESS)
} }

@ -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") 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 { type deleteFailingKubeClient struct {
tillerEnv.PrintingKubeClient tillerEnv.PrintingKubeClient
} }
@ -167,6 +171,10 @@ func (p *deleteFailingKubeClient) Delete(ns string, r io.Reader) error {
return errors.New("delete failed") return errors.New("delete failed")
} }
func (p *deleteFailingKubeClient) GetPodLogs(namespace string, reader io.Reader) (string, error) {
return "", nil
}
type createFailingKubeClient struct { type createFailingKubeClient struct {
tillerEnv.PrintingKubeClient 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 { 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") return errors.New("We ran out of budget and couldn't create finding-nemo")
} }

@ -102,10 +102,21 @@ func (ts *TestSuite) Run(env *Environment) error {
} }
if resourceCreated && resourceCleanExit { if resourceCreated && resourceCleanExit {
if err := test.assignTestResult(status); err != nil { if err := test.assignTestResult(status); err != nil {
return err 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 { if err := env.streamResult(test.result); err != nil {
return err return err
} }
@ -140,6 +151,10 @@ func (t *test) assignTestResult(podStatus core.PodPhase) error {
return nil return nil
} }
func (t *test) assignTestLog(log string) {
t.result.Log = log
}
func expectedSuccess(hookTypes []string) (bool, error) { func expectedSuccess(hookTypes []string) (bool, error) {
for _, hookType := range hookTypes { for _, hookType := range hookTypes {
hookType = strings.ToLower(strings.TrimSpace(hookType)) hookType = strings.ToLower(strings.TrimSpace(hookType))

@ -120,6 +120,10 @@ func TestRun(t *testing.T) {
t.Errorf("Expected test result to be successful, got: %v", result.Status) 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] result2 := ts.Results[1]
if result2.StartedAt == nil { if result2.StartedAt == nil {
t.Errorf("Expected test StartedAt to not be nil. Got: %v", result2.StartedAt) 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) 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) { func TestRunEmptyTestSuite(t *testing.T) {
@ -206,6 +213,10 @@ func TestRunSuccessWithTestFailureHook(t *testing.T) {
if result.Status != release.TestRun_SUCCESS { if result.Status != release.TestRun_SUCCESS {
t.Errorf("Expected test result to be successful, got: %v", result.Status) 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) { 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) { func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) {
return core.PodSucceeded, nil 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) { func (p *podFailedKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) {
return core.PodFailed, nil return core.PodFailed, nil
} }

@ -143,6 +143,9 @@ type KubeClient interface {
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
// and returns said phase (PodSucceeded or PodFailed qualify). // and returns said phase (PodSucceeded or PodFailed qualify).
WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) 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 // 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 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. // Update implements KubeClient Update.
func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 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) _, err := io.Copy(p.Out, modifiedReader)

@ -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 { func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error {
return nil 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) { func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }

Loading…
Cancel
Save