From 58c05f87d7bf73353aa77ae4cacf50af1dc26d4f Mon Sep 17 00:00:00 2001 From: Michelle Noorali <michelle@deis.com> Date: Wed, 25 Jan 2017 15:22:48 -0500 Subject: [PATCH] feat(*): stream helm test messages to client --- .../{test_result.proto => test_run.proto} | 5 +- _proto/hapi/release/test_suite.proto | 11 +- _proto/hapi/services/tiller.proto | 5 +- cmd/helm/release_testing.go | 26 ++--- pkg/helm/client.go | 42 +++++-- pkg/helm/interface.go | 2 +- pkg/proto/hapi/release/hook.pb.go | 4 +- pkg/proto/hapi/release/test_result.pb.go | 85 -------------- pkg/proto/hapi/release/test_run.pb.go | 94 +++++++++++++++ pkg/proto/hapi/release/test_suite.pb.go | 48 +++++--- pkg/proto/hapi/services/tiller.pb.go | 87 ++++++++------ pkg/tiller/environment/environment.go | 5 + pkg/tiller/environment/environment_test.go | 6 + pkg/tiller/release_server.go | 20 ++-- pkg/tiller/release_testing.go | 110 +++++++++++++----- 15 files changed, 342 insertions(+), 208 deletions(-) rename _proto/hapi/release/{test_result.proto => test_run.proto} (88%) delete mode 100644 pkg/proto/hapi/release/test_result.pb.go create mode 100644 pkg/proto/hapi/release/test_run.pb.go diff --git a/_proto/hapi/release/test_result.proto b/_proto/hapi/release/test_run.proto similarity index 88% rename from _proto/hapi/release/test_result.proto rename to _proto/hapi/release/test_run.proto index 7b73e5d6d..a441e729f 100644 --- a/_proto/hapi/release/test_result.proto +++ b/_proto/hapi/release/test_run.proto @@ -21,7 +21,7 @@ import "google/protobuf/timestamp.proto"; option go_package = "release"; -message TestResult { +message TestRun { enum Status { UNKNOWN = 0; SUCCESS = 1; @@ -31,5 +31,6 @@ message TestResult { string name = 1; Status status = 2; string info = 3; - google.protobuf.Timestamp last_run = 4; + google.protobuf.Timestamp started_at = 4; + google.protobuf.Timestamp completed_at = 5; } diff --git a/_proto/hapi/release/test_suite.proto b/_proto/hapi/release/test_suite.proto index 1ff5eca07..2f6feb08c 100644 --- a/_proto/hapi/release/test_suite.proto +++ b/_proto/hapi/release/test_suite.proto @@ -17,15 +17,18 @@ syntax = "proto3"; package hapi.release; import "google/protobuf/timestamp.proto"; -import "hapi/release/test_result.proto"; +import "hapi/release/test_run.proto"; option go_package = "release"; // TestSuite comprises of the last run of the pre-defined test suite of a release version message TestSuite { - // LastRun indicates the date/time this test was last run. - google.protobuf.Timestamp last_run = 1; + // StartedAt indicates the date/time this test suite was kicked off + google.protobuf.Timestamp started_at = 1; + + // CompletedAt indicates the date/time this test suite was completed + google.protobuf.Timestamp completed_at = 2; // Results are the results of each segment of the test - repeated hapi.release.TestResult results = 2; + repeated hapi.release.TestRun results = 3; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index df663acc1..572e39be0 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -22,7 +22,6 @@ import "hapi/release/release.proto"; import "hapi/release/info.proto"; import "hapi/release/status.proto"; import "hapi/version/version.proto"; -import "hapi/release/test_suite.proto"; option go_package = "services"; @@ -82,7 +81,7 @@ service ReleaseService { //TODO: move this to a test release service or rename to RunReleaseTest // TestRelease runs the tests for a given release - rpc RunReleaseTest(TestReleaseRequest) returns (TestReleaseResponse) { + rpc RunReleaseTest(TestReleaseRequest) returns (stream TestReleaseResponse) { } } @@ -322,5 +321,5 @@ message TestReleaseRequest { // TestReleaseResponse message TestReleaseResponse { // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) - hapi.release.TestSuite result = 1; + string msg = 1; } diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 796198bf8..e4c8e2c44 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -20,11 +20,9 @@ import ( "fmt" "io" - "github.com/gosuri/uitable" "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" - //"k8s.io/helm/pkg/proto/hapi/release" ) const releaseTestDesc = ` @@ -69,20 +67,20 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command { return cmd } -func (t *releaseTestCmd) run() error { - res, err := t.client.ReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout)) - if err != nil { - return prettyError(err) - } +func (t *releaseTestCmd) run() (err error) { + c, errc := t.client.RunReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout)) - table := uitable.New() - table.MaxColWidth = 50 - table.AddRow("NAME", "Result", "Info") - //TODO: change Result to Suite - for _, r := range res.Result.Results { - table.AddRow(r.Name, r.Status, r.Info) + for { + select { + case err := <-errc: + return prettyError(err) + case res, ok := <-c: + if !ok { + break + } + fmt.Fprintf(t.out, res.Msg+"\n") + } } - fmt.Fprintln(t.out, table.String()) //TODO: or no tests found return nil } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 489d78ba9..771974bb7 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -17,6 +17,8 @@ limitations under the License. package helm // import "k8s.io/helm/pkg/helm" import ( + "io" + "golang.org/x/net/context" "google.golang.org/grpc" @@ -244,8 +246,8 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get return h.history(ctx, req) } -// ReleaseTest executes a pre-defined test on a release -func (h *Client) ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error) { +//ReleaseTest executes a pre-defined test on a release +func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { for _, opt := range opts { opt(&h.opts) } @@ -371,13 +373,39 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls. } // Executes tiller.TestRelease RPC. -func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (*rls.TestReleaseResponse, error) { +func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) { + errc := make(chan error, 1) c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) if err != nil { - return nil, err + errc <- err + return nil, errc } - defer c.Close() - rlc := rls.NewReleaseServiceClient(c) - return rlc.RunReleaseTest(ctx, req) + ch := make(chan *rls.TestReleaseResponse, 1) + go func() { + defer close(errc) + defer close(ch) + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + s, err := rlc.RunReleaseTest(ctx, req) + if err != nil { + errc <- err + return + } + + for { + msg, err := s.Recv() + if err == io.EOF { + return + } + if err != nil { + errc <- err + return + } + ch <- msg + } + }() + + return ch, errc } diff --git a/pkg/helm/interface.go b/pkg/helm/interface.go index 84af3aaab..bff110b34 100644 --- a/pkg/helm/interface.go +++ b/pkg/helm/interface.go @@ -34,5 +34,5 @@ type Interface interface { ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) - ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error) + RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) } diff --git a/pkg/proto/hapi/release/hook.pb.go b/pkg/proto/hapi/release/hook.pb.go index 45b0533f2..c90e0b59e 100644 --- a/pkg/proto/hapi/release/hook.pb.go +++ b/pkg/proto/hapi/release/hook.pb.go @@ -10,7 +10,7 @@ It is generated from these files: hapi/release/info.proto hapi/release/release.proto hapi/release/status.proto - hapi/release/test_result.proto + hapi/release/test_run.proto hapi/release/test_suite.proto It has these top-level messages: @@ -18,7 +18,7 @@ It has these top-level messages: Info Release Status - TestResult + TestRun TestSuite */ package release diff --git a/pkg/proto/hapi/release/test_result.pb.go b/pkg/proto/hapi/release/test_result.pb.go deleted file mode 100644 index 691b66abb..000000000 --- a/pkg/proto/hapi/release/test_result.pb.go +++ /dev/null @@ -1,85 +0,0 @@ -// Code generated by protoc-gen-go. -// source: hapi/release/test_result.proto -// DO NOT EDIT! - -package release - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type TestResult_Status int32 - -const ( - TestResult_UNKNOWN TestResult_Status = 0 - TestResult_SUCCESS TestResult_Status = 1 - TestResult_FAILURE TestResult_Status = 2 -) - -var TestResult_Status_name = map[int32]string{ - 0: "UNKNOWN", - 1: "SUCCESS", - 2: "FAILURE", -} -var TestResult_Status_value = map[string]int32{ - "UNKNOWN": 0, - "SUCCESS": 1, - "FAILURE": 2, -} - -func (x TestResult_Status) String() string { - return proto.EnumName(TestResult_Status_name, int32(x)) -} -func (TestResult_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} } - -type TestResult struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Status TestResult_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestResult_Status" json:"status,omitempty"` - Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"` - LastRun *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` -} - -func (m *TestResult) Reset() { *m = TestResult{} } -func (m *TestResult) String() string { return proto.CompactTextString(m) } -func (*TestResult) ProtoMessage() {} -func (*TestResult) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} } - -func (m *TestResult) GetLastRun() *google_protobuf.Timestamp { - if m != nil { - return m.LastRun - } - return nil -} - -func init() { - proto.RegisterType((*TestResult)(nil), "hapi.release.TestResult") - proto.RegisterEnum("hapi.release.TestResult_Status", TestResult_Status_name, TestResult_Status_value) -} - -func init() { proto.RegisterFile("hapi/release/test_result.proto", fileDescriptor4) } - -var fileDescriptor4 = []byte{ - // 244 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x41, 0x4b, 0xc3, 0x30, - 0x18, 0x86, 0xcd, 0x1c, 0xad, 0xcb, 0x44, 0x4a, 0x4e, 0x65, 0x07, 0x57, 0x76, 0xea, 0x29, 0x81, - 0x89, 0x78, 0xd6, 0x31, 0x41, 0x94, 0x0a, 0xe9, 0x8a, 0xe0, 0x45, 0x32, 0xf8, 0x36, 0x0b, 0x6d, - 0x53, 0x9a, 0x2f, 0x3f, 0xd5, 0xff, 0x23, 0x49, 0x5a, 0xf4, 0xf6, 0xbd, 0xbc, 0x6f, 0x9e, 0x3c, - 0xf4, 0xf6, 0x5b, 0xf5, 0xb5, 0x18, 0xa0, 0x01, 0x65, 0x40, 0x20, 0x18, 0xfc, 0x1a, 0xc0, 0xd8, - 0x06, 0x79, 0x3f, 0x68, 0xd4, 0xec, 0xda, 0xf5, 0x7c, 0xec, 0x57, 0xeb, 0xb3, 0xd6, 0xe7, 0x06, - 0x84, 0xef, 0x8e, 0xf6, 0x24, 0xb0, 0x6e, 0xc1, 0xa0, 0x6a, 0xfb, 0x30, 0xdf, 0xfc, 0x10, 0x4a, - 0x0f, 0x60, 0x50, 0x7a, 0x06, 0x63, 0x74, 0xde, 0xa9, 0x16, 0x52, 0x92, 0x91, 0x7c, 0x21, 0xfd, - 0xcd, 0x1e, 0x68, 0x64, 0x50, 0xa1, 0x35, 0xe9, 0x2c, 0x23, 0xf9, 0xcd, 0x76, 0xcd, 0xff, 0x7f, - 0xc1, 0xff, 0x5e, 0xf3, 0xd2, 0xcf, 0xe4, 0x38, 0x77, 0xb0, 0xba, 0x3b, 0xe9, 0xf4, 0x32, 0xc0, - 0xdc, 0xcd, 0xee, 0xe9, 0x55, 0xa3, 0x9c, 0xb3, 0xed, 0xd2, 0x79, 0x46, 0xf2, 0xe5, 0x76, 0xc5, - 0x83, 0x23, 0x9f, 0x1c, 0xf9, 0x61, 0x72, 0x94, 0xb1, 0xdb, 0x4a, 0xdb, 0x6d, 0x04, 0x8d, 0x02, - 0x9c, 0x2d, 0x69, 0x5c, 0x15, 0xaf, 0xc5, 0xfb, 0x47, 0x91, 0x5c, 0xb8, 0x50, 0x56, 0xbb, 0xdd, - 0xbe, 0x2c, 0x13, 0xe2, 0xc2, 0xf3, 0xe3, 0xcb, 0x5b, 0x25, 0xf7, 0xc9, 0xec, 0x69, 0xf1, 0x19, - 0x8f, 0x82, 0xc7, 0xc8, 0x83, 0xef, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x44, 0x22, 0xbb, - 0x3a, 0x01, 0x00, 0x00, -} diff --git a/pkg/proto/hapi/release/test_run.pb.go b/pkg/proto/hapi/release/test_run.pb.go new file mode 100644 index 000000000..51b3e72f9 --- /dev/null +++ b/pkg/proto/hapi/release/test_run.pb.go @@ -0,0 +1,94 @@ +// Code generated by protoc-gen-go. +// source: hapi/release/test_run.proto +// DO NOT EDIT! + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type TestRun_Status int32 + +const ( + TestRun_UNKNOWN TestRun_Status = 0 + TestRun_SUCCESS TestRun_Status = 1 + TestRun_FAILURE TestRun_Status = 2 +) + +var TestRun_Status_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SUCCESS", + 2: "FAILURE", +} +var TestRun_Status_value = map[string]int32{ + "UNKNOWN": 0, + "SUCCESS": 1, + "FAILURE": 2, +} + +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} } + +type TestRun struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Status TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,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"` + CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=completed_at,json=completedAt" json:"completed_at,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 (m *TestRun) GetStartedAt() *google_protobuf.Timestamp { + if m != nil { + return m.StartedAt + } + return nil +} + +func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp { + if m != nil { + return m.CompletedAt + } + return nil +} + +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) } + +var fileDescriptor4 = []byte{ + // 265 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xfb, 0x40, + 0x14, 0xc4, 0xff, 0xc9, 0xbf, 0x26, 0x64, 0x53, 0x24, 0xec, 0x29, 0x54, 0xc1, 0xd0, 0x53, 0x4e, + 0xbb, 0x50, 0xbd, 0x78, 0xf0, 0x10, 0x4b, 0x05, 0x51, 0x22, 0x6c, 0x1a, 0x04, 0x2f, 0x65, 0xab, + 0xaf, 0x35, 0x90, 0x64, 0x43, 0xf6, 0xe5, 0x8b, 0xf8, 0x89, 0x65, 0x93, 0xad, 0x78, 0xf3, 0xf6, + 0x86, 0xf9, 0xcd, 0x30, 0x8f, 0x5c, 0x7c, 0xca, 0xae, 0xe2, 0x3d, 0xd4, 0x20, 0x35, 0x70, 0x04, + 0x8d, 0xbb, 0x7e, 0x68, 0x59, 0xd7, 0x2b, 0x54, 0x74, 0x6e, 0x4c, 0x66, 0xcd, 0xc5, 0xd5, 0x51, + 0xa9, 0x63, 0x0d, 0x7c, 0xf4, 0xf6, 0xc3, 0x81, 0x63, 0xd5, 0x80, 0x46, 0xd9, 0x74, 0x13, 0xbe, + 0xfc, 0x72, 0x89, 0xbf, 0x05, 0x8d, 0x62, 0x68, 0x29, 0x25, 0xb3, 0x56, 0x36, 0x10, 0x3b, 0x89, + 0x93, 0x06, 0x62, 0xbc, 0xe9, 0x0d, 0xf1, 0x34, 0x4a, 0x1c, 0x74, 0xec, 0x26, 0x4e, 0x7a, 0xbe, + 0xba, 0x64, 0xbf, 0xfb, 0x99, 0x8d, 0xb2, 0x62, 0x64, 0x84, 0x65, 0x4d, 0x53, 0xd5, 0x1e, 0x54, + 0xfc, 0x7f, 0x6a, 0x32, 0x37, 0xbd, 0x25, 0x44, 0xa3, 0xec, 0x11, 0x3e, 0x76, 0x12, 0xe3, 0x59, + 0xe2, 0xa4, 0xe1, 0x6a, 0xc1, 0xa6, 0x7d, 0xec, 0xb4, 0x8f, 0x6d, 0x4f, 0xfb, 0x44, 0x60, 0xe9, + 0x0c, 0xe9, 0x1d, 0x99, 0xbf, 0xab, 0xa6, 0xab, 0xc1, 0x86, 0xcf, 0xfe, 0x0c, 0x87, 0x3f, 0x7c, + 0x86, 0x4b, 0x4e, 0xbc, 0x69, 0x1f, 0x0d, 0x89, 0x5f, 0xe6, 0x4f, 0xf9, 0xcb, 0x6b, 0x1e, 0xfd, + 0x33, 0xa2, 0x28, 0xd7, 0xeb, 0x4d, 0x51, 0x44, 0x8e, 0x11, 0x0f, 0xd9, 0xe3, 0x73, 0x29, 0x36, + 0x91, 0x7b, 0x1f, 0xbc, 0xf9, 0xf6, 0xc1, 0xbd, 0x37, 0x96, 0x5f, 0x7f, 0x07, 0x00, 0x00, 0xff, + 0xff, 0x8d, 0xb9, 0xce, 0x57, 0x74, 0x01, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/release/test_suite.pb.go b/pkg/proto/hapi/release/test_suite.pb.go index bc6357cbd..27fe45ac5 100644 --- a/pkg/proto/hapi/release/test_suite.pb.go +++ b/pkg/proto/hapi/release/test_suite.pb.go @@ -16,10 +16,12 @@ var _ = math.Inf // TestSuite comprises of the last run of the pre-defined test suite of a release version type TestSuite struct { - // LastRun indicates the date/time this test was last run. - LastRun *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` + // StartedAt indicates the date/time this test suite was kicked off + StartedAt *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=started_at,json=startedAt" json:"started_at,omitempty"` + // CompletedAt indicates the date/time this test suite was completed + CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"` // Results are the results of each segment of the test - Results []*TestResult `protobuf:"bytes,2,rep,name=results" json:"results,omitempty"` + Results []*TestRun `protobuf:"bytes,3,rep,name=results" json:"results,omitempty"` } func (m *TestSuite) Reset() { *m = TestSuite{} } @@ -27,14 +29,21 @@ func (m *TestSuite) String() string { return proto.CompactTextString( func (*TestSuite) ProtoMessage() {} func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} } -func (m *TestSuite) GetLastRun() *google_protobuf.Timestamp { +func (m *TestSuite) GetStartedAt() *google_protobuf.Timestamp { if m != nil { - return m.LastRun + return m.StartedAt } return nil } -func (m *TestSuite) GetResults() []*TestResult { +func (m *TestSuite) GetCompletedAt() *google_protobuf.Timestamp { + if m != nil { + return m.CompletedAt + } + return nil +} + +func (m *TestSuite) GetResults() []*TestRun { if m != nil { return m.Results } @@ -48,17 +57,18 @@ func init() { func init() { proto.RegisterFile("hapi/release/test_suite.proto", fileDescriptor5) } var fileDescriptor5 = []byte{ - // 183 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x8e, 0xc1, 0x8a, 0x83, 0x30, - 0x14, 0x45, 0x71, 0x06, 0xc6, 0x31, 0xce, 0xca, 0x95, 0x08, 0xd3, 0x4a, 0x57, 0xae, 0x5e, 0xc0, - 0xd2, 0x1f, 0xe8, 0x27, 0xa4, 0xae, 0xba, 0x29, 0x11, 0x5e, 0xad, 0x10, 0x8d, 0xf8, 0x5e, 0xfa, - 0xfd, 0x25, 0x46, 0xa1, 0xd0, 0xf5, 0x39, 0xdc, 0x73, 0xc5, 0xff, 0x43, 0x4f, 0xbd, 0x9c, 0xd1, - 0xa0, 0x26, 0x94, 0x8c, 0xc4, 0x37, 0x72, 0x3d, 0x23, 0x4c, 0xb3, 0x65, 0x9b, 0xfd, 0x79, 0x0c, - 0x2b, 0x2e, 0xf6, 0x9d, 0xb5, 0x9d, 0x41, 0xb9, 0xb0, 0xd6, 0xdd, 0x25, 0xf7, 0x03, 0x12, 0xeb, - 0x61, 0x0a, 0x7a, 0xb1, 0xfb, 0x5c, 0x9b, 0x91, 0x9c, 0xe1, 0xc0, 0x0f, 0x4f, 0x91, 0x34, 0x48, - 0x7c, 0xf1, 0x85, 0xec, 0x24, 0x7e, 0x8d, 0xf6, 0x86, 0x1b, 0xf3, 0xa8, 0x8c, 0xaa, 0xb4, 0x2e, - 0x20, 0x04, 0x60, 0x0b, 0x40, 0xb3, 0x05, 0x54, 0xec, 0x5d, 0xe5, 0xc6, 0xac, 0x16, 0x71, 0xd8, - 0xa4, 0xfc, 0xab, 0xfc, 0xae, 0xd2, 0x3a, 0x87, 0xf7, 0x93, 0xe0, 0x03, 0x6a, 0x11, 0xd4, 0x26, - 0x9e, 0x93, 0x6b, 0xbc, 0xe2, 0xf6, 0x67, 0xd9, 0x3e, 0xbe, 0x02, 0x00, 0x00, 0xff, 0xff, 0x05, - 0x00, 0xf5, 0xbb, 0xf9, 0x00, 0x00, 0x00, + // 207 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4a, 0x86, 0x40, + 0x14, 0x85, 0x31, 0x21, 0x71, 0x74, 0x35, 0x10, 0x88, 0x11, 0x49, 0x2b, 0x57, 0x33, 0x60, 0xab, + 0x16, 0x2d, 0xec, 0x11, 0xcc, 0x55, 0x1b, 0x19, 0xeb, 0x66, 0xc2, 0xe8, 0x0c, 0x73, 0xef, 0xbc, + 0x5a, 0xcf, 0x17, 0xea, 0x18, 0x41, 0x8b, 0x7f, 0xfd, 0x7d, 0xe7, 0x9c, 0x7b, 0xd9, 0xdd, 0x97, + 0xb2, 0xb3, 0x74, 0xa0, 0x41, 0x21, 0x48, 0x02, 0xa4, 0x01, 0xfd, 0x4c, 0x20, 0xac, 0x33, 0x64, + 0x78, 0xbe, 0x61, 0x11, 0x70, 0x79, 0x3f, 0x19, 0x33, 0x69, 0x90, 0x3b, 0x1b, 0xfd, 0xa7, 0xa4, + 0x79, 0x01, 0x24, 0xb5, 0xd8, 0x43, 0x2f, 0x6f, 0xff, 0xb7, 0x39, 0xbf, 0x1e, 0xf0, 0xe1, 0x3b, + 0x62, 0x69, 0x0f, 0x48, 0xaf, 0x5b, 0x3f, 0x7f, 0x62, 0x0c, 0x49, 0x39, 0x82, 0x8f, 0x41, 0x51, + 0x11, 0x55, 0x51, 0x9d, 0x35, 0xa5, 0x38, 0x06, 0xc4, 0x39, 0x20, 0xfa, 0x73, 0xa0, 0x4b, 0x83, + 0xdd, 0x12, 0x7f, 0x66, 0xf9, 0xbb, 0x59, 0xac, 0x86, 0x10, 0xbe, 0xba, 0x18, 0xce, 0x7e, 0xfd, + 0x96, 0xb8, 0x64, 0x89, 0x03, 0xf4, 0x9a, 0xb0, 0x88, 0xab, 0xb8, 0xce, 0x9a, 0x1b, 0xf1, 0xf7, + 0x4b, 0xb1, 0xdd, 0xd8, 0xf9, 0xb5, 0x3b, 0xad, 0x97, 0xf4, 0x2d, 0x09, 0x6c, 0xbc, 0xde, 0xcb, + 0x1f, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x59, 0x65, 0x4f, 0x37, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index bfe6cb43d..883d5a194 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -42,7 +42,6 @@ import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release2 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release" import hapi_version "k8s.io/helm/pkg/proto/hapi/version" -import hapi_release4 "k8s.io/helm/pkg/proto/hapi/release" import ( context "golang.org/x/net/context" @@ -507,7 +506,7 @@ func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0 // TestReleaseResponse type TestReleaseResponse struct { // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) - Result *hapi_release4.TestSuite `protobuf:"bytes,1,opt,name=result" json:"result,omitempty"` + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` } func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} } @@ -515,13 +514,6 @@ func (m *TestReleaseResponse) String() string { return proto.CompactT func (*TestReleaseResponse) ProtoMessage() {} func (*TestReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } -func (m *TestReleaseResponse) GetResult() *hapi_release4.TestSuite { - if m != nil { - return m.Result - } - return nil -} - func init() { proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort") @@ -582,7 +574,7 @@ type ReleaseServiceClient interface { GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error) // TODO: move this to a test release service or rename to RunReleaseTest // TestRelease runs the tests for a given release - RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (*TestReleaseResponse, error) + RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) } type releaseServiceClient struct { @@ -697,13 +689,36 @@ func (c *releaseServiceClient) GetHistory(ctx context.Context, in *GetHistoryReq return out, nil } -func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (*TestReleaseResponse, error) { - out := new(TestReleaseResponse) - err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/RunReleaseTest", in, out, c.cc, opts...) +func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) { + stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[1], c.cc, "/hapi.services.tiller.ReleaseService/RunReleaseTest", opts...) if err != nil { return nil, err } - return out, nil + x := &releaseServiceRunReleaseTestClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ReleaseService_RunReleaseTestClient interface { + Recv() (*TestReleaseResponse, error) + grpc.ClientStream +} + +type releaseServiceRunReleaseTestClient struct { + grpc.ClientStream +} + +func (x *releaseServiceRunReleaseTestClient) Recv() (*TestReleaseResponse, error) { + m := new(TestReleaseResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil } // Server API for ReleaseService service @@ -732,7 +747,7 @@ type ReleaseServiceServer interface { GetHistory(context.Context, *GetHistoryRequest) (*GetHistoryResponse, error) // TODO: move this to a test release service or rename to RunReleaseTest // TestRelease runs the tests for a given release - RunReleaseTest(context.Context, *TestReleaseRequest) (*TestReleaseResponse, error) + RunReleaseTest(*TestReleaseRequest, ReleaseService_RunReleaseTestServer) error } func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) { @@ -904,22 +919,25 @@ func _ReleaseService_GetHistory_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } -func _ReleaseService_RunReleaseTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TestReleaseRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ReleaseServiceServer).RunReleaseTest(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/hapi.services.tiller.ReleaseService/RunReleaseTest", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ReleaseServiceServer).RunReleaseTest(ctx, req.(*TestReleaseRequest)) +func _ReleaseService_RunReleaseTest_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(TestReleaseRequest) + if err := stream.RecvMsg(m); err != nil { + return err } - return interceptor(ctx, in, info, handler) + return srv.(ReleaseServiceServer).RunReleaseTest(m, &releaseServiceRunReleaseTestServer{stream}) +} + +type ReleaseService_RunReleaseTestServer interface { + Send(*TestReleaseResponse) error + grpc.ServerStream +} + +type releaseServiceRunReleaseTestServer struct { + grpc.ServerStream +} + +func (x *releaseServiceRunReleaseTestServer) Send(m *TestReleaseResponse) error { + return x.ServerStream.SendMsg(m) } var _ReleaseService_serviceDesc = grpc.ServiceDesc{ @@ -958,10 +976,6 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetHistory", Handler: _ReleaseService_GetHistory_Handler, }, - { - MethodName: "RunReleaseTest", - Handler: _ReleaseService_RunReleaseTest_Handler, - }, }, Streams: []grpc.StreamDesc{ { @@ -969,6 +983,11 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ Handler: _ReleaseService_ListReleases_Handler, ServerStreams: true, }, + { + StreamName: "RunReleaseTest", + Handler: _ReleaseService_RunReleaseTest_Handler, + ServerStreams: true, + }, }, Metadata: fileDescriptor0, } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 5ddff63cc..010d14060 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -137,6 +137,7 @@ type KubeClient interface { Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error Build(namespace string, reader io.Reader) (kube.Result, error) + //TODO: insert description WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) } @@ -184,6 +185,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 +} + // Environment provides the context for executing a client request. // // All services in a context are concurrency safe. diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index a6621e5e7..7544a3938 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -20,10 +20,12 @@ import ( "bytes" "io" "testing" + "time" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -56,6 +58,10 @@ func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) return []*resource.Info{}, nil } +func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + return "", nil +} + var _ Engine = &mockEngine{} var _ KubeClient = &mockKubeClient{} var _ KubeClient = &PrintingKubeClient{} diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 40e9fa75f..28d127ac0 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -1066,27 +1066,27 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro } // RunTestRelease runs a pre-defined test on a given release -func (s *ReleaseServer) RunReleaseTest(c ctx.Context, req *services.TestReleaseRequest) (*services.TestReleaseResponse, error) { +func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { - res := &services.TestReleaseResponse{} if !ValidName.MatchString(req.Name) { - return nil, errMissingRelease + return errMissingRelease } // finds the non-deleted release with the given name - r, err := s.env.Releases.Last(req.Name) + rel, err := s.env.Releases.Last(req.Name) if err != nil { - return nil, err + return err } + tests, err := prepareTests(rel.Hooks, rel.Name) kubeCli := s.env.KubeClient - testSuite, err := runReleaseTestSuite(r.Hooks, kubeCli, r.Name, r.Namespace, req.Timeout) + + testSuite, err := runReleaseTests(tests, rel, kubeCli, stream, req.Timeout) if err != nil { - return nil, err + return err } - r.TestSuite = testSuite - res.Result = testSuite + rel.TestSuite = testSuite - return res, nil + return nil } diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go index 64fffc094..90379887a 100644 --- a/pkg/tiller/release_testing.go +++ b/pkg/tiller/release_testing.go @@ -26,43 +26,55 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/timeconv" ) -// change name to runReleaseTestSuite -func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, name, namespace string, timeout int64) (*release.TestSuite, error) { +//TODO: testSuiteRunner.Run() +//struct testSuiteRunner { +//suite *release.TestSuite, +//tests []string, +//kube environemtn.KubeClient, +//timeout int64 +////stream or output channel +//} - suite := &release.TestSuite{} - suite.LastRun = timeconv.Now() - results := []*release.TestResult{} +func runReleaseTests(tests []string, rel *release.Release, kube environment.KubeClient, stream services.ReleaseService_RunReleaseTestServer, timeout int64) (*release.TestSuite, error) { + results := []*release.TestRun{} - tests, err := prepareTests(hooks, name) - if err != nil { - return suite, err - } + //TODO: add results to test suite + suite := &release.TestSuite{} + suite.StartedAt = timeconv.Now() for _, h := range tests { var sh simpleHead err := yaml.Unmarshal([]byte(h), &sh) if err != nil { - //handle err better return nil, err } - ts := &release.TestResult{Name: sh.Metadata.Name} - // should this be lower? should we even be saving time to hook? - // TODO: should be start time really - ts.LastRun = timeconv.Now() + if sh.Kind != "Pod" { + return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name) + } + + ts := &release.TestRun{Name: sh.Metadata.Name} + ts.StartedAt = timeconv.Now() + if err := streamRunning(ts.Name, stream); err != nil { + return nil, err + } resourceCreated := true b := bytes.NewBufferString(h) - if err := kube.Create(namespace, b); err != nil { - log.Printf("Could not create %s(%s): %v", ts.Name, sh.Kind, err) - ts.Info = err.Error() - //TODO: status option should be constant not random int - ts.Status = 2 + if err := kube.Create(rel.Namespace, b); err != nil { resourceCreated = false + msg := fmt.Sprintf("ERROR: %s", err) + log.Printf(msg) + ts.Info = err.Error() + ts.Status = release.TestRun_FAILURE + if streamErr := streamMessage(msg, stream); streamErr != nil { + return nil, err + } } status := api.PodUnknown @@ -70,31 +82,41 @@ func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, nam if resourceCreated { b.Reset() b.WriteString(h) - status, err = kube.WaitAndGetCompletedPodStatus(namespace, b, time.Duration(timeout)*time.Second) + status, err = kube.WaitAndGetCompletedPodStatus(rel.Namespace, b, time.Duration(timeout)*time.Second) if err != nil { - log.Printf("Error getting status for %s(%s): %s", ts.Name, sh.Kind, err) - ts.Info = err.Error() - ts.Status = 0 resourceCleanExit = false + log.Printf("Error getting status for pod %s: %s", ts.Name, err) + ts.Info = err.Error() + ts.Status = release.TestRun_UNKNOWN + if streamErr := streamFailed(ts.Name, stream); streamErr != nil { + return nil, err + } } } // TODO: maybe better suited as a switch statement and include // PodUnknown, PodFailed, PodRunning, and PodPending scenarios if resourceCreated && resourceCleanExit && status == api.PodSucceeded { - ts.Status = 1 + ts.Status = release.TestRun_SUCCESS + if streamErr := streamSuccess(ts.Name, stream); streamErr != nil { + return nil, streamErr + } } else if resourceCreated && resourceCleanExit && status == api.PodFailed { - ts.Status = 2 + ts.Status = release.TestRun_FAILURE + if streamErr := streamFailed(ts.Name, stream); streamErr != nil { + return nil, err + } } results = append(results, ts) - log.Printf("Test %s(%s) complete", ts.Name, sh.Kind) + log.Printf("Test %s completed", ts.Name) //TODO: recordTests() - add test results to configmap with standardized name } suite.Results = results - log.Printf("Finished running test suite for %s", name) + //TODO: delete flag + log.Printf("Finished running test suite for %s", rel.Name) return suite, nil } @@ -145,3 +167,37 @@ func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) { } return tests, nil } + +func streamRunning(name string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := "RUNNING: " + name + if err := streamMessage(msg, stream); err != nil { + return err + } + return nil +} + +func streamFailed(name string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s` for more info", name, name) + if err := streamMessage(msg, stream); err != nil { + return err + } + return nil +} + +func streamSuccess(name string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := fmt.Sprintf("PASSED: %s", name) + if err := streamMessage(msg, stream); err != nil { + return err + } + return nil +} + +func streamMessage(msg string, stream services.ReleaseService_RunReleaseTestServer) error { + resp := &services.TestReleaseResponse{Msg: msg} + // TODO: handle err better + if err := stream.Send(resp); err != nil { + return err + } + + return nil +} -- GitLab