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;
google.protobuf.Timestamp started_at = 4;
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
}
//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) {

@ -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,
}

@ -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)
}

@ -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")
}

@ -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))

@ -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
}

@ -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)

@ -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
}

Loading…
Cancel
Save