From d46d63a8f7e4db221f28c051d38a6c074cbd0423 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 4 Jan 2017 10:13:26 -0500 Subject: [PATCH 1/8] feat(*): add helm test command mvp * This is a simple mvp which processes a test definition with the hook annotation for test when you run `helm test [release]` * helm client cmd, proto def, tiller logic --- _proto/hapi/release/hook.proto | 1 + _proto/hapi/release/release.proto | 4 + _proto/hapi/release/test_result.proto | 35 +++++ _proto/hapi/release/test_suite.proto | 31 ++++ _proto/hapi/services/tiller.proto | 20 +++ cmd/helm/helm.go | 1 + cmd/helm/release_testing.go | 88 +++++++++++ .../nginx/templates/tests/service-test.yaml | 27 ++++ pkg/helm/client.go | 25 +++ pkg/helm/interface.go | 1 + pkg/helm/option.go | 13 ++ pkg/kube/client.go | 45 ++++++ pkg/kube/client_test.go | 45 ++++++ pkg/proto/hapi/release/hook.pb.go | 51 +++--- pkg/proto/hapi/release/release.pb.go | 46 ++++-- pkg/proto/hapi/release/test_result.pb.go | 85 ++++++++++ pkg/proto/hapi/release/test_suite.pb.go | 64 ++++++++ pkg/proto/hapi/services/tiller.pb.go | 105 +++++++++++-- pkg/tiller/environment/environment.go | 4 + pkg/tiller/hooks.go | 2 + pkg/tiller/release_server.go | 26 ++++ pkg/tiller/release_testing.go | 147 ++++++++++++++++++ 22 files changed, 811 insertions(+), 55 deletions(-) create mode 100644 _proto/hapi/release/test_result.proto create mode 100644 _proto/hapi/release/test_suite.proto create mode 100644 cmd/helm/release_testing.go create mode 100644 docs/examples/nginx/templates/tests/service-test.yaml create mode 100644 pkg/proto/hapi/release/test_result.pb.go create mode 100644 pkg/proto/hapi/release/test_suite.pb.go create mode 100644 pkg/tiller/release_testing.go diff --git a/_proto/hapi/release/hook.proto b/_proto/hapi/release/hook.proto index 388c34535..a871f25ee 100644 --- a/_proto/hapi/release/hook.proto +++ b/_proto/hapi/release/hook.proto @@ -32,6 +32,7 @@ message Hook { POST_UPGRADE = 6; PRE_ROLLBACK = 7; POST_ROLLBACK = 8; + RELEASE_TEST = 9; } string name = 1; // Kind is the Kubernetes kind. diff --git a/_proto/hapi/release/release.proto b/_proto/hapi/release/release.proto index 4a6afa0fe..1de073f50 100644 --- a/_proto/hapi/release/release.proto +++ b/_proto/hapi/release/release.proto @@ -18,6 +18,7 @@ package hapi.release; import "hapi/release/hook.proto"; import "hapi/release/info.proto"; +import "hapi/release/test_suite.proto"; import "hapi/chart/config.proto"; import "hapi/chart/chart.proto"; @@ -50,4 +51,7 @@ message Release { // Namespace is the kubernetes namespace of the release. string namespace = 8; + + // TestSuite provides results on the last test run on a release + hapi.release.TestSuite test_suite = 9; } diff --git a/_proto/hapi/release/test_result.proto b/_proto/hapi/release/test_result.proto new file mode 100644 index 000000000..7b73e5d6d --- /dev/null +++ b/_proto/hapi/release/test_result.proto @@ -0,0 +1,35 @@ + +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; + +option go_package = "release"; + +message TestResult { + enum Status { + UNKNOWN = 0; + SUCCESS = 1; + FAILURE = 2; + } + + string name = 1; + Status status = 2; + string info = 3; + google.protobuf.Timestamp last_run = 4; +} diff --git a/_proto/hapi/release/test_suite.proto b/_proto/hapi/release/test_suite.proto new file mode 100644 index 000000000..1ff5eca07 --- /dev/null +++ b/_proto/hapi/release/test_suite.proto @@ -0,0 +1,31 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; +import "hapi/release/test_result.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; + + // Results are the results of each segment of the test + repeated hapi.release.TestResult results = 2; +} diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index a47ab035b..df663acc1 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -22,6 +22,7 @@ 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"; @@ -78,6 +79,11 @@ service ReleaseService { // ReleaseHistory retrieves a releasse's history. rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) { } + + //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) { + } } // ListReleasesRequest requests a list of releases. @@ -304,3 +310,17 @@ message GetHistoryRequest { message GetHistoryResponse { repeated hapi.release.Release releases = 1; } + +// TestReleaseRequest is a request to get the status of a release. +message TestReleaseRequest { + // Name is the name of the release + string name = 1; + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 2; +} + +// TestReleaseResponse +message TestReleaseResponse { + // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) + hapi.release.TestSuite result = 1; +} diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index e71c0da8f..93f81eb4a 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -122,6 +122,7 @@ func newRootCmd(out io.Writer) *cobra.Command { newInitCmd(out), newResetCmd(nil, out), newVersionCmd(nil, out), + newReleaseTestCmd(nil, out), // Hidden documentation generator command: 'helm docs' newDocsCmd(out), diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go new file mode 100644 index 000000000..796198bf8 --- /dev/null +++ b/cmd/helm/release_testing.go @@ -0,0 +1,88 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +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 = ` +Th test command runs the tests for a release. + +The argument this command takes is the name of a deployed release. +The tests to be run are defined in the chart that was installed. +` + +type releaseTestCmd struct { + name string + out io.Writer + client helm.Interface + timeout int64 +} + +func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command { + rlsTest := &releaseTestCmd{ + out: out, + client: c, + } + + cmd := &cobra.Command{ + Use: "test [RELEASE]", + Short: "test a release", + Long: releaseTestDesc, + PersistentPreRunE: setupConnection, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "release name"); err != nil { + return err + } + + rlsTest.name = args[0] + rlsTest.client = ensureHelmClient(rlsTest.client) + return rlsTest.run() + }, + } + + f := cmd.Flags() + f.Int64Var(&rlsTest.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") + + return cmd +} + +func (t *releaseTestCmd) run() error { + res, err := t.client.ReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout)) + if err != nil { + return prettyError(err) + } + + 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) + } + + fmt.Fprintln(t.out, table.String()) //TODO: or no tests found + return nil +} diff --git a/docs/examples/nginx/templates/tests/service-test.yaml b/docs/examples/nginx/templates/tests/service-test.yaml new file mode 100644 index 000000000..a5a9400fa --- /dev/null +++ b/docs/examples/nginx/templates/tests/service-test.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-service-test" + annotations: + "helm.sh/hook": test +spec: + containers: + - name: curl + image: radial/busyboxplus:curl + command: ['curl'] + args: [ '{{ template "fullname" .}}:{{default 80 .Values.httpPort}}' ] + restartPolicy: Never +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-service-failing-test" + annotations: + "helm.sh/hook": test +spec: + containers: + - name: curl + image: radial/busyboxplus:curl + command: ['curl'] + args: [ '{{ template "fullname" .}}-service:{{default 80 .Values.httpPort}}' ] + restartPolicy: Never diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 049c6af60..489d78ba9 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -244,6 +244,19 @@ 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) { + for _, opt := range opts { + opt(&h.opts) + } + + req := &h.opts.testReq + req.Name = rlsName + ctx := NewContext() + + return h.test(ctx, req) +} + // Executes tiller.ListReleases RPC. func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) { c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) @@ -356,3 +369,15 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls. rlc := rls.NewReleaseServiceClient(c) return rlc.GetHistory(ctx, req) } + +// Executes tiller.TestRelease RPC. +func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (*rls.TestReleaseResponse, error) { + c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.RunReleaseTest(ctx, req) +} diff --git a/pkg/helm/interface.go b/pkg/helm/interface.go index 6b88463b7..84af3aaab 100644 --- a/pkg/helm/interface.go +++ b/pkg/helm/interface.go @@ -34,4 +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) } diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 42df562cf..45f6b23ee 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -66,6 +66,8 @@ type options struct { histReq rls.GetHistoryRequest // resetValues instructs Tiller to reset values to their defaults. resetValues bool + // release test options are applied directly to the test release history request + testReq rls.TestReleaseRequest } // Host specifies the host address of the Tiller release server, (default = ":44134"). @@ -174,6 +176,13 @@ func DeleteTimeout(timeout int64) DeleteOption { } } +// TestTimeout specifies the number of seconds before kubernetes calls timeout +func ReleaseTestTimeout(timeout int64) ReleaseTestOption { + return func(opts *options) { + opts.testReq.Timeout = timeout + } +} + // RollbackTimeout specifies the number of seconds before kubernetes calls timeout func RollbackTimeout(timeout int64) RollbackOption { return func(opts *options) { @@ -364,3 +373,7 @@ func NewContext() context.Context { md := metadata.Pairs("x-helm-api-client", version.Version) return metadata.NewContext(context.TODO(), md) } + +// ReleaseTestOption allows configuring optional request data for +// issuing a TestRelease rpc. +type ReleaseTestOption func(*options) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 0022bb71f..89d044ff1 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/kubectl" @@ -305,6 +306,10 @@ func perform(c *Client, namespace string, infos Result, fn ResourceActorFunc) er if len(infos) == 0 { return ErrNoObjectsVisited } + + if err != nil { + return err + } for _, info := range infos { if err := fn(info); err != nil { return err @@ -610,3 +615,43 @@ func scrubValidationError(err error) error { } return err } + +func (c *Client) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + infos, err := c.Build(namespace, reader) + if err != nil { + return api.PodUnknown, err + } + info := infos[0] + + // TODO: should we be checking kind before hand? probably yes. + // TODO: add validation to linter: any manifest with a test hook has to be a pod kind? + kind := info.Mapping.GroupVersionKind.Kind + if kind != "Pod" { + return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) + } + + if err := watchPodUntilComplete(timeout, info); err != nil { + return api.PodUnknown, err + } + + if err := info.Get(); err != nil { + return api.PodUnknown, err + } + status := info.Object.(*api.Pod).Status.Phase + + return status, nil +} + +func 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 { + return err + } + + log.Printf("Watching pod %s for completion with timeout of %v", info.Name, timeout) + _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { + return conditions.PodCompleted(e) + }) + + return err +} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 080e9882c..90cf4e6d5 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -305,6 +305,51 @@ func TestPerform(t *testing.T) { } } +func TestWaitAndGetCompletedPodStatus(t *testing.T) { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + actions := make(map[string]string) + testPodList := newPodList("bestpod") + + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + actions[p] = m + count := 0 + switch { + case p == "/namespaces/test/pods/bestpod" && m == "GET": + return newResponse(200, &testPodList.Items[0]) + case p == "/watch/namespaces/test/pods/bestpod" && m == "GET": + //TODO: fix response + count = count + 1 + if count == 1 { + //returns event running + return newResponse(200, &testPodList.Items[0]) + } + if count == 2 { + //return event succeeded + return newResponse(200, &testPodList.Items[0]) + } + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + + c := &Client{Factory: f} + //stub watchUntil to return no error + + status, err := c.WaitAndGetCompletedPodStatus("test", objBody(codec, &testPodList), 30*time.Second) + if err != nil { + t.Fatal(err) + } + + if status != api.PodSucceeded { + t.Fatal("Expected %s, got %s", api.PodSucceeded, status) + } +} + func TestReal(t *testing.T) { t.Skip("This is a live test, comment this line to run") c := New(nil) diff --git a/pkg/proto/hapi/release/hook.pb.go b/pkg/proto/hapi/release/hook.pb.go index 57581b14e..45b0533f2 100644 --- a/pkg/proto/hapi/release/hook.pb.go +++ b/pkg/proto/hapi/release/hook.pb.go @@ -10,12 +10,16 @@ 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_suite.proto It has these top-level messages: Hook Info Release Status + TestResult + TestSuite */ package release @@ -47,6 +51,7 @@ const ( Hook_POST_UPGRADE Hook_Event = 6 Hook_PRE_ROLLBACK Hook_Event = 7 Hook_POST_ROLLBACK Hook_Event = 8 + Hook_RELEASE_TEST Hook_Event = 9 ) var Hook_Event_name = map[int32]string{ @@ -59,6 +64,7 @@ var Hook_Event_name = map[int32]string{ 6: "POST_UPGRADE", 7: "PRE_ROLLBACK", 8: "POST_ROLLBACK", + 9: "RELEASE_TEST", } var Hook_Event_value = map[string]int32{ "UNKNOWN": 0, @@ -70,6 +76,7 @@ var Hook_Event_value = map[string]int32{ "POST_UPGRADE": 6, "PRE_ROLLBACK": 7, "POST_ROLLBACK": 8, + "RELEASE_TEST": 9, } func (x Hook_Event) String() string { @@ -112,26 +119,26 @@ func init() { func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 321 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6e, 0xea, 0x30, - 0x14, 0x85, 0x5f, 0x20, 0x24, 0x70, 0xe1, 0xd1, 0xd4, 0x93, 0x5a, 0x4c, 0x8a, 0x18, 0x31, 0x72, - 0x2a, 0xaa, 0x2e, 0x00, 0x8a, 0xd5, 0x56, 0x44, 0x01, 0x99, 0xa0, 0x4a, 0x9d, 0x20, 0xa3, 0x1a, - 0x88, 0x20, 0x71, 0x44, 0x4c, 0xd7, 0xd3, 0xf5, 0x75, 0x15, 0x95, 0x9d, 0x1f, 0x75, 0x76, 0xfd, - 0xdd, 0xcf, 0xc7, 0x3e, 0x70, 0x77, 0xe4, 0x59, 0xec, 0x5f, 0xc4, 0x59, 0xf0, 0x5c, 0xf8, 0x47, - 0x29, 0x4f, 0x24, 0xbb, 0x48, 0x25, 0x51, 0x4f, 0x2f, 0x48, 0xb9, 0x18, 0xdc, 0x1f, 0xa4, 0x3c, - 0x9c, 0x85, 0x6f, 0x76, 0xbb, 0xeb, 0xde, 0x57, 0x71, 0x22, 0x72, 0xc5, 0x93, 0xac, 0xd0, 0x47, - 0x3f, 0x0d, 0xb0, 0x5f, 0xa5, 0x3c, 0x21, 0x04, 0x76, 0xca, 0x13, 0x81, 0xad, 0xa1, 0x35, 0xee, - 0x30, 0x33, 0x6b, 0x76, 0x8a, 0xd3, 0x4f, 0xdc, 0x28, 0x98, 0x9e, 0x35, 0xcb, 0xb8, 0x3a, 0xe2, - 0x66, 0xc1, 0xf4, 0x8c, 0x06, 0xd0, 0x4e, 0x78, 0x1a, 0xef, 0x45, 0xae, 0xb0, 0x6d, 0x78, 0x7d, - 0x46, 0x0f, 0xe0, 0x88, 0x2f, 0x91, 0xaa, 0x1c, 0xb7, 0x86, 0xcd, 0x71, 0x7f, 0x82, 0xc9, 0xdf, - 0x0f, 0x12, 0xfd, 0x36, 0xa1, 0x5a, 0x60, 0xa5, 0x87, 0x9e, 0xa0, 0x7d, 0xe6, 0xb9, 0xda, 0x5e, - 0xae, 0x29, 0x76, 0x86, 0xd6, 0xb8, 0x3b, 0x19, 0x90, 0xa2, 0x06, 0xa9, 0x6a, 0x90, 0xa8, 0xaa, - 0xc1, 0x5c, 0xed, 0xb2, 0x6b, 0x3a, 0xfa, 0xb6, 0xa0, 0x65, 0x82, 0x50, 0x17, 0xdc, 0x4d, 0xb8, - 0x08, 0x97, 0xef, 0xa1, 0xf7, 0x0f, 0xdd, 0x40, 0x77, 0xc5, 0xe8, 0xf6, 0x2d, 0x5c, 0x47, 0xd3, - 0x20, 0xf0, 0x2c, 0xe4, 0x41, 0x6f, 0xb5, 0x5c, 0x47, 0x35, 0x69, 0xa0, 0x3e, 0x80, 0x56, 0xe6, - 0x34, 0xa0, 0x11, 0xf5, 0x9a, 0xe6, 0x8a, 0x36, 0x4a, 0x60, 0x57, 0x19, 0x9b, 0xd5, 0x0b, 0x9b, - 0xce, 0xa9, 0xd7, 0xaa, 0x33, 0x2a, 0xe2, 0x18, 0xc2, 0xe8, 0x96, 0x2d, 0x83, 0x60, 0x36, 0x7d, - 0x5e, 0x78, 0x2e, 0xba, 0x85, 0xff, 0xc6, 0xa9, 0x51, 0x7b, 0xd6, 0xf9, 0x70, 0xcb, 0xde, 0x3b, - 0xc7, 0x54, 0x79, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x2e, 0x6f, 0xbd, 0xc8, 0x01, 0x00, - 0x00, + // 334 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0xcd, 0x6e, 0xe2, 0x30, + 0x14, 0x85, 0x27, 0x10, 0x12, 0x30, 0x0c, 0xe3, 0xf1, 0x66, 0x2c, 0x36, 0x83, 0x58, 0xb1, 0x72, + 0x2a, 0xaa, 0x3e, 0x40, 0x28, 0x56, 0x5b, 0x11, 0x05, 0xe4, 0x04, 0x55, 0xea, 0x26, 0x32, 0xaa, + 0x81, 0x08, 0x12, 0x47, 0xc4, 0xf4, 0xc1, 0xfa, 0x7c, 0x5d, 0x54, 0x76, 0x7e, 0xd4, 0xdd, 0xcd, + 0x77, 0xbf, 0x1c, 0xfb, 0x18, 0xfc, 0x3b, 0xf1, 0x22, 0xf5, 0xae, 0xe2, 0x22, 0x78, 0x29, 0xbc, + 0x93, 0x94, 0x67, 0x52, 0x5c, 0xa5, 0x92, 0x68, 0xa4, 0x17, 0xa4, 0x5e, 0x4c, 0xfe, 0x1f, 0xa5, + 0x3c, 0x5e, 0x84, 0x67, 0x76, 0xfb, 0xdb, 0xc1, 0x53, 0x69, 0x26, 0x4a, 0xc5, 0xb3, 0xa2, 0xd2, + 0x67, 0x5f, 0x1d, 0x60, 0x3f, 0x4b, 0x79, 0x46, 0x08, 0xd8, 0x39, 0xcf, 0x04, 0xb6, 0xa6, 0xd6, + 0x7c, 0xc0, 0xcc, 0xac, 0xd9, 0x39, 0xcd, 0xdf, 0x71, 0xa7, 0x62, 0x7a, 0xd6, 0xac, 0xe0, 0xea, + 0x84, 0xbb, 0x15, 0xd3, 0x33, 0x9a, 0x80, 0x7e, 0xc6, 0xf3, 0xf4, 0x20, 0x4a, 0x85, 0x6d, 0xc3, + 0xdb, 0x6f, 0x74, 0x07, 0x1c, 0xf1, 0x21, 0x72, 0x55, 0xe2, 0xde, 0xb4, 0x3b, 0x1f, 0x2f, 0x30, + 0xf9, 0x79, 0x41, 0xa2, 0xcf, 0x26, 0x54, 0x0b, 0xac, 0xf6, 0xd0, 0x03, 0xe8, 0x5f, 0x78, 0xa9, + 0x92, 0xeb, 0x2d, 0xc7, 0xce, 0xd4, 0x9a, 0x0f, 0x17, 0x13, 0x52, 0xd5, 0x20, 0x4d, 0x0d, 0x12, + 0x37, 0x35, 0x98, 0xab, 0x5d, 0x76, 0xcb, 0x67, 0x9f, 0x16, 0xe8, 0x99, 0x20, 0x34, 0x04, 0xee, + 0x2e, 0x5c, 0x87, 0x9b, 0xd7, 0x10, 0xfe, 0x42, 0x7f, 0xc0, 0x70, 0xcb, 0x68, 0xf2, 0x12, 0x46, + 0xb1, 0x1f, 0x04, 0xd0, 0x42, 0x10, 0x8c, 0xb6, 0x9b, 0x28, 0x6e, 0x49, 0x07, 0x8d, 0x01, 0xd0, + 0xca, 0x8a, 0x06, 0x34, 0xa6, 0xb0, 0x6b, 0x7e, 0xd1, 0x46, 0x0d, 0xec, 0x26, 0x63, 0xb7, 0x7d, + 0x62, 0xfe, 0x8a, 0xc2, 0x5e, 0x9b, 0xd1, 0x10, 0xc7, 0x10, 0x46, 0x13, 0xb6, 0x09, 0x82, 0xa5, + 0xff, 0xb8, 0x86, 0x2e, 0xfa, 0x0b, 0x7e, 0x1b, 0xa7, 0x45, 0x7d, 0x2d, 0x31, 0x1a, 0x50, 0x3f, + 0xa2, 0x49, 0x4c, 0xa3, 0x18, 0x0e, 0x96, 0x83, 0x37, 0xb7, 0x7e, 0x89, 0xbd, 0x63, 0xca, 0xdd, + 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x35, 0xb7, 0x2a, 0x22, 0xda, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/release/release.pb.go b/pkg/proto/hapi/release/release.pb.go index 72255e3e2..55360f4b7 100644 --- a/pkg/proto/hapi/release/release.pb.go +++ b/pkg/proto/hapi/release/release.pb.go @@ -35,6 +35,8 @@ type Release struct { Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` // Namespace is the kubernetes namespace of the release. Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"` + // TestSuite provides results on the last test run on a release + TestSuite *TestSuite `protobuf:"bytes,9,opt,name=test_suite,json=testSuite" json:"test_suite,omitempty"` } func (m *Release) Reset() { *m = Release{} } @@ -70,6 +72,13 @@ func (m *Release) GetHooks() []*Hook { return nil } +func (m *Release) GetTestSuite() *TestSuite { + if m != nil { + return m.TestSuite + } + return nil +} + func init() { proto.RegisterType((*Release)(nil), "hapi.release.Release") } @@ -77,21 +86,24 @@ func init() { func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 256 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x40, - 0x0c, 0xc6, 0x95, 0x36, 0x7f, 0x1a, 0xc3, 0x82, 0x07, 0xb0, 0x22, 0x86, 0x88, 0x01, 0x22, 0x86, - 0x54, 0x82, 0x37, 0x80, 0x05, 0xd6, 0x1b, 0xd9, 0x8e, 0xe8, 0x42, 0x4e, 0xa5, 0xe7, 0x28, 0x17, - 0xf1, 0x2c, 0x3c, 0x2e, 0xba, 0x3f, 0x85, 0x94, 0x2e, 0x4e, 0xec, 0xdf, 0xa7, 0xcf, 0xdf, 0x19, - 0xaa, 0x41, 0x8e, 0x7a, 0x3b, 0xa9, 0x4f, 0x25, 0xad, 0x3a, 0x7c, 0xdb, 0x71, 0xe2, 0x99, 0xf1, - 0xdc, 0xb1, 0x36, 0xce, 0xaa, 0xab, 0x23, 0xe5, 0xc0, 0xbc, 0x0b, 0xb2, 0x7f, 0x40, 0x9b, 0x9e, - 0x8f, 0x40, 0x37, 0xc8, 0x69, 0xde, 0x76, 0x6c, 0x7a, 0xfd, 0x11, 0xc1, 0xe5, 0x12, 0xb8, 0x1a, - 0xe6, 0x37, 0xdf, 0x2b, 0x28, 0x44, 0xf0, 0x41, 0x84, 0xd4, 0xc8, 0xbd, 0xa2, 0xa4, 0x4e, 0x9a, - 0x52, 0xf8, 0x7f, 0xbc, 0x85, 0xd4, 0xd9, 0xd3, 0xaa, 0x4e, 0x9a, 0xb3, 0x07, 0x6c, 0x97, 0xf9, - 0xda, 0x57, 0xd3, 0xb3, 0xf0, 0x1c, 0xef, 0x20, 0xf3, 0xb6, 0xb4, 0xf6, 0xc2, 0x8b, 0x20, 0x0c, - 0x9b, 0x9e, 0x5d, 0x15, 0x81, 0xe3, 0x3d, 0xe4, 0x21, 0x18, 0xa5, 0x4b, 0xcb, 0xa8, 0xf4, 0x44, - 0x44, 0x05, 0x56, 0xb0, 0xd9, 0x4b, 0xa3, 0x7b, 0x65, 0x67, 0xca, 0x7c, 0xa8, 0xdf, 0x1e, 0x1b, - 0xc8, 0xdc, 0x41, 0x2c, 0xe5, 0xf5, 0xfa, 0x34, 0xd9, 0x0b, 0xf3, 0x4e, 0x04, 0x01, 0x12, 0x14, - 0x5f, 0x6a, 0xb2, 0x9a, 0x0d, 0x15, 0x75, 0xd2, 0x64, 0xe2, 0xd0, 0xe2, 0x35, 0x94, 0xee, 0x91, - 0x76, 0x94, 0x9d, 0xa2, 0x8d, 0x5f, 0xf0, 0x37, 0x78, 0x2a, 0xdf, 0x8a, 0x68, 0xf7, 0x9e, 0xfb, - 0x63, 0x3d, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x8f, 0xec, 0x97, 0xbb, 0x01, 0x00, 0x00, + // 290 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x30, + 0x10, 0xc6, 0x95, 0x36, 0x7f, 0x9a, 0x83, 0x85, 0x1b, 0xa8, 0x15, 0x81, 0x14, 0x31, 0x40, 0xc4, + 0x90, 0x4a, 0x20, 0xf1, 0x00, 0xb0, 0xc0, 0x6a, 0x98, 0x58, 0x90, 0x89, 0x1c, 0x62, 0x95, 0xda, + 0x51, 0x6c, 0x78, 0x4e, 0x1e, 0x09, 0xf9, 0x4f, 0x68, 0x42, 0x17, 0xc7, 0x77, 0xbf, 0x2f, 0xf7, + 0x7d, 0x3e, 0x28, 0x3a, 0xd6, 0x8b, 0xcd, 0xc0, 0x3f, 0x39, 0xd3, 0x7c, 0xfc, 0xd6, 0xfd, 0xa0, + 0x8c, 0xc2, 0x63, 0xcb, 0xea, 0xd0, 0x2b, 0xd6, 0x33, 0x65, 0xa7, 0xd4, 0xd6, 0xcb, 0xfe, 0x01, + 0x21, 0x5b, 0x15, 0xc0, 0xf9, 0x0c, 0x18, 0xae, 0xcd, 0x9b, 0xfe, 0x12, 0x86, 0xcf, 0xfe, 0x6b, + 0x3a, 0x36, 0x98, 0x4d, 0xa3, 0x64, 0x2b, 0x3e, 0x02, 0x38, 0x9d, 0x02, 0x7b, 0xfa, 0xfe, 0xc5, + 0xcf, 0x02, 0x32, 0xea, 0xa7, 0x21, 0x42, 0x2c, 0xd9, 0x8e, 0x93, 0xa8, 0x8c, 0xaa, 0x9c, 0xba, + 0x3b, 0x5e, 0x42, 0x6c, 0xdd, 0xc9, 0xa2, 0x8c, 0xaa, 0xa3, 0x1b, 0xac, 0xa7, 0xf1, 0xeb, 0x27, + 0xd9, 0x2a, 0xea, 0x38, 0x5e, 0x41, 0xe2, 0xc6, 0x92, 0xa5, 0x13, 0x9e, 0x78, 0xa1, 0x77, 0x7a, + 0xb0, 0x27, 0xf5, 0x1c, 0xaf, 0x21, 0xf5, 0xc1, 0x48, 0x3c, 0x1d, 0x19, 0x94, 0x8e, 0xd0, 0xa0, + 0xc0, 0x02, 0x56, 0x3b, 0x26, 0x45, 0xcb, 0xb5, 0x21, 0x89, 0x0b, 0xf5, 0x57, 0x63, 0x05, 0x89, + 0xdd, 0x97, 0x26, 0x69, 0xb9, 0x3c, 0x4c, 0xf6, 0xa8, 0xd4, 0x96, 0x7a, 0x01, 0x12, 0xc8, 0xbe, + 0xf9, 0xa0, 0x85, 0x92, 0x24, 0x2b, 0xa3, 0x2a, 0xa1, 0x63, 0x89, 0x67, 0x90, 0xdb, 0x47, 0xea, + 0x9e, 0x35, 0x9c, 0xac, 0x9c, 0xc1, 0xbe, 0x81, 0x77, 0x00, 0xfb, 0xfd, 0x92, 0xdc, 0xa5, 0x5d, + 0xcf, 0x6d, 0x5e, 0xb8, 0x36, 0xcf, 0x16, 0xd3, 0xdc, 0x8c, 0xd7, 0xfb, 0xfc, 0x35, 0x0b, 0xfc, + 0x3d, 0x75, 0x4b, 0xbe, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe6, 0xdb, 0x71, 0x12, 0x02, + 0x00, 0x00, } diff --git a/pkg/proto/hapi/release/test_result.pb.go b/pkg/proto/hapi/release/test_result.pb.go new file mode 100644 index 000000000..691b66abb --- /dev/null +++ b/pkg/proto/hapi/release/test_result.pb.go @@ -0,0 +1,85 @@ +// 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_suite.pb.go b/pkg/proto/hapi/release/test_suite.pb.go new file mode 100644 index 000000000..bc6357cbd --- /dev/null +++ b/pkg/proto/hapi/release/test_suite.pb.go @@ -0,0 +1,64 @@ +// Code generated by protoc-gen-go. +// source: hapi/release/test_suite.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 + +// 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"` + // Results are the results of each segment of the test + Results []*TestResult `protobuf:"bytes,2,rep,name=results" json:"results,omitempty"` +} + +func (m *TestSuite) Reset() { *m = TestSuite{} } +func (m *TestSuite) String() string { return proto.CompactTextString(m) } +func (*TestSuite) ProtoMessage() {} +func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} } + +func (m *TestSuite) GetLastRun() *google_protobuf.Timestamp { + if m != nil { + return m.LastRun + } + return nil +} + +func (m *TestSuite) GetResults() []*TestResult { + if m != nil { + return m.Results + } + return nil +} + +func init() { + proto.RegisterType((*TestSuite)(nil), "hapi.release.TestSuite") +} + +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, +} diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 9faca5f8b..bfe6cb43d 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -28,6 +28,8 @@ It has these top-level messages: GetVersionResponse GetHistoryRequest GetHistoryResponse + TestReleaseRequest + TestReleaseResponse */ package services @@ -36,10 +38,11 @@ import fmt "fmt" import math "math" import hapi_chart3 "k8s.io/helm/pkg/proto/hapi/chart" import hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" -import hapi_release3 "k8s.io/helm/pkg/proto/hapi/release" +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" @@ -153,7 +156,7 @@ type ListReleasesResponse struct { // Total is the total number of queryable releases. Total int64 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"` // Releases is the list of found release objects. - Releases []*hapi_release3.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"` + Releases []*hapi_release5.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"` } func (m *ListReleasesResponse) Reset() { *m = ListReleasesResponse{} } @@ -161,7 +164,7 @@ func (m *ListReleasesResponse) String() string { return proto.Compact func (*ListReleasesResponse) ProtoMessage() {} func (*ListReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } -func (m *ListReleasesResponse) GetReleases() []*hapi_release3.Release { +func (m *ListReleasesResponse) GetReleases() []*hapi_release5.Release { if m != nil { return m.Releases } @@ -219,7 +222,7 @@ func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescr // GetReleaseContentResponse is a response containing the contents of a release. type GetReleaseContentResponse struct { // The release content - Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *GetReleaseContentResponse) Reset() { *m = GetReleaseContentResponse{} } @@ -227,7 +230,7 @@ func (m *GetReleaseContentResponse) String() string { return proto.Co func (*GetReleaseContentResponse) ProtoMessage() {} func (*GetReleaseContentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } -func (m *GetReleaseContentResponse) GetRelease() *hapi_release3.Release { +func (m *GetReleaseContentResponse) GetRelease() *hapi_release5.Release { if m != nil { return m.Release } @@ -278,7 +281,7 @@ func (m *UpdateReleaseRequest) GetValues() *hapi_chart.Config { // UpdateReleaseResponse is the response to an update request. type UpdateReleaseResponse struct { - Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} } @@ -286,7 +289,7 @@ func (m *UpdateReleaseResponse) String() string { return proto.Compac func (*UpdateReleaseResponse) ProtoMessage() {} func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } -func (m *UpdateReleaseResponse) GetRelease() *hapi_release3.Release { +func (m *UpdateReleaseResponse) GetRelease() *hapi_release5.Release { if m != nil { return m.Release } @@ -318,7 +321,7 @@ func (*RollbackReleaseRequest) Descriptor() ([]byte, []int) { return fileDescrip // RollbackReleaseResponse is the response to an update request. type RollbackReleaseResponse struct { - Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *RollbackReleaseResponse) Reset() { *m = RollbackReleaseResponse{} } @@ -326,7 +329,7 @@ func (m *RollbackReleaseResponse) String() string { return proto.Comp func (*RollbackReleaseResponse) ProtoMessage() {} func (*RollbackReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } -func (m *RollbackReleaseResponse) GetRelease() *hapi_release3.Release { +func (m *RollbackReleaseResponse) GetRelease() *hapi_release5.Release { if m != nil { return m.Release } @@ -381,7 +384,7 @@ func (m *InstallReleaseRequest) GetValues() *hapi_chart.Config { // InstallReleaseResponse is the response from a release installation. type InstallReleaseResponse struct { - Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } @@ -389,7 +392,7 @@ func (m *InstallReleaseResponse) String() string { return proto.Compa func (*InstallReleaseResponse) ProtoMessage() {} func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } -func (m *InstallReleaseResponse) GetRelease() *hapi_release3.Release { +func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release { if m != nil { return m.Release } @@ -416,7 +419,7 @@ func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescri // UninstallReleaseResponse represents a successful response to an uninstall request. type UninstallReleaseResponse struct { // Release is the release that was marked deleted. - Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` // Info is an uninstall message Info string `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` } @@ -426,7 +429,7 @@ func (m *UninstallReleaseResponse) String() string { return proto.Com func (*UninstallReleaseResponse) ProtoMessage() {} func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } -func (m *UninstallReleaseResponse) GetRelease() *hapi_release3.Release { +func (m *UninstallReleaseResponse) GetRelease() *hapi_release5.Release { if m != nil { return m.Release } @@ -443,7 +446,7 @@ func (*GetVersionRequest) ProtoMessage() {} func (*GetVersionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } type GetVersionResponse struct { - Version *hapi_version.Version `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"` + Version *hapi_version.Version `protobuf:"bytes,1,opt,name=Version,json=version" json:"Version,omitempty"` } func (m *GetVersionResponse) Reset() { *m = GetVersionResponse{} } @@ -473,7 +476,7 @@ func (*GetHistoryRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, // GetHistoryResponse is received in response to a GetHistory rpc. type GetHistoryResponse struct { - Releases []*hapi_release3.Release `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"` + Releases []*hapi_release5.Release `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"` } func (m *GetHistoryResponse) Reset() { *m = GetHistoryResponse{} } @@ -481,13 +484,44 @@ func (m *GetHistoryResponse) String() string { return proto.CompactTe func (*GetHistoryResponse) ProtoMessage() {} func (*GetHistoryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } -func (m *GetHistoryResponse) GetReleases() []*hapi_release3.Release { +func (m *GetHistoryResponse) GetReleases() []*hapi_release5.Release { if m != nil { return m.Releases } return nil } +// TestReleaseRequest is a request to get the status of a release. +type TestReleaseRequest struct { + // Name is the name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,2,opt,name=timeout" json:"timeout,omitempty"` +} + +func (m *TestReleaseRequest) Reset() { *m = TestReleaseRequest{} } +func (m *TestReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*TestReleaseRequest) ProtoMessage() {} +func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } + +// 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"` +} + +func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} } +func (m *TestReleaseResponse) String() string { return proto.CompactTextString(m) } +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") @@ -508,6 +542,8 @@ func init() { proto.RegisterType((*GetVersionResponse)(nil), "hapi.services.tiller.GetVersionResponse") proto.RegisterType((*GetHistoryRequest)(nil), "hapi.services.tiller.GetHistoryRequest") proto.RegisterType((*GetHistoryResponse)(nil), "hapi.services.tiller.GetHistoryResponse") + proto.RegisterType((*TestReleaseRequest)(nil), "hapi.services.tiller.TestReleaseRequest") + proto.RegisterType((*TestReleaseResponse)(nil), "hapi.services.tiller.TestReleaseResponse") proto.RegisterEnum("hapi.services.tiller.ListSort_SortBy", ListSort_SortBy_name, ListSort_SortBy_value) proto.RegisterEnum("hapi.services.tiller.ListSort_SortOrder", ListSort_SortOrder_name, ListSort_SortOrder_value) } @@ -544,6 +580,9 @@ type ReleaseServiceClient interface { RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error) // ReleaseHistory retrieves a releasse's history. 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) } type releaseServiceClient struct { @@ -658,6 +697,15 @@ 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...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for ReleaseService service type ReleaseServiceServer interface { @@ -682,6 +730,9 @@ type ReleaseServiceServer interface { RollbackRelease(context.Context, *RollbackReleaseRequest) (*RollbackReleaseResponse, error) // ReleaseHistory retrieves a releasse's history. 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) } func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) { @@ -853,6 +904,24 @@ 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)) + } + return interceptor(ctx, in, info, handler) +} + var _ReleaseService_serviceDesc = grpc.ServiceDesc{ ServiceName: "hapi.services.tiller.ReleaseService", HandlerType: (*ReleaseServiceServer)(nil), @@ -889,6 +958,10 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetHistory", Handler: _ReleaseService_GetHistory_Handler, }, + { + MethodName: "RunReleaseTest", + Handler: _ReleaseService_RunReleaseTest_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 7e8e94ab6..5ddff63cc 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -24,6 +24,7 @@ package environment import ( "io" + "time" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" @@ -31,6 +32,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage/driver" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -135,6 +137,8 @@ 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) } // PrintingKubeClient implements KubeClient, but simply prints the reader to diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 1687f2f3b..7e0857897 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -40,6 +40,7 @@ const ( postUpgrade = "post-upgrade" preRollback = "pre-rollback" postRollback = "post-rollback" + releaseTest = "test" ) var events = map[string]release.Hook_Event{ @@ -51,6 +52,7 @@ var events = map[string]release.Hook_Event{ postUpgrade: release.Hook_POST_UPGRADE, preRollback: release.Hook_PRE_ROLLBACK, postRollback: release.Hook_POST_ROLLBACK, + releaseTest: release.Hook_RELEASE_TEST, } type simpleHead struct { diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 9ef14c915..40e9fa75f 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -1064,3 +1064,29 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro _, err := c.Build(ns, r) return err } + +// RunTestRelease runs a pre-defined test on a given release +func (s *ReleaseServer) RunReleaseTest(c ctx.Context, req *services.TestReleaseRequest) (*services.TestReleaseResponse, error) { + + res := &services.TestReleaseResponse{} + if !ValidName.MatchString(req.Name) { + return nil, errMissingRelease + } + + // finds the non-deleted release with the given name + r, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, err + } + + kubeCli := s.env.KubeClient + testSuite, err := runReleaseTestSuite(r.Hooks, kubeCli, r.Name, r.Namespace, req.Timeout) + if err != nil { + return nil, err + } + + r.TestSuite = testSuite + res.Result = testSuite + + return res, nil +} diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go new file mode 100644 index 000000000..64fffc094 --- /dev/null +++ b/pkg/tiller/release_testing.go @@ -0,0 +1,147 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tiller + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/ghodss/yaml" + "k8s.io/kubernetes/pkg/api" + + "k8s.io/helm/pkg/proto/hapi/release" + "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) { + + suite := &release.TestSuite{} + suite.LastRun = timeconv.Now() + results := []*release.TestResult{} + + tests, err := prepareTests(hooks, name) + if err != nil { + return suite, err + } + + 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() + + 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 + resourceCreated = false + } + + status := api.PodUnknown + resourceCleanExit := true + if resourceCreated { + b.Reset() + b.WriteString(h) + status, err = kube.WaitAndGetCompletedPodStatus(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 + } + } + + // 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 + } else if resourceCreated && resourceCleanExit && status == api.PodFailed { + ts.Status = 2 + } + + results = append(results, ts) + log.Printf("Test %s(%s) complete", ts.Name, sh.Kind) + + //TODO: recordTests() - add test results to configmap with standardized name + } + + suite.Results = results + log.Printf("Finished running test suite for %s", name) + + return suite, nil +} + +func filterTests(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) { + testHooks := []*release.Hook{} + notFoundErr := fmt.Errorf("no tests found for release %s", releaseName) + if len(hooks) == 0 { + return nil, notFoundErr + } + + code, ok := events[releaseTest] + if !ok { + return nil, fmt.Errorf("unknown hook %q", releaseTest) + } + + found := false + for _, h := range hooks { + for _, e := range h.Events { + if e == code { + found = true + testHooks = append(testHooks, h) + continue + } + } + } + + //TODO: probably don't need to check found + if found == false && len(testHooks) == 0 { + return nil, notFoundErr + } + + return testHooks, nil +} + +func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) { + testHooks, err := filterTests(hooks, releaseName) + if err != nil { + return nil, err + } + + tests := []string{} + for _, h := range testHooks { + individualTests := splitManifests(h.Manifest) + for _, t := range individualTests { + tests = append(tests, t) + } + } + return tests, nil +} From 58c05f87d7bf73353aa77ae4cacf50af1dc26d4f Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 25 Jan 2017 15:22:48 -0500 Subject: [PATCH 2/8] 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 +} From 9bd12953a91efaa0636f810b1f36cff89f9009b8 Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Tue, 17 Jan 2017 09:50:12 -0700 Subject: [PATCH 3/8] feat(*): add kube client test * for WaitAndGetCompletedPodPhase --- cmd/helm/helm_test.go | 4 + pkg/kube/client.go | 6 +- pkg/kube/client_test.go | 135 +++++++++++++++------ pkg/tiller/environment/environment.go | 11 +- pkg/tiller/environment/environment_test.go | 3 + pkg/tiller/release_testing.go | 4 +- 6 files changed, 117 insertions(+), 46 deletions(-) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index fc4c86b68..758acd12d 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -190,6 +190,10 @@ func (c *fakeReleaseClient) ReleaseHistory(rlsName string, opts ...helm.HistoryO return &rls.GetHistoryResponse{Releases: c.rels}, c.err } +func (c *fakeReleaseClient) ReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (*rls.TestReleaseResponse, error) { + return nil, nil +} + func (c *fakeReleaseClient) Option(opt ...helm.Option) helm.Interface { return c } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 89d044ff1..0f4eb58ac 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -616,14 +616,16 @@ func scrubValidationError(err error) error { return err } -func (c *Client) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { +// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase +// and returns said phase (PodSucceeded or PodFailed qualify) +func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { infos, err := c.Build(namespace, reader) if err != nil { return api.PodUnknown, err } info := infos[0] - // TODO: should we be checking kind before hand? probably yes. + // TODO: should we be checking kind beforehand? probably yes. // TODO: add validation to linter: any manifest with a test hook has to be a pod kind? kind := info.Mapping.GroupVersionKind.Kind if kind != "Pod" { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 90cf4e6d5..0c9cf788b 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -18,6 +18,7 @@ package kube import ( "bytes" + "encoding/json" "io" "io/ioutil" "net/http" @@ -37,6 +38,8 @@ import ( "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + watchjson "k8s.io/kubernetes/pkg/watch/json" ) func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { @@ -44,10 +47,18 @@ func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { } func newPod(name string) api.Pod { + return newPodWithStatus(name, api.PodStatus{}, "") +} + +func newPodWithStatus(name string, status api.PodStatus, namespace string) api.Pod { + ns := api.NamespaceDefault + if namespace != "" { + ns = namespace + } return api.Pod{ ObjectMeta: api.ObjectMeta{ Name: name, - Namespace: api.NamespaceDefault, + Namespace: ns, }, Spec: api.PodSpec{ Containers: []api.Container{{ @@ -56,6 +67,7 @@ func newPod(name string) api.Pod { Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}}, }}, }, + Status: status, } } @@ -102,6 +114,32 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e return f.reaper, nil } +func newEventResponse(code int, e *watch.Event) (*http.Response, error) { + dispatchedEvent, err := encodeAndMarshalEvent(e) + if err != nil { + return nil, err + } + + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + body := ioutil.NopCloser(bytes.NewReader(dispatchedEvent)) + return &http.Response{StatusCode: 200, Header: header, Body: body}, nil +} + +func encodeAndMarshalEvent(e *watch.Event) ([]byte, error) { + encodedEvent, err := watchjson.Object(testapi.Default.Codec(), e) + if err != nil { + return nil, err + } + + marshaledEvent, err := json.Marshal(encodedEvent) + if err != nil { + return nil, err + } + + return marshaledEvent, nil +} + func TestUpdate(t *testing.T) { listA := newPodList("starfish", "otter", "squid") listB := newPodList("starfish", "otter", "dolphin") @@ -305,48 +343,69 @@ func TestPerform(t *testing.T) { } } -func TestWaitAndGetCompletedPodStatus(t *testing.T) { - f, tf, codec, ns := cmdtesting.NewAPIFactory() - actions := make(map[string]string) - testPodList := newPodList("bestpod") +func TestWaitAndGetCompletedPodPhase(t *testing.T) { + tests := []struct { + podPhase api.PodPhase + expectedPhase api.PodPhase + err bool + errMessage string + }{ + { + podPhase: api.PodPending, + expectedPhase: api.PodUnknown, + err: true, + errMessage: "timed out waiting for the condition", + }, { + podPhase: api.PodRunning, + expectedPhase: api.PodUnknown, + err: true, + errMessage: "timed out waiting for the condition", + }, { + podPhase: api.PodSucceeded, + expectedPhase: api.PodSucceeded, + }, { + podPhase: api.PodFailed, + expectedPhase: api.PodFailed, + }, + } - tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - p, m := req.URL.Path, req.Method - actions[p] = m - count := 0 - switch { - case p == "/namespaces/test/pods/bestpod" && m == "GET": - return newResponse(200, &testPodList.Items[0]) - case p == "/watch/namespaces/test/pods/bestpod" && m == "GET": - //TODO: fix response - count = count + 1 - if count == 1 { - //returns event running - return newResponse(200, &testPodList.Items[0]) - } - if count == 2 { - //return event succeeded + for _, tt := range tests { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + actions := make(map[string]string) + + var testPodList api.PodList + testPodList.Items = append(testPodList.Items, newPodWithStatus("bestpod", api.PodStatus{Phase: tt.podPhase}, "test")) + + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + actions[p] = m + switch { + case p == "/namespaces/test/pods/bestpod" && m == "GET": return newResponse(200, &testPodList.Items[0]) + case p == "/watch/namespaces/test/pods/bestpod" && m == "GET": + event := watch.Event{Type: watch.Added, Object: &testPodList.Items[0]} + return newEventResponse(200, &event) + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - - c := &Client{Factory: f} - //stub watchUntil to return no error + }), + } - status, err := c.WaitAndGetCompletedPodStatus("test", objBody(codec, &testPodList), 30*time.Second) - if err != nil { - t.Fatal(err) - } + c := &Client{Factory: f} - if status != api.PodSucceeded { - t.Fatal("Expected %s, got %s", api.PodSucceeded, status) + phase, err := c.WaitAndGetCompletedPodPhase("test", objBody(codec, &testPodList), 1*time.Second) + if (err != nil) != tt.err { + t.Fatalf("Expected error but there was none.") + } + if err != nil && err.Error() != tt.errMessage { + t.Fatalf("Expected error %s, got %s", tt.errMessage, err.Error()) + } + if phase != tt.expectedPhase { + t.Fatalf("Expected pod phase %s, got %s", tt.expectedPhase, phase) + } } } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 010d14060..fa5d1ecab 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -138,8 +138,9 @@ type KubeClient interface { Build(namespace string, reader io.Reader) (kube.Result, error) - //TODO: insert description - WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) + // 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) (api.PodPhase, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -185,8 +186,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 +// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase +func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + _, err := io.Copy(p.Out, reader) + return api.PodUnknown, err } // Environment provides the context for executing a client request. diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index 7544a3938..cb36de356 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -57,6 +57,9 @@ func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } +func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { + return api.PodUnknown, nil +} func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { return "", nil diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go index 90379887a..ff601c697 100644 --- a/pkg/tiller/release_testing.go +++ b/pkg/tiller/release_testing.go @@ -82,7 +82,7 @@ func runReleaseTests(tests []string, rel *release.Release, kube environment.Kube if resourceCreated { b.Reset() b.WriteString(h) - status, err = kube.WaitAndGetCompletedPodStatus(rel.Namespace, b, time.Duration(timeout)*time.Second) + status, err = kube.WaitAndGetCompletedPodPhase(rel.Namespace, b, time.Duration(timeout)*time.Second) if err != nil { resourceCleanExit = false log.Printf("Error getting status for pod %s: %s", ts.Name, err) @@ -145,7 +145,7 @@ func filterTests(hooks []*release.Hook, releaseName string) ([]*release.Hook, er } //TODO: probably don't need to check found - if found == false && len(testHooks) == 0 { + if !found && len(testHooks) == 0 { return nil, notFoundErr } From e1321912756f065afdca72496708b70baf66d0b2 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 30 Jan 2017 16:29:28 -0500 Subject: [PATCH 4/8] ref(pkg/): refactor helm test logic pulled logic out in pkg/releasetesting --- _proto/hapi/release/release.proto | 4 +- cmd/helm/status.go | 1 + pkg/kube/client.go | 3 - pkg/proto/hapi/release/info.pb.go | 30 ++-- pkg/proto/hapi/release/release.pb.go | 48 +++--- pkg/proto/hapi/services/tiller.pb.go | 141 +++++++++--------- pkg/releasetesting/environment.go | 81 ++++++++++ pkg/releasetesting/release_testing.go | 195 +++++++++++++++++++++++++ pkg/releaseutil/manifest.go | 46 ++++++ pkg/tiller/hooks.go | 14 +- pkg/tiller/hooks_test.go | 3 +- pkg/tiller/kind_sorter_test.go | 12 +- pkg/tiller/release_server.go | 36 ++--- pkg/tiller/release_testing.go | 203 -------------------------- 14 files changed, 463 insertions(+), 354 deletions(-) create mode 100644 pkg/releasetesting/environment.go create mode 100644 pkg/releasetesting/release_testing.go create mode 100644 pkg/releaseutil/manifest.go delete mode 100644 pkg/tiller/release_testing.go diff --git a/_proto/hapi/release/release.proto b/_proto/hapi/release/release.proto index 1de073f50..efa2298d1 100644 --- a/_proto/hapi/release/release.proto +++ b/_proto/hapi/release/release.proto @@ -52,6 +52,6 @@ message Release { // Namespace is the kubernetes namespace of the release. string namespace = 8; - // TestSuite provides results on the last test run on a release - hapi.release.TestSuite test_suite = 9; + // LastTestSuiteRun provides results on the last test run on a release + hapi.release.TestSuite last_test_suite_run = 9; } diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 400429625..7b857a3d3 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -103,6 +103,7 @@ func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) { fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(res.Info.Status.Resources, "\t")) w.Flush() } + if len(res.Info.Status.Notes) > 0 { fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Status.Notes) } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 0f4eb58ac..48f9975e6 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -307,9 +307,6 @@ func perform(c *Client, namespace string, infos Result, fn ResourceActorFunc) er return ErrNoObjectsVisited } - if err != nil { - return err - } for _, info := range infos { if err := fn(info); err != nil { return err diff --git a/pkg/proto/hapi/release/info.pb.go b/pkg/proto/hapi/release/info.pb.go index a73dcab2f..2a0ec863d 100644 --- a/pkg/proto/hapi/release/info.pb.go +++ b/pkg/proto/hapi/release/info.pb.go @@ -22,7 +22,7 @@ type Info struct { // Deleted tracks when this object was deleted. Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"` // Description is human-friendly "log entry" about this release. - Description string `protobuf:"bytes,5,opt,name=Description" json:"Description,omitempty"` + Description string `protobuf:"bytes,5,opt,name=Description,json=description" json:"Description,omitempty"` } func (m *Info) Reset() { *m = Info{} } @@ -65,20 +65,20 @@ func init() { func init() { proto.RegisterFile("hapi/release/info.proto", fileDescriptor1) } var fileDescriptor1 = []byte{ - // 235 bytes of a gzipped FileDescriptorProto + // 236 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x31, 0x4f, 0xc3, 0x30, - 0x10, 0x85, 0x95, 0x52, 0x5a, 0xd5, 0x6d, 0x19, 0x2c, 0x24, 0x42, 0x16, 0x22, 0xa6, 0x0e, 0xc8, + 0x10, 0x85, 0x95, 0x52, 0x5a, 0xd5, 0x69, 0x19, 0x2c, 0x24, 0x42, 0x16, 0x22, 0xa6, 0x0e, 0xc8, 0x91, 0x80, 0x1d, 0x81, 0xba, 0xb0, 0x06, 0x26, 0x16, 0xe4, 0xe2, 0x73, 0xb1, 0xe4, 0xe6, 0x2c, - 0xfb, 0x3a, 0xf0, 0x2f, 0xf8, 0xc9, 0xa8, 0xb6, 0x83, 0xd2, 0xa9, 0xab, 0xbf, 0xf7, 0x3e, 0xbf, - 0x63, 0x57, 0xdf, 0xd2, 0x99, 0xc6, 0x83, 0x05, 0x19, 0xa0, 0x31, 0x9d, 0x46, 0xe1, 0x3c, 0x12, - 0xf2, 0xc5, 0x01, 0x88, 0x0c, 0xaa, 0x9b, 0x2d, 0xe2, 0xd6, 0x42, 0x13, 0xd9, 0x66, 0xaf, 0x1b, - 0x32, 0x3b, 0x08, 0x24, 0x77, 0x2e, 0xc5, 0xab, 0xeb, 0x23, 0x4f, 0x20, 0x49, 0xfb, 0x90, 0xd0, - 0xed, 0xef, 0x88, 0x8d, 0x5f, 0x3b, 0x8d, 0xfc, 0x8e, 0x4d, 0x12, 0x28, 0x8b, 0xba, 0x58, 0xcd, - 0xef, 0x2f, 0xc5, 0xf0, 0x0f, 0xf1, 0x16, 0x59, 0x9b, 0x33, 0xfc, 0x99, 0x5d, 0x68, 0xe3, 0x03, - 0x7d, 0x2a, 0x70, 0x16, 0x7f, 0x40, 0x95, 0xa3, 0xd8, 0xaa, 0x44, 0xda, 0x22, 0xfa, 0x2d, 0xe2, - 0xbd, 0xdf, 0xd2, 0x2e, 0x63, 0x63, 0x9d, 0x0b, 0xfc, 0x89, 0x2d, 0xad, 0x1c, 0x1a, 0xce, 0x4e, - 0x1a, 0x16, 0x87, 0xc2, 0xbf, 0xe0, 0x91, 0x4d, 0x15, 0x58, 0x20, 0x50, 0xe5, 0xf8, 0x64, 0xb5, - 0x8f, 0xf2, 0x9a, 0xcd, 0xd7, 0x10, 0xbe, 0xbc, 0x71, 0x64, 0xb0, 0x2b, 0xcf, 0xeb, 0x62, 0x35, - 0x6b, 0x87, 0x4f, 0x2f, 0xb3, 0x8f, 0x69, 0xbe, 0x7a, 0x33, 0x89, 0xa6, 0x87, 0xbf, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x1a, 0x52, 0x8f, 0x9c, 0x89, 0x01, 0x00, 0x00, + 0xfb, 0x3a, 0xf0, 0x2f, 0xf8, 0xc9, 0xa8, 0xb6, 0x03, 0x65, 0xea, 0xea, 0xef, 0xbd, 0xcf, 0xef, + 0xd8, 0xc5, 0xa7, 0x74, 0xa6, 0xf5, 0x60, 0x41, 0x06, 0x68, 0x4d, 0xaf, 0x51, 0x38, 0x8f, 0x84, + 0x7c, 0xbe, 0x07, 0x22, 0x83, 0xfa, 0x6a, 0x83, 0xb8, 0xb1, 0xd0, 0x46, 0xb6, 0xde, 0xe9, 0x96, + 0xcc, 0x16, 0x02, 0xc9, 0xad, 0x4b, 0xf1, 0xfa, 0xf2, 0x9f, 0x27, 0x90, 0xa4, 0x5d, 0x48, 0xe8, + 0xfa, 0x7b, 0xc4, 0xc6, 0xcf, 0xbd, 0x46, 0x7e, 0xc3, 0x26, 0x09, 0x54, 0x45, 0x53, 0x2c, 0xcb, + 0xdb, 0x73, 0x71, 0xf8, 0x87, 0x78, 0x89, 0xac, 0xcb, 0x19, 0xfe, 0xc8, 0xce, 0xb4, 0xf1, 0x81, + 0xde, 0x15, 0x38, 0x8b, 0x5f, 0xa0, 0xaa, 0x51, 0x6c, 0xd5, 0x22, 0x6d, 0x11, 0xc3, 0x16, 0xf1, + 0x3a, 0x6c, 0xe9, 0x16, 0xb1, 0xb1, 0xca, 0x05, 0xfe, 0xc0, 0x16, 0x56, 0x1e, 0x1a, 0x4e, 0x8e, + 0x1a, 0xe6, 0xfb, 0xc2, 0xaf, 0xe0, 0x9e, 0x4d, 0x15, 0x58, 0x20, 0x50, 0xd5, 0xf8, 0x68, 0x75, + 0x88, 0xf2, 0x86, 0x95, 0x2b, 0x08, 0x1f, 0xde, 0x38, 0x32, 0xd8, 0x57, 0xa7, 0x4d, 0xb1, 0x9c, + 0x75, 0xa5, 0xfa, 0x7b, 0x7a, 0x9a, 0xbd, 0x4d, 0xf3, 0xd5, 0xeb, 0x49, 0x34, 0xdd, 0xfd, 0x04, + 0x00, 0x00, 0xff, 0xff, 0x1e, 0x2a, 0x57, 0x7d, 0x89, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/release/release.pb.go b/pkg/proto/hapi/release/release.pb.go index 55360f4b7..471a03c79 100644 --- a/pkg/proto/hapi/release/release.pb.go +++ b/pkg/proto/hapi/release/release.pb.go @@ -35,8 +35,8 @@ type Release struct { Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` // Namespace is the kubernetes namespace of the release. Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"` - // TestSuite provides results on the last test run on a release - TestSuite *TestSuite `protobuf:"bytes,9,opt,name=test_suite,json=testSuite" json:"test_suite,omitempty"` + // LastTestSuiteRun provides results on the last test run on a release + LastTestSuiteRun *TestSuite `protobuf:"bytes,9,opt,name=last_test_suite_run,json=lastTestSuiteRun" json:"last_test_suite_run,omitempty"` } func (m *Release) Reset() { *m = Release{} } @@ -72,9 +72,9 @@ func (m *Release) GetHooks() []*Hook { return nil } -func (m *Release) GetTestSuite() *TestSuite { +func (m *Release) GetLastTestSuiteRun() *TestSuite { if m != nil { - return m.TestSuite + return m.LastTestSuiteRun } return nil } @@ -86,24 +86,24 @@ func init() { func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 290 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x30, - 0x10, 0xc6, 0x95, 0x36, 0x7f, 0x9a, 0x83, 0x85, 0x1b, 0xa8, 0x15, 0x81, 0x14, 0x31, 0x40, 0xc4, - 0x90, 0x4a, 0x20, 0xf1, 0x00, 0xb0, 0xc0, 0x6a, 0x98, 0x58, 0x90, 0x89, 0x1c, 0x62, 0x95, 0xda, - 0x51, 0x6c, 0x78, 0x4e, 0x1e, 0x09, 0xf9, 0x4f, 0x68, 0x42, 0x17, 0xc7, 0x77, 0xbf, 0x2f, 0xf7, - 0x7d, 0x3e, 0x28, 0x3a, 0xd6, 0x8b, 0xcd, 0xc0, 0x3f, 0x39, 0xd3, 0x7c, 0xfc, 0xd6, 0xfd, 0xa0, - 0x8c, 0xc2, 0x63, 0xcb, 0xea, 0xd0, 0x2b, 0xd6, 0x33, 0x65, 0xa7, 0xd4, 0xd6, 0xcb, 0xfe, 0x01, - 0x21, 0x5b, 0x15, 0xc0, 0xf9, 0x0c, 0x18, 0xae, 0xcd, 0x9b, 0xfe, 0x12, 0x86, 0xcf, 0xfe, 0x6b, - 0x3a, 0x36, 0x98, 0x4d, 0xa3, 0x64, 0x2b, 0x3e, 0x02, 0x38, 0x9d, 0x02, 0x7b, 0xfa, 0xfe, 0xc5, - 0xcf, 0x02, 0x32, 0xea, 0xa7, 0x21, 0x42, 0x2c, 0xd9, 0x8e, 0x93, 0xa8, 0x8c, 0xaa, 0x9c, 0xba, - 0x3b, 0x5e, 0x42, 0x6c, 0xdd, 0xc9, 0xa2, 0x8c, 0xaa, 0xa3, 0x1b, 0xac, 0xa7, 0xf1, 0xeb, 0x27, - 0xd9, 0x2a, 0xea, 0x38, 0x5e, 0x41, 0xe2, 0xc6, 0x92, 0xa5, 0x13, 0x9e, 0x78, 0xa1, 0x77, 0x7a, - 0xb0, 0x27, 0xf5, 0x1c, 0xaf, 0x21, 0xf5, 0xc1, 0x48, 0x3c, 0x1d, 0x19, 0x94, 0x8e, 0xd0, 0xa0, - 0xc0, 0x02, 0x56, 0x3b, 0x26, 0x45, 0xcb, 0xb5, 0x21, 0x89, 0x0b, 0xf5, 0x57, 0x63, 0x05, 0x89, - 0xdd, 0x97, 0x26, 0x69, 0xb9, 0x3c, 0x4c, 0xf6, 0xa8, 0xd4, 0x96, 0x7a, 0x01, 0x12, 0xc8, 0xbe, - 0xf9, 0xa0, 0x85, 0x92, 0x24, 0x2b, 0xa3, 0x2a, 0xa1, 0x63, 0x89, 0x67, 0x90, 0xdb, 0x47, 0xea, - 0x9e, 0x35, 0x9c, 0xac, 0x9c, 0xc1, 0xbe, 0x81, 0x77, 0x00, 0xfb, 0xfd, 0x92, 0xdc, 0xa5, 0x5d, - 0xcf, 0x6d, 0x5e, 0xb8, 0x36, 0xcf, 0x16, 0xd3, 0xdc, 0x8c, 0xd7, 0xfb, 0xfc, 0x35, 0x0b, 0xfc, - 0x3d, 0x75, 0x4b, 0xbe, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe6, 0xdb, 0x71, 0x12, 0x02, - 0x00, 0x00, + // 300 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xcd, 0x4e, 0x84, 0x30, + 0x10, 0x80, 0xc3, 0x2e, 0x3f, 0x4b, 0xf5, 0xa0, 0x63, 0xe2, 0x36, 0x44, 0x13, 0xe2, 0x41, 0x89, + 0x07, 0x36, 0xd1, 0x37, 0xd0, 0xc4, 0xe8, 0xb5, 0x7a, 0xf2, 0x42, 0x2a, 0x29, 0xd2, 0xec, 0x6e, + 0x4b, 0x68, 0xf1, 0x79, 0x7d, 0x14, 0xd3, 0x9f, 0x15, 0xd0, 0x4b, 0x61, 0xe6, 0xfb, 0x3a, 0x33, + 0x1d, 0x94, 0xb5, 0xb4, 0xe3, 0x9b, 0x9e, 0xed, 0x18, 0x55, 0xec, 0xf0, 0x2d, 0xbb, 0x5e, 0x6a, + 0x09, 0xc7, 0x86, 0x95, 0x3e, 0x97, 0xad, 0x67, 0x66, 0x2b, 0xe5, 0xd6, 0x69, 0x7f, 0x00, 0x17, + 0x8d, 0xf4, 0xe0, 0x72, 0x06, 0x34, 0x53, 0xba, 0x52, 0x03, 0xd7, 0x6c, 0x76, 0xaf, 0x6e, 0x69, + 0xaf, 0x37, 0xb5, 0x14, 0x0d, 0xff, 0xf4, 0xe0, 0x7c, 0x0a, 0xcc, 0xe9, 0xf2, 0x57, 0xdf, 0x0b, + 0x94, 0x10, 0x57, 0x0d, 0x00, 0x85, 0x82, 0xee, 0x19, 0x0e, 0xf2, 0xa0, 0x48, 0x89, 0xfd, 0x87, + 0x6b, 0x14, 0x9a, 0xee, 0x78, 0x91, 0x07, 0xc5, 0xd1, 0x1d, 0x94, 0xd3, 0xf1, 0xcb, 0x17, 0xd1, + 0x48, 0x62, 0x39, 0xdc, 0xa0, 0xc8, 0x96, 0xc5, 0x4b, 0x2b, 0x9e, 0x3a, 0xd1, 0x75, 0x7a, 0x34, + 0x27, 0x71, 0x1c, 0x6e, 0x51, 0xec, 0x06, 0xc3, 0xe1, 0xb4, 0xa4, 0x37, 0x2d, 0x21, 0xde, 0x80, + 0x0c, 0xad, 0xf6, 0x54, 0xf0, 0x86, 0x29, 0x8d, 0x23, 0x3b, 0xd4, 0x6f, 0x0c, 0x05, 0x8a, 0xcc, + 0xbe, 0x14, 0x8e, 0xf3, 0xe5, 0xff, 0xc9, 0x9e, 0xa5, 0xdc, 0x12, 0x27, 0x00, 0x46, 0xc9, 0x17, + 0xeb, 0x15, 0x97, 0x02, 0x27, 0x79, 0x50, 0x44, 0xe4, 0x10, 0xc2, 0x05, 0x4a, 0xcd, 0x23, 0x55, + 0x47, 0x6b, 0x86, 0x57, 0xb6, 0xc1, 0x98, 0x80, 0x27, 0x74, 0xb6, 0xa3, 0x4a, 0x57, 0xe3, 0x92, + 0xab, 0x7e, 0x10, 0x38, 0xb5, 0x63, 0xaf, 0xe7, 0xfd, 0xde, 0x98, 0xd2, 0xaf, 0x46, 0x21, 0x27, + 0xe6, 0xce, 0x18, 0x0e, 0xe2, 0x21, 0x7d, 0x4f, 0xbc, 0xf6, 0x11, 0xdb, 0xa5, 0xdf, 0xff, 0x04, + 0x00, 0x00, 0xff, 0xff, 0x85, 0x06, 0x87, 0x2b, 0x22, 0x02, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 883d5a194..2d689ca36 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -995,74 +995,77 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1092 bytes of a gzipped FileDescriptorProto + // 1141 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0xae, 0xf3, 0xe3, 0x24, 0xa7, 0x3f, 0xa4, 0xb3, 0x6d, 0xe3, 0x5a, 0x80, 0x82, 0x11, 0x6c, - 0x58, 0xd8, 0x14, 0xc2, 0x15, 0x12, 0x42, 0xea, 0x66, 0xa3, 0xb4, 0x50, 0xb2, 0x92, 0x43, 0x17, - 0x89, 0x0b, 0x22, 0x37, 0x99, 0x6c, 0xcd, 0x3a, 0x9e, 0xe0, 0x99, 0x94, 0xcd, 0x2d, 0x77, 0xbc, - 0x06, 0x77, 0xf0, 0x30, 0x3c, 0x0b, 0x8f, 0x80, 0x3c, 0x3f, 0xae, 0xed, 0xda, 0x59, 0x93, 0x9b, - 0xd8, 0x33, 0xe7, 0xcc, 0x77, 0xce, 0xf9, 0xe6, 0xfc, 0x38, 0x60, 0xde, 0x3a, 0x4b, 0xf7, 0x8c, - 0xe2, 0xe0, 0xce, 0x9d, 0x62, 0x7a, 0xc6, 0x5c, 0xcf, 0xc3, 0x41, 0x77, 0x19, 0x10, 0x46, 0xd0, - 0x51, 0x28, 0xeb, 0x2a, 0x59, 0x57, 0xc8, 0xcc, 0x13, 0x7e, 0x62, 0x7a, 0xeb, 0x04, 0x4c, 0xfc, - 0x0a, 0x6d, 0xb3, 0x15, 0xdf, 0x27, 0xfe, 0xdc, 0x7d, 0x25, 0x05, 0xc2, 0x44, 0x80, 0x3d, 0xec, - 0x50, 0xac, 0x9e, 0x89, 0x43, 0x4a, 0xe6, 0xfa, 0x73, 0x22, 0x05, 0xa7, 0x09, 0x01, 0x65, 0x0e, - 0x5b, 0xd1, 0x04, 0xde, 0x1d, 0x0e, 0xa8, 0x4b, 0x7c, 0xf5, 0x14, 0x32, 0xeb, 0xcf, 0x12, 0x3c, - 0xba, 0x72, 0x29, 0xb3, 0xc5, 0x41, 0x6a, 0xe3, 0x5f, 0x57, 0x98, 0x32, 0x74, 0x04, 0x55, 0xcf, - 0x5d, 0xb8, 0xcc, 0xd0, 0xda, 0x5a, 0xa7, 0x6c, 0x8b, 0x05, 0x3a, 0x01, 0x9d, 0xcc, 0xe7, 0x14, - 0x33, 0xa3, 0xd4, 0xd6, 0x3a, 0x0d, 0x5b, 0xae, 0xd0, 0x37, 0x50, 0xa3, 0x24, 0x60, 0x93, 0x9b, - 0xb5, 0x51, 0x6e, 0x6b, 0x9d, 0x83, 0xde, 0x47, 0xdd, 0x2c, 0x2a, 0xba, 0xa1, 0xa5, 0x31, 0x09, - 0x58, 0x37, 0xfc, 0x79, 0xb6, 0xb6, 0x75, 0xca, 0x9f, 0x21, 0xee, 0xdc, 0xf5, 0x18, 0x0e, 0x8c, - 0x8a, 0xc0, 0x15, 0x2b, 0x34, 0x04, 0xe0, 0xb8, 0x24, 0x98, 0xe1, 0xc0, 0xa8, 0x72, 0xe8, 0x4e, - 0x01, 0xe8, 0x17, 0xa1, 0xbe, 0xdd, 0xa0, 0xea, 0x15, 0x7d, 0x0d, 0x7b, 0x82, 0x92, 0xc9, 0x94, - 0xcc, 0x30, 0x35, 0xf4, 0x76, 0xb9, 0x73, 0xd0, 0x3b, 0x15, 0x50, 0x8a, 0xe1, 0xb1, 0x20, 0xad, - 0x4f, 0x66, 0xd8, 0xde, 0x15, 0xea, 0xe1, 0x3b, 0xb5, 0x7e, 0x86, 0xba, 0x82, 0xb7, 0x7a, 0xa0, - 0x0b, 0xe7, 0xd1, 0x2e, 0xd4, 0xae, 0x47, 0xdf, 0x8d, 0x5e, 0xfc, 0x38, 0x6a, 0xee, 0xa0, 0x3a, - 0x54, 0x46, 0xe7, 0xdf, 0x0f, 0x9a, 0x1a, 0x3a, 0x84, 0xfd, 0xab, 0xf3, 0xf1, 0x0f, 0x13, 0x7b, - 0x70, 0x35, 0x38, 0x1f, 0x0f, 0x9e, 0x37, 0x4b, 0xd6, 0xfb, 0xd0, 0x88, 0xbc, 0x42, 0x35, 0x28, - 0x9f, 0x8f, 0xfb, 0xe2, 0xc8, 0xf3, 0xc1, 0xb8, 0xdf, 0xd4, 0xac, 0x3f, 0x34, 0x38, 0x4a, 0x5e, - 0x02, 0x5d, 0x12, 0x9f, 0xe2, 0xf0, 0x16, 0xa6, 0x64, 0xe5, 0x47, 0xb7, 0xc0, 0x17, 0x08, 0x41, - 0xc5, 0xc7, 0x6f, 0xd4, 0x1d, 0xf0, 0xf7, 0x50, 0x93, 0x11, 0xe6, 0x78, 0x9c, 0xff, 0xb2, 0x2d, - 0x16, 0xe8, 0x0b, 0xa8, 0xcb, 0xe0, 0xa8, 0x51, 0x69, 0x97, 0x3b, 0xbb, 0xbd, 0xe3, 0x64, 0xc8, - 0xd2, 0xa2, 0x1d, 0xa9, 0x59, 0x43, 0x68, 0x0d, 0xb1, 0xf2, 0x44, 0x30, 0xa2, 0x72, 0x22, 0xb4, - 0xeb, 0x2c, 0x30, 0x77, 0x26, 0xb4, 0xeb, 0x2c, 0x30, 0x32, 0xa0, 0x26, 0x13, 0x8a, 0xbb, 0x53, - 0xb5, 0xd5, 0xd2, 0x62, 0x60, 0x3c, 0x04, 0x92, 0x71, 0x65, 0x21, 0x7d, 0x0c, 0x95, 0x30, 0x9d, - 0x39, 0xcc, 0x6e, 0x0f, 0x25, 0xfd, 0xbc, 0xf4, 0xe7, 0xc4, 0xe6, 0x72, 0xf4, 0x2e, 0x34, 0x42, - 0x7d, 0xba, 0x74, 0xa6, 0x98, 0x47, 0xdb, 0xb0, 0xef, 0x37, 0xac, 0x8b, 0xb8, 0xd5, 0x3e, 0xf1, - 0x19, 0xf6, 0xd9, 0x76, 0xfe, 0x5f, 0xc1, 0x69, 0x06, 0x92, 0x0c, 0xe0, 0x0c, 0x6a, 0xd2, 0x35, - 0x8e, 0x96, 0xcb, 0xab, 0xd2, 0xb2, 0xfe, 0x2e, 0xc1, 0xd1, 0xf5, 0x72, 0xe6, 0x30, 0xac, 0x44, - 0x1b, 0x9c, 0x7a, 0x0c, 0x55, 0xde, 0x16, 0x24, 0x17, 0x87, 0x02, 0x5b, 0xf4, 0x8e, 0x7e, 0xf8, - 0x6b, 0x0b, 0x39, 0x7a, 0x02, 0xfa, 0x9d, 0xe3, 0xad, 0x30, 0xe5, 0x44, 0x44, 0xac, 0x49, 0x4d, - 0xde, 0x53, 0x6c, 0xa9, 0x81, 0x5a, 0x50, 0x9b, 0x05, 0xeb, 0x49, 0xb0, 0xf2, 0x79, 0x91, 0xd5, - 0x6d, 0x7d, 0x16, 0xac, 0xed, 0x95, 0x8f, 0x3e, 0x84, 0xfd, 0x99, 0x4b, 0x9d, 0x1b, 0x0f, 0x4f, - 0x6e, 0x09, 0x79, 0x4d, 0x79, 0x9d, 0xd5, 0xed, 0x3d, 0xb9, 0x79, 0x11, 0xee, 0x21, 0x33, 0xcc, - 0xa4, 0x69, 0x80, 0x1d, 0x86, 0x0d, 0x9d, 0xcb, 0xa3, 0x75, 0xc8, 0x21, 0x73, 0x17, 0x98, 0xac, - 0x98, 0x51, 0xe3, 0xd9, 0xa7, 0x96, 0xe8, 0x03, 0xd8, 0x0b, 0x30, 0xc5, 0x6c, 0x22, 0xbd, 0xac, - 0xf3, 0x93, 0xbb, 0x7c, 0xef, 0xa5, 0x70, 0x0b, 0x41, 0xe5, 0x37, 0xc7, 0x65, 0x46, 0x83, 0x8b, - 0xf8, 0xbb, 0x75, 0x01, 0xc7, 0x29, 0xae, 0xb6, 0xa5, 0xfd, 0x1f, 0x0d, 0x4e, 0x6c, 0xe2, 0x79, - 0x37, 0xce, 0xf4, 0x75, 0x01, 0xe2, 0x63, 0x1c, 0x95, 0x36, 0x73, 0x54, 0xce, 0xe0, 0x28, 0x96, - 0x4b, 0x95, 0x44, 0x2e, 0x25, 0xd8, 0xab, 0xe6, 0xb3, 0xa7, 0x27, 0xd9, 0x53, 0xd4, 0xd4, 0x62, - 0xd4, 0x7c, 0x0b, 0xad, 0x07, 0xf1, 0x6c, 0x4b, 0xce, 0x5f, 0x25, 0x38, 0xbe, 0xf4, 0x29, 0x73, - 0x3c, 0x2f, 0xc5, 0x4d, 0x94, 0x80, 0x5a, 0xe1, 0x04, 0x2c, 0xfd, 0x9f, 0x04, 0x2c, 0x27, 0xc8, - 0x55, 0x37, 0x51, 0x89, 0xdd, 0x44, 0xa1, 0xa4, 0x4c, 0xb4, 0x02, 0x3d, 0xd5, 0x0a, 0xd0, 0x7b, - 0x00, 0x01, 0x5e, 0x51, 0x3c, 0xe1, 0xe0, 0x82, 0xc4, 0x06, 0xdf, 0x19, 0xc9, 0xca, 0x57, 0xbc, - 0xd7, 0xb3, 0x79, 0x8f, 0xa7, 0xe4, 0x25, 0x9c, 0xa4, 0xa9, 0xda, 0x96, 0xf6, 0xdf, 0x35, 0x68, - 0x5d, 0xfb, 0x6e, 0x26, 0xf1, 0x59, 0x49, 0xf9, 0x80, 0x8a, 0x52, 0x06, 0x15, 0x47, 0x50, 0x5d, - 0xae, 0x82, 0x57, 0x58, 0x52, 0x2b, 0x16, 0xf1, 0x18, 0x2b, 0x89, 0x18, 0xad, 0x09, 0x18, 0x0f, - 0x7d, 0xd8, 0x32, 0xa2, 0xd0, 0xeb, 0xa8, 0x75, 0x37, 0x44, 0x9b, 0xb6, 0x1e, 0xc1, 0xe1, 0x10, - 0xb3, 0x97, 0xa2, 0x00, 0x64, 0x78, 0xd6, 0x00, 0x50, 0x7c, 0xf3, 0xde, 0x9e, 0xdc, 0x4a, 0xda, - 0x53, 0x5f, 0x2a, 0x4a, 0x5f, 0x69, 0x59, 0x5f, 0x71, 0xec, 0x0b, 0x97, 0x32, 0x12, 0xac, 0x37, - 0x51, 0xd7, 0x84, 0xf2, 0xc2, 0x79, 0x23, 0x3b, 0x7b, 0xf8, 0x6a, 0x0d, 0xb9, 0x07, 0xd1, 0x51, - 0xe9, 0x41, 0x7c, 0x4e, 0x6a, 0x85, 0xe6, 0x64, 0xef, 0xdf, 0x1a, 0x1c, 0xa8, 0xe1, 0x26, 0x3e, - 0x45, 0x90, 0x0b, 0x7b, 0xf1, 0x29, 0x8e, 0x3e, 0xc9, 0xff, 0x52, 0x49, 0x7d, 0x6e, 0x99, 0x4f, - 0x8a, 0xa8, 0x0a, 0x67, 0xad, 0x9d, 0xcf, 0x35, 0x44, 0xa1, 0x99, 0x1e, 0xae, 0xe8, 0x69, 0x36, - 0x46, 0xce, 0x34, 0x37, 0xbb, 0x45, 0xd5, 0x95, 0x59, 0x74, 0xc7, 0x69, 0x4f, 0x4e, 0x44, 0xf4, - 0x56, 0x98, 0xe4, 0x10, 0x36, 0xcf, 0x0a, 0xeb, 0x47, 0x76, 0x7f, 0x81, 0xfd, 0xc4, 0x38, 0x40, - 0x39, 0x6c, 0x65, 0xcd, 0x57, 0xf3, 0xd3, 0x42, 0xba, 0x91, 0xad, 0x05, 0x1c, 0x24, 0xeb, 0x1c, - 0xe5, 0x00, 0x64, 0x36, 0x4e, 0xf3, 0xb3, 0x62, 0xca, 0x91, 0x39, 0x0a, 0xcd, 0x74, 0x19, 0xe6, - 0xdd, 0x63, 0x4e, 0xcb, 0xc8, 0xbb, 0xc7, 0xbc, 0xea, 0xb6, 0x76, 0x90, 0x03, 0x70, 0x5f, 0x85, - 0xe8, 0x71, 0xee, 0x85, 0x24, 0x8b, 0xd7, 0xec, 0xbc, 0x5d, 0x31, 0x32, 0xb1, 0x84, 0x77, 0x52, - 0x63, 0x0a, 0xe5, 0x50, 0x93, 0x3d, 0x9d, 0xcd, 0xa7, 0x05, 0xb5, 0x53, 0x41, 0xc9, 0xc2, 0xde, - 0x10, 0x54, 0xb2, 0x6b, 0x6c, 0x08, 0x2a, 0xd5, 0x23, 0xac, 0x9d, 0x67, 0xf0, 0x53, 0x5d, 0xe9, - 0xdd, 0xe8, 0xfc, 0xef, 0xd3, 0x97, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x3d, 0xae, 0x84, - 0x0f, 0x0e, 0x00, 0x00, + 0x14, 0xae, 0xf3, 0x9f, 0xd3, 0x1f, 0xd2, 0xe9, 0x9f, 0x6b, 0x01, 0x2a, 0x46, 0xd0, 0xec, 0xc2, + 0xa6, 0x10, 0xae, 0x90, 0x10, 0x52, 0xdb, 0x8d, 0xda, 0x42, 0xe9, 0x4a, 0xce, 0x76, 0x91, 0xb8, + 0x20, 0x72, 0x93, 0x49, 0x6b, 0xd6, 0xf1, 0x04, 0xcf, 0xa4, 0x6c, 0x6f, 0xb9, 0xe3, 0x35, 0xb8, + 0x83, 0x87, 0xe1, 0x05, 0x78, 0x19, 0x34, 0x7f, 0xae, 0x27, 0xb5, 0x5b, 0x93, 0x9b, 0x78, 0x66, + 0xce, 0x99, 0xef, 0x9c, 0xf3, 0x9d, 0x33, 0x67, 0x26, 0xe0, 0xdc, 0xf8, 0xd3, 0xe0, 0x80, 0xe2, + 0xf8, 0x36, 0x18, 0x62, 0x7a, 0xc0, 0x82, 0x30, 0xc4, 0x71, 0x67, 0x1a, 0x13, 0x46, 0xd0, 0x26, + 0x97, 0x75, 0xb4, 0xac, 0x23, 0x65, 0xce, 0xb6, 0xd8, 0x31, 0xbc, 0xf1, 0x63, 0x26, 0x7f, 0xa5, + 0xb6, 0xb3, 0x93, 0x5e, 0x27, 0xd1, 0x38, 0xb8, 0x56, 0x02, 0x69, 0x22, 0xc6, 0x21, 0xf6, 0x29, + 0xd6, 0x5f, 0x63, 0x93, 0x96, 0x05, 0xd1, 0x98, 0x28, 0xc1, 0xae, 0x21, 0xa0, 0xcc, 0x67, 0x33, + 0x6a, 0xe0, 0xdd, 0xe2, 0x98, 0x06, 0x24, 0xd2, 0x5f, 0x29, 0x73, 0xff, 0x2c, 0xc1, 0xc6, 0x79, + 0x40, 0x99, 0x27, 0x37, 0x52, 0x0f, 0xff, 0x3a, 0xc3, 0x94, 0xa1, 0x4d, 0xa8, 0x86, 0xc1, 0x24, + 0x60, 0xb6, 0xb5, 0x67, 0xb5, 0xcb, 0x9e, 0x9c, 0xa0, 0x6d, 0xa8, 0x91, 0xf1, 0x98, 0x62, 0x66, + 0x97, 0xf6, 0xac, 0x76, 0xd3, 0x53, 0x33, 0xf4, 0x2d, 0xd4, 0x29, 0x89, 0xd9, 0xe0, 0xea, 0xce, + 0x2e, 0xef, 0x59, 0xed, 0xb5, 0xee, 0x27, 0x9d, 0x2c, 0x2a, 0x3a, 0xdc, 0x52, 0x9f, 0xc4, 0xac, + 0xc3, 0x7f, 0x8e, 0xee, 0xbc, 0x1a, 0x15, 0x5f, 0x8e, 0x3b, 0x0e, 0x42, 0x86, 0x63, 0xbb, 0x22, + 0x71, 0xe5, 0x0c, 0x9d, 0x00, 0x08, 0x5c, 0x12, 0x8f, 0x70, 0x6c, 0x57, 0x05, 0x74, 0xbb, 0x00, + 0xf4, 0x2b, 0xae, 0xef, 0x35, 0xa9, 0x1e, 0xa2, 0x6f, 0x60, 0x45, 0x52, 0x32, 0x18, 0x92, 0x11, + 0xa6, 0x76, 0x6d, 0xaf, 0xdc, 0x5e, 0xeb, 0xee, 0x4a, 0x28, 0xcd, 0x70, 0x5f, 0x92, 0x76, 0x4c, + 0x46, 0xd8, 0x5b, 0x96, 0xea, 0x7c, 0x4c, 0xdd, 0x9f, 0xa1, 0xa1, 0xe1, 0xdd, 0x2e, 0xd4, 0xa4, + 0xf3, 0x68, 0x19, 0xea, 0x97, 0x17, 0xdf, 0x5f, 0xbc, 0xfa, 0xf1, 0xa2, 0xb5, 0x84, 0x1a, 0x50, + 0xb9, 0x38, 0xfc, 0xa1, 0xd7, 0xb2, 0xd0, 0x3a, 0xac, 0x9e, 0x1f, 0xf6, 0x5f, 0x0f, 0xbc, 0xde, + 0x79, 0xef, 0xb0, 0xdf, 0x7b, 0xd9, 0x2a, 0xb9, 0x1f, 0x42, 0x33, 0xf1, 0x0a, 0xd5, 0xa1, 0x7c, + 0xd8, 0x3f, 0x96, 0x5b, 0x5e, 0xf6, 0xfa, 0xc7, 0x2d, 0xcb, 0xfd, 0xc3, 0x82, 0x4d, 0x33, 0x09, + 0x74, 0x4a, 0x22, 0x8a, 0x79, 0x16, 0x86, 0x64, 0x16, 0x25, 0x59, 0x10, 0x13, 0x84, 0xa0, 0x12, + 0xe1, 0x77, 0x3a, 0x07, 0x62, 0xcc, 0x35, 0x19, 0x61, 0x7e, 0x28, 0xf8, 0x2f, 0x7b, 0x72, 0x82, + 0xbe, 0x84, 0x86, 0x0a, 0x8e, 0xda, 0x95, 0xbd, 0x72, 0x7b, 0xb9, 0xbb, 0x65, 0x86, 0xac, 0x2c, + 0x7a, 0x89, 0x9a, 0x7b, 0x02, 0x3b, 0x27, 0x58, 0x7b, 0x22, 0x19, 0xd1, 0x35, 0xc1, 0xed, 0xfa, + 0x13, 0x2c, 0x9c, 0xe1, 0x76, 0xfd, 0x09, 0x46, 0x36, 0xd4, 0x55, 0x41, 0x09, 0x77, 0xaa, 0x9e, + 0x9e, 0xba, 0x0c, 0xec, 0x87, 0x40, 0x2a, 0xae, 0x2c, 0xa4, 0x4f, 0xa1, 0xc2, 0xcb, 0x59, 0xc0, + 0x2c, 0x77, 0x91, 0xe9, 0xe7, 0x59, 0x34, 0x26, 0x9e, 0x90, 0xa3, 0xf7, 0xa1, 0xc9, 0xf5, 0xe9, + 0xd4, 0x1f, 0x62, 0x11, 0x6d, 0xd3, 0xbb, 0x5f, 0x70, 0x4f, 0xd3, 0x56, 0x8f, 0x49, 0xc4, 0x70, + 0xc4, 0x16, 0xf3, 0xff, 0x1c, 0x76, 0x33, 0x90, 0x54, 0x00, 0x07, 0x50, 0x57, 0xae, 0x09, 0xb4, + 0x5c, 0x5e, 0xb5, 0x96, 0xfb, 0x77, 0x09, 0x36, 0x2f, 0xa7, 0x23, 0x9f, 0x61, 0x2d, 0x7a, 0xc4, + 0xa9, 0x7d, 0xa8, 0x8a, 0xb6, 0xa0, 0xb8, 0x58, 0x97, 0xd8, 0xb2, 0x77, 0x1c, 0xf3, 0x5f, 0x4f, + 0xca, 0xd1, 0x73, 0xa8, 0xdd, 0xfa, 0xe1, 0x0c, 0x53, 0x41, 0x44, 0xc2, 0x9a, 0xd2, 0x14, 0x3d, + 0xc5, 0x53, 0x1a, 0x68, 0x07, 0xea, 0xa3, 0xf8, 0x6e, 0x10, 0xcf, 0x22, 0x71, 0xc8, 0x1a, 0x5e, + 0x6d, 0x14, 0xdf, 0x79, 0xb3, 0x08, 0x7d, 0x0c, 0xab, 0xa3, 0x80, 0xfa, 0x57, 0x21, 0x1e, 0xdc, + 0x10, 0xf2, 0x96, 0x8a, 0x73, 0xd6, 0xf0, 0x56, 0xd4, 0xe2, 0x29, 0x5f, 0x43, 0x0e, 0xaf, 0xa4, + 0x61, 0x8c, 0x7d, 0x86, 0xed, 0x9a, 0x90, 0x27, 0x73, 0xce, 0x21, 0x0b, 0x26, 0x98, 0xcc, 0x98, + 0x5d, 0x17, 0xd5, 0xa7, 0xa7, 0xe8, 0x23, 0x58, 0x89, 0x31, 0xc5, 0x6c, 0xa0, 0xbc, 0x6c, 0x88, + 0x9d, 0xcb, 0x62, 0xed, 0x8d, 0x74, 0x0b, 0x41, 0xe5, 0x37, 0x3f, 0x60, 0x76, 0x53, 0x88, 0xc4, + 0xd8, 0x3d, 0x85, 0xad, 0x39, 0xae, 0x16, 0xa5, 0xfd, 0x1f, 0x0b, 0xb6, 0x3d, 0x12, 0x86, 0x57, + 0xfe, 0xf0, 0x6d, 0x01, 0xe2, 0x53, 0x1c, 0x95, 0x1e, 0xe7, 0xa8, 0x9c, 0xc1, 0x51, 0xaa, 0x96, + 0x2a, 0x46, 0x2d, 0x19, 0xec, 0x55, 0xf3, 0xd9, 0xab, 0x99, 0xec, 0x69, 0x6a, 0xea, 0x29, 0x6a, + 0xbe, 0x83, 0x9d, 0x07, 0xf1, 0x2c, 0x4a, 0xce, 0x5f, 0x25, 0xd8, 0x3a, 0x8b, 0x28, 0xf3, 0xc3, + 0x70, 0x8e, 0x9b, 0xa4, 0x00, 0xad, 0xc2, 0x05, 0x58, 0xfa, 0x3f, 0x05, 0x58, 0x36, 0xc8, 0xd5, + 0x99, 0xa8, 0xa4, 0x32, 0x51, 0xa8, 0x28, 0x8d, 0x56, 0x50, 0x9b, 0x6b, 0x05, 0xe8, 0x03, 0x80, + 0x18, 0xcf, 0x28, 0x1e, 0x08, 0x70, 0x49, 0x62, 0x53, 0xac, 0x5c, 0xa8, 0x93, 0xaf, 0x79, 0x6f, + 0x64, 0xf3, 0x9e, 0x2e, 0xc9, 0x33, 0xd8, 0x9e, 0xa7, 0x6a, 0x51, 0xda, 0x7f, 0xb7, 0x60, 0xe7, + 0x32, 0x0a, 0x32, 0x89, 0xcf, 0x2a, 0xca, 0x07, 0x54, 0x94, 0x32, 0xa8, 0xd8, 0x84, 0xea, 0x74, + 0x16, 0x5f, 0x63, 0x45, 0xad, 0x9c, 0xa4, 0x63, 0xac, 0x18, 0x31, 0xba, 0x03, 0xb0, 0x1f, 0xfa, + 0xb0, 0x60, 0x44, 0xdc, 0xeb, 0xa4, 0x75, 0x37, 0x65, 0x9b, 0x76, 0x37, 0x60, 0xfd, 0x04, 0xb3, + 0x37, 0xf2, 0x00, 0xa8, 0xf0, 0xdc, 0x1e, 0xa0, 0xf4, 0xe2, 0xbd, 0x3d, 0xb5, 0x64, 0xda, 0xd3, + 0x2f, 0x15, 0xad, 0x9f, 0xb4, 0xe6, 0xaf, 0x05, 0xf6, 0x69, 0x40, 0x19, 0x89, 0xef, 0x1e, 0xa3, + 0xae, 0x05, 0xe5, 0x89, 0xff, 0x4e, 0x75, 0x76, 0x3e, 0x74, 0x4f, 0x84, 0x07, 0xc9, 0x56, 0xe5, + 0x41, 0xfa, 0x9e, 0xb4, 0x8a, 0xdd, 0x93, 0x47, 0x80, 0x5e, 0xe3, 0xe4, 0xca, 0x7e, 0xe2, 0x8a, + 0xd1, 0x49, 0x28, 0x99, 0x49, 0xd8, 0x87, 0x0d, 0x03, 0x43, 0x79, 0xc3, 0xbd, 0xa6, 0xd7, 0x0a, + 0x83, 0x0f, 0xbb, 0xff, 0x36, 0x60, 0x4d, 0xdf, 0xa4, 0xf2, 0xdd, 0x83, 0x02, 0x58, 0x49, 0x3f, + 0x19, 0xd0, 0xb3, 0xfc, 0x67, 0xd1, 0xdc, 0xdb, 0xce, 0x79, 0x5e, 0x44, 0x55, 0xfa, 0xe2, 0x2e, + 0x7d, 0x61, 0x21, 0x0a, 0xad, 0xf9, 0x9b, 0x1c, 0xbd, 0xc8, 0xc6, 0xc8, 0x79, 0x3a, 0x38, 0x9d, + 0xa2, 0xea, 0xda, 0x2c, 0xba, 0x15, 0x39, 0x36, 0xaf, 0x5f, 0xf4, 0x24, 0x8c, 0x79, 0xe3, 0x3b, + 0x07, 0x85, 0xf5, 0x13, 0xbb, 0xbf, 0xc0, 0xaa, 0x71, 0xf7, 0xa0, 0x1c, 0xb6, 0xb2, 0x2e, 0x73, + 0xe7, 0xb3, 0x42, 0xba, 0x89, 0xad, 0x09, 0xac, 0x99, 0x4d, 0x05, 0xe5, 0x00, 0x64, 0x76, 0x69, + 0xe7, 0xf3, 0x62, 0xca, 0x89, 0x39, 0x0a, 0xad, 0xf9, 0x33, 0x9f, 0x97, 0xc7, 0x9c, 0xfe, 0x94, + 0x97, 0xc7, 0xbc, 0x56, 0xe2, 0x2e, 0x21, 0x1f, 0xe0, 0xfe, 0xc8, 0xa3, 0xfd, 0xdc, 0x84, 0x98, + 0x9d, 0xc2, 0x69, 0x3f, 0xad, 0x98, 0x98, 0x98, 0xc2, 0x7b, 0x73, 0x77, 0x22, 0xca, 0xa1, 0x26, + 0xfb, 0x29, 0xe0, 0xbc, 0x28, 0xa8, 0x3d, 0x17, 0x94, 0xea, 0x22, 0x8f, 0x04, 0x65, 0xb6, 0xa8, + 0x47, 0x82, 0x9a, 0x6b, 0x48, 0xee, 0x12, 0x0a, 0x60, 0xcd, 0x9b, 0x45, 0xca, 0x34, 0xef, 0x12, + 0x28, 0x67, 0xf7, 0xc3, 0x2e, 0xe4, 0x3c, 0x2b, 0xa0, 0x79, 0x7f, 0xbe, 0x8f, 0xe0, 0xa7, 0x86, + 0x56, 0xbd, 0xaa, 0x89, 0xbf, 0x85, 0x5f, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0xe7, 0xd5, 0x3c, + 0xdf, 0xe7, 0x0e, 0x00, 0x00, } diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go new file mode 100644 index 000000000..0ead90cf1 --- /dev/null +++ b/pkg/releasetesting/environment.go @@ -0,0 +1,81 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package releasetesting + +import ( + "fmt" + + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/tiller/environment" +) + +type Environment struct { + Namespace string + KubeClient environment.KubeClient + Stream services.ReleaseService_RunReleaseTestServer + Timeout int64 +} + +func streamRunning(name string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := "RUNNING: " + name + if err := streamMessage(msg, stream); err != nil { + return err + } + return nil +} + +func streamError(info string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := "ERROR: " + info + 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 streamUnknown(name, info string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info) + 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 +} diff --git a/pkg/releasetesting/release_testing.go b/pkg/releasetesting/release_testing.go new file mode 100644 index 000000000..3ec553a17 --- /dev/null +++ b/pkg/releasetesting/release_testing.go @@ -0,0 +1,195 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package releasetesting + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/timestamp" + "k8s.io/kubernetes/pkg/api" + + "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +type TestSuite struct { + StartedAt *timestamp.Timestamp + CompletedAt *timestamp.Timestamp + TestManifests []string + Results []*release.TestRun +} + +type test struct { + manifest string + result *release.TestRun +} + +func NewTestSuite(rel *release.Release, env *Environment) (*TestSuite, error) { + testManifests, err := prepareTestManifests(rel.Hooks, rel.Name) + if err != nil { + return nil, err + } + + results := []*release.TestRun{} + + return &TestSuite{ + TestManifests: testManifests, + Results: results, + }, nil +} + +func newTest(testManifest string) (*test, error) { + var sh util.SimpleHead + err := yaml.Unmarshal([]byte(testManifest), &sh) + if err != nil { + return nil, err + } + + if sh.Kind != "Pod" { + return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name) + } + + return &test{ + manifest: testManifest, + result: &release.TestRun{ + Name: sh.Metadata.Name, + }, + }, nil +} + +func (t *TestSuite) createTestPod(test *test, env *Environment) error { + b := bytes.NewBufferString(test.manifest) + if err := env.KubeClient.Create(env.Namespace, b); err != nil { + log.Printf(err.Error()) + test.result.Info = err.Error() + test.result.Status = release.TestRun_FAILURE + return err + } + + return nil +} + +func (t *TestSuite) getPodExitStatus(test *test, env *Environment) (api.PodPhase, error) { + b := bytes.NewBufferString(test.manifest) + status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second) + if err != nil { + log.Printf("Error getting status for pod %s: %s", test.result.Name, err) + test.result.Info = err.Error() + test.result.Status = release.TestRun_UNKNOWN + return status, err + } + + return status, err +} + +func (t *TestSuite) Run(env *Environment) error { + t.StartedAt = timeconv.Now() + + for _, testManifest := range t.TestManifests { + test, err := newTest(testManifest) + if err != nil { + return err + } + + test.result.StartedAt = timeconv.Now() + if err := streamRunning(test.result.Name, env.Stream); err != nil { + return err + } + + resourceCreated := true + if err := t.createTestPod(test, env); err != nil { + resourceCreated = false + if streamErr := streamError(test.result.Info, env.Stream); streamErr != nil { + return err + } + } + + resourceCleanExit := true + status := api.PodUnknown + if resourceCreated { + status, err = t.getPodExitStatus(test, env) + if err != nil { + resourceCleanExit = false + if streamErr := streamUnknown(test.result.Name, test.result.Info, env.Stream); streamErr != nil { + return streamErr + } + } + } + + if resourceCreated && resourceCleanExit && status == api.PodSucceeded { + test.result.Status = release.TestRun_SUCCESS + if streamErr := streamSuccess(test.result.Name, env.Stream); streamErr != nil { + return streamErr + } + } else if resourceCreated && resourceCleanExit && status == api.PodFailed { + test.result.Status = release.TestRun_FAILURE + if streamErr := streamFailed(test.result.Name, env.Stream); streamErr != nil { + return err + } + } //else if resourceCreated && resourceCleanExit && status == api.PodUnkown { + + _ = append(t.Results, test.result) + } + + t.CompletedAt = timeconv.Now() + return nil +} + +func filterTestHooks(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) { + testHooks := []*release.Hook{} + notFoundErr := fmt.Errorf("no tests found for release %s", releaseName) + + if len(hooks) == 0 { + return nil, notFoundErr + } + + for _, h := range hooks { + for _, e := range h.Events { + if e == release.Hook_RELEASE_TEST { + testHooks = append(testHooks, h) + continue + } + } + } + + if len(testHooks) == 0 { + return nil, notFoundErr + } + + return testHooks, nil +} + +func prepareTestManifests(hooks []*release.Hook, releaseName string) ([]string, error) { + testHooks, err := filterTestHooks(hooks, releaseName) + if err != nil { + return nil, err + } + + tests := []string{} + for _, h := range testHooks { + individualTests := util.SplitManifests(h.Manifest) + for _, t := range individualTests { + tests = append(tests, t) + } + } + return tests, nil +} diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go new file mode 100644 index 000000000..d79ea5a77 --- /dev/null +++ b/pkg/releaseutil/manifest.go @@ -0,0 +1,46 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package releaseutil + +import ( + "fmt" + "strings" +) + +type SimpleHead struct { + Version string `json:"apiVersion"` + Kind string `json:"kind,omitempty"` + Metadata *struct { + Name string `json:"name"` + Annotations map[string]string `json:"annotations"` + } `json:"metadata,omitempty"` +} + +func SplitManifests(bigfile string) map[string]string { + // This is not the best way of doing things, but it's how k8s itself does it. + // Basically, we're quickly splitting a stream of YAML documents into an + // array of YAML docs. In the current implementation, the file name is just + // a place holder, and doesn't have any further meaning. + sep := "\n---\n" + tpl := "manifest-%d" + res := map[string]string{} + tmp := strings.Split(bigfile, sep) + for i, d := range tmp { + res[fmt.Sprintf(tpl, i)] = d + } + return res +} diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 7e0857897..225da94df 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -26,6 +26,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" ) // hookAnno is the label name for a hook @@ -55,20 +56,11 @@ var events = map[string]release.Hook_Event{ releaseTest: release.Hook_RELEASE_TEST, } -type simpleHead struct { - Version string `json:"apiVersion"` - Kind string `json:"kind,omitempty"` - Metadata *struct { - Name string `json:"name"` - Annotations map[string]string `json:"annotations"` - } `json:"metadata,omitempty"` -} - // manifest represents a manifest file, which has a name and some content. type manifest struct { name string content string - head *simpleHead + head *util.SimpleHead } // sortManifests takes a map of filename/YAML contents and sorts them into hook types. @@ -108,7 +100,7 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort continue } - var sh simpleHead + var sh util.SimpleHead err := yaml.Unmarshal([]byte(c), &sh) if err != nil { diff --git a/pkg/tiller/hooks_test.go b/pkg/tiller/hooks_test.go index b9bfed71a..8c83c1468 100644 --- a/pkg/tiller/hooks_test.go +++ b/pkg/tiller/hooks_test.go @@ -22,6 +22,7 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" ) @@ -162,7 +163,7 @@ metadata: // Verify the sort order sorted := make([]manifest, len(data)) for i, s := range data { - var sh simpleHead + var sh util.SimpleHead err := yaml.Unmarshal([]byte(s.manifest), &sh) if err != nil { // This is expected for manifests that are corrupt or empty. diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index 7b7e5f679..568daf300 100644 --- a/pkg/tiller/kind_sorter_test.go +++ b/pkg/tiller/kind_sorter_test.go @@ -18,6 +18,8 @@ package tiller import ( "testing" + + "k8s.io/helm/pkg/hooks" ) func TestKindSorter(t *testing.T) { @@ -25,27 +27,27 @@ func TestKindSorter(t *testing.T) { { name: "m", content: "", - head: &simpleHead{Kind: "Deployment"}, + head: &util.SimpleHead{Kind: "Deployment"}, }, { name: "l", content: "", - head: &simpleHead{Kind: "Service"}, + head: &util.SimpleHead{Kind: "Service"}, }, { name: "!", content: "", - head: &simpleHead{Kind: "HonkyTonkSet"}, + head: &util.SimpleHead{Kind: "HonkyTonkSet"}, }, { name: "h", content: "", - head: &simpleHead{Kind: "Namespace"}, + head: &util.SimpleHead{Kind: "Namespace"}, }, { name: "e", content: "", - head: &simpleHead{Kind: "ConfigMap"}, + head: &util.SimpleHead{Kind: "ConfigMap"}, }, } diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 28d127ac0..33ce48c97 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -36,6 +36,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" + reltesting "k8s.io/helm/pkg/releasetesting" relutil "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/tiller/environment" @@ -986,7 +987,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR log.Printf("uninstall: Failed to store updated release: %s", err) } - manifests := splitManifests(rel.Manifest) + manifests := relutil.SplitManifests(rel.Manifest) _, files, err := sortManifests(manifests, vs, UninstallOrder) if err != nil { // We could instead just delete everything in no particular order. @@ -1044,21 +1045,6 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR return res, errs } -func splitManifests(bigfile string) map[string]string { - // This is not the best way of doing things, but it's how k8s itself does it. - // Basically, we're quickly splitting a stream of YAML documents into an - // array of YAML docs. In the current implementation, the file name is just - // a place holder, and doesn't have any further meaning. - sep := "\n---\n" - tpl := "manifest-%d" - res := map[string]string{} - tmp := strings.Split(bigfile, sep) - for i, d := range tmp { - res[fmt.Sprintf(tpl, i)] = d - } - return res -} - func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { r := bytes.NewReader(manifest) _, err := c.Build(ns, r) @@ -1078,15 +1064,23 @@ func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream return err } - tests, err := prepareTests(rel.Hooks, rel.Name) - kubeCli := s.env.KubeClient + testEnv := &reltesting.Environment{ + Namespace: rel.Namespace, + KubeClient: s.env.KubeClient, + Timeout: req.Timeout, + Stream: stream, + } - testSuite, err := runReleaseTests(tests, rel, kubeCli, stream, req.Timeout) - if err != nil { + tSuite, err := reltesting.NewTestSuite(rel, testEnv) + if err := tSuite.Run(testEnv); err != nil { return err } - rel.TestSuite = testSuite + rel.LastTestSuiteRun = &release.TestSuite{ + StartedAt: tSuite.StartedAt, + CompletedAt: tSuite.CompletedAt, + Results: tSuite.Results, + } return nil } diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go deleted file mode 100644 index ff601c697..000000000 --- a/pkg/tiller/release_testing.go +++ /dev/null @@ -1,203 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package tiller - -import ( - "bytes" - "fmt" - "log" - "time" - - "github.com/ghodss/yaml" - "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" -) - -//TODO: testSuiteRunner.Run() -//struct testSuiteRunner { -//suite *release.TestSuite, -//tests []string, -//kube environemtn.KubeClient, -//timeout int64 -////stream or output channel -//} - -func runReleaseTests(tests []string, rel *release.Release, kube environment.KubeClient, stream services.ReleaseService_RunReleaseTestServer, timeout int64) (*release.TestSuite, error) { - results := []*release.TestRun{} - - //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 { - return nil, err - } - - 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(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 - resourceCleanExit := true - if resourceCreated { - b.Reset() - b.WriteString(h) - status, err = kube.WaitAndGetCompletedPodPhase(rel.Namespace, b, time.Duration(timeout)*time.Second) - if err != nil { - 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 = release.TestRun_SUCCESS - if streamErr := streamSuccess(ts.Name, stream); streamErr != nil { - return nil, streamErr - } - } else if resourceCreated && resourceCleanExit && status == api.PodFailed { - ts.Status = release.TestRun_FAILURE - if streamErr := streamFailed(ts.Name, stream); streamErr != nil { - return nil, err - } - } - - results = append(results, ts) - log.Printf("Test %s completed", ts.Name) - - //TODO: recordTests() - add test results to configmap with standardized name - } - - suite.Results = results - //TODO: delete flag - log.Printf("Finished running test suite for %s", rel.Name) - - return suite, nil -} - -func filterTests(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) { - testHooks := []*release.Hook{} - notFoundErr := fmt.Errorf("no tests found for release %s", releaseName) - if len(hooks) == 0 { - return nil, notFoundErr - } - - code, ok := events[releaseTest] - if !ok { - return nil, fmt.Errorf("unknown hook %q", releaseTest) - } - - found := false - for _, h := range hooks { - for _, e := range h.Events { - if e == code { - found = true - testHooks = append(testHooks, h) - continue - } - } - } - - //TODO: probably don't need to check found - if !found && len(testHooks) == 0 { - return nil, notFoundErr - } - - return testHooks, nil -} - -func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) { - testHooks, err := filterTests(hooks, releaseName) - if err != nil { - return nil, err - } - - tests := []string{} - for _, h := range testHooks { - individualTests := splitManifests(h.Manifest) - for _, t := range individualTests { - tests = append(tests, t) - } - } - 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 -} From e95a0570adf092d77b46c78e7b655409ba2fa130 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 1 Feb 2017 10:54:53 -0500 Subject: [PATCH 5/8] chore(pkg/releasetesting): add test_suite tests * and comments --- cmd/helm/helm_test.go | 2 +- cmd/helm/release_testing.go | 2 - pkg/helm/client.go | 2 +- pkg/helm/option.go | 2 +- pkg/kube/client.go | 2 - pkg/releasetesting/environment.go | 39 +-- .../{release_testing.go => test_suite.go} | 111 ++++---- pkg/releasetesting/test_suite_test.go | 247 ++++++++++++++++++ pkg/releaseutil/manifest.go | 2 + pkg/tiller/hooks_test.go | 2 +- pkg/tiller/kind_sorter_test.go | 2 +- pkg/tiller/release_server.go | 8 +- pkg/tiller/release_server_test.go | 4 +- 13 files changed, 335 insertions(+), 90 deletions(-) rename pkg/releasetesting/{release_testing.go => test_suite.go} (78%) create mode 100644 pkg/releasetesting/test_suite_test.go diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 758acd12d..138c4dac0 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -190,7 +190,7 @@ func (c *fakeReleaseClient) ReleaseHistory(rlsName string, opts ...helm.HistoryO return &rls.GetHistoryResponse{Releases: c.rels}, c.err } -func (c *fakeReleaseClient) ReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (*rls.TestReleaseResponse, error) { +func (c *fakeReleaseClient) RunReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { return nil, nil } diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index e4c8e2c44..13af3641d 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -81,6 +81,4 @@ func (t *releaseTestCmd) run() (err error) { fmt.Fprintf(t.out, res.Msg+"\n") } } - - return nil } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 771974bb7..c8ade3467 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -246,7 +246,7 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get return h.history(ctx, req) } -//ReleaseTest executes a pre-defined test on a release +// RunReleaseTest 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) diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 45f6b23ee..818f31a1c 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -176,7 +176,7 @@ func DeleteTimeout(timeout int64) DeleteOption { } } -// TestTimeout specifies the number of seconds before kubernetes calls timeout +// ReleaseTestTimeout specifies the number of seconds before kubernetes calls timeout func ReleaseTestTimeout(timeout int64) ReleaseTestOption { return func(opts *options) { opts.testReq.Timeout = timeout diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 48f9975e6..850dfe1a9 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -622,8 +622,6 @@ func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, } info := infos[0] - // TODO: should we be checking kind beforehand? probably yes. - // TODO: add validation to linter: any manifest with a test hook has to be a pod kind? kind := info.Mapping.GroupVersionKind.Kind if kind != "Pod" { return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index 0ead90cf1..565d4ab0e 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -23,6 +23,7 @@ import ( "k8s.io/helm/pkg/tiller/environment" ) +// Environment encapsulates information about where test suite executes and returns results type Environment struct { Namespace string KubeClient environment.KubeClient @@ -32,50 +33,36 @@ type Environment struct { func streamRunning(name string, stream services.ReleaseService_RunReleaseTestServer) error { msg := "RUNNING: " + name - if err := streamMessage(msg, stream); err != nil { - return err - } - return nil + err := streamMessage(msg, stream) + return err } func streamError(info string, stream services.ReleaseService_RunReleaseTestServer) error { msg := "ERROR: " + info - if err := streamMessage(msg, stream); err != nil { - return err - } - return nil + err := streamMessage(msg, stream) + return err } 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 + err := streamMessage(msg, stream) + return err } 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 + err := streamMessage(msg, stream) + return err } func streamUnknown(name, info string, stream services.ReleaseService_RunReleaseTestServer) error { msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info) - if err := streamMessage(msg, stream); err != nil { - return err - } - return nil + err := streamMessage(msg, stream) + return err } 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 + err := stream.Send(resp) + return err } diff --git a/pkg/releasetesting/release_testing.go b/pkg/releasetesting/test_suite.go similarity index 78% rename from pkg/releasetesting/release_testing.go rename to pkg/releasetesting/test_suite.go index 3ec553a17..bd43655eb 100644 --- a/pkg/releasetesting/release_testing.go +++ b/pkg/releasetesting/test_suite.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "log" + "strings" "time" "github.com/ghodss/yaml" @@ -31,6 +32,7 @@ import ( "k8s.io/helm/pkg/timeconv" ) +// TestSuite what tests are run, results, and metadata type TestSuite struct { StartedAt *timestamp.Timestamp CompletedAt *timestamp.Timestamp @@ -43,8 +45,10 @@ type test struct { result *release.TestRun } -func NewTestSuite(rel *release.Release, env *Environment) (*TestSuite, error) { - testManifests, err := prepareTestManifests(rel.Hooks, rel.Name) +// NewTestSuite takes a release object and returns a TestSuite object with test definitions +// extracted from the release +func NewTestSuite(rel *release.Release) (*TestSuite, error) { + testManifests, err := extractTestManifestsFromHooks(rel.Hooks, rel.Name) if err != nil { return nil, err } @@ -57,50 +61,7 @@ func NewTestSuite(rel *release.Release, env *Environment) (*TestSuite, error) { }, nil } -func newTest(testManifest string) (*test, error) { - var sh util.SimpleHead - err := yaml.Unmarshal([]byte(testManifest), &sh) - if err != nil { - return nil, err - } - - if sh.Kind != "Pod" { - return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name) - } - - return &test{ - manifest: testManifest, - result: &release.TestRun{ - Name: sh.Metadata.Name, - }, - }, nil -} - -func (t *TestSuite) createTestPod(test *test, env *Environment) error { - b := bytes.NewBufferString(test.manifest) - if err := env.KubeClient.Create(env.Namespace, b); err != nil { - log.Printf(err.Error()) - test.result.Info = err.Error() - test.result.Status = release.TestRun_FAILURE - return err - } - - return nil -} - -func (t *TestSuite) getPodExitStatus(test *test, env *Environment) (api.PodPhase, error) { - b := bytes.NewBufferString(test.manifest) - status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second) - if err != nil { - log.Printf("Error getting status for pod %s: %s", test.result.Name, err) - test.result.Info = err.Error() - test.result.Status = release.TestRun_UNKNOWN - return status, err - } - - return status, err -} - +// Run executes tests in a test suite and stores a result within the context of a given environment func (t *TestSuite) Run(env *Environment) error { t.StartedAt = timeconv.Now() @@ -126,7 +87,7 @@ func (t *TestSuite) Run(env *Environment) error { resourceCleanExit := true status := api.PodUnknown if resourceCreated { - status, err = t.getPodExitStatus(test, env) + status, err = t.getTestPodStatus(test, env) if err != nil { resourceCleanExit = false if streamErr := streamUnknown(test.result.Name, test.result.Info, env.Stream); streamErr != nil { @@ -147,14 +108,16 @@ func (t *TestSuite) Run(env *Environment) error { } } //else if resourceCreated && resourceCleanExit && status == api.PodUnkown { - _ = append(t.Results, test.result) + test.result.CompletedAt = timeconv.Now() + t.Results = append(t.Results, test.result) } t.CompletedAt = timeconv.Now() return nil } -func filterTestHooks(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) { +// NOTE: may want to move this function to pkg/tiller in the future +func filterHooksForTestHooks(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) { testHooks := []*release.Hook{} notFoundErr := fmt.Errorf("no tests found for release %s", releaseName) @@ -178,8 +141,9 @@ func filterTestHooks(hooks []*release.Hook, releaseName string) ([]*release.Hook return testHooks, nil } -func prepareTestManifests(hooks []*release.Hook, releaseName string) ([]string, error) { - testHooks, err := filterTestHooks(hooks, releaseName) +// NOTE: may want to move this function to pkg/tiller in the future +func extractTestManifestsFromHooks(hooks []*release.Hook, releaseName string) ([]string, error) { + testHooks, err := filterHooksForTestHooks(hooks, releaseName) if err != nil { return nil, err } @@ -193,3 +157,48 @@ func prepareTestManifests(hooks []*release.Hook, releaseName string) ([]string, } return tests, nil } + +func newTest(testManifest string) (*test, error) { + var sh util.SimpleHead + err := yaml.Unmarshal([]byte(testManifest), &sh) + if err != nil { + return nil, err + } + + if sh.Kind != "Pod" { + return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name) + } + + name := strings.TrimSuffix(sh.Metadata.Name, ",") + return &test{ + manifest: testManifest, + result: &release.TestRun{ + Name: name, + }, + }, nil +} + +func (t *TestSuite) createTestPod(test *test, env *Environment) error { + b := bytes.NewBufferString(test.manifest) + if err := env.KubeClient.Create(env.Namespace, b, env.Timeout, false); err != nil { + log.Printf(err.Error()) + test.result.Info = err.Error() + test.result.Status = release.TestRun_FAILURE + return err + } + + return nil +} + +func (t *TestSuite) getTestPodStatus(test *test, env *Environment) (api.PodPhase, error) { + b := bytes.NewBufferString(test.manifest) + status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second) + if err != nil { + log.Printf("Error getting status for pod %s: %s", test.result.Name, err) + test.result.Info = err.Error() + test.result.Status = release.TestRun_UNKNOWN + return status, err + } + + return status, err +} diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go new file mode 100644 index 000000000..329346bd9 --- /dev/null +++ b/pkg/releasetesting/test_suite_test.go @@ -0,0 +1,247 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package releasetesting + +import ( + "io" + "os" + "testing" + "time" + + "github.com/golang/protobuf/ptypes/timestamp" + "golang.org/x/net/context" + grpc "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "k8s.io/kubernetes/pkg/api" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" + tillerEnv "k8s.io/helm/pkg/tiller/environment" +) + +func TestNewTestSuite(t *testing.T) { + rel := releaseStub() + + _, err := NewTestSuite(rel) + if err != nil { + t.Errorf("%s", err) + } +} + +func TestRun(t *testing.T) { + + ts := testSuiteFixture() + if err := ts.Run(testEnvFixture()); err != nil { + t.Errorf("%s", err) + } + + if ts.StartedAt == nil { + t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) + } + + if ts.CompletedAt == nil { + t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) + } + + if len(ts.Results) != 1 { + t.Errorf("Expected 1 test result. Got %v", len(ts.Results)) + } + + result := ts.Results[0] + if result.StartedAt == nil { + t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt) + } + + if result.CompletedAt == nil { + t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt) + } + + if result.Name != "finding-nemo" { + t.Errorf("Expected test name to be finding-nemo. Got: %v", result.Name) + } + + if result.Status != release.TestRun_SUCCESS { + t.Errorf("Expected test result to be successful, got: %v", result.Status) + } + +} + +func TestGetTestPodStatus(t *testing.T) { + ts := testSuiteFixture() + + status, err := ts.getTestPodStatus(testFixture(), testEnvFixture()) + if err != nil { + t.Errorf("Expected getTestPodStatus not to return err, Got: %s", err) + } + + if status != api.PodSucceeded { + t.Errorf("Expected pod status to be succeeded, Got: %s ", status) + } + +} + +func TestExtractTestManifestsFromHooks(t *testing.T) { + rel := releaseStub() + testManifests, err := extractTestManifestsFromHooks(rel.Hooks, rel.Name) + if err != nil { + t.Errorf("Expected no error, Got: %s", err) + } + + if len(testManifests) != 1 { + t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests)) + } +} + +func chartStub() *chart.Chart { + return &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "nemo", + }, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithTestHook)}, + }, + } +} + +var manifestWithTestHook = ` +apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command +` +var manifestWithInstallHooks = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm + annotations: + "helm.sh/hook": post-install,pre-delete +data: + name: value +` + +func releaseStub() *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + return &release.Release{ + Name: "lost-fish", + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, + Description: "a release stub", + }, + Chart: chartStub(), + Config: &chart.Config{Raw: `name: value`}, + Version: 1, + Hooks: []*release.Hook{ + { + Name: "finding-nemo", + Kind: "Pod", + Path: "finding-nemo", + Manifest: manifestWithTestHook, + Events: []release.Hook_Event{ + release.Hook_RELEASE_TEST, + }, + }, + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithInstallHooks, + Events: []release.Hook_Event{ + release.Hook_POST_INSTALL, + release.Hook_PRE_DELETE, + }, + }, + }, + } +} + +func testFixture() *test { + return &test{ + manifest: manifestWithTestHook, + result: &release.TestRun{}, + } +} + +func testSuiteFixture() *TestSuite { + testManifests := []string{manifestWithTestHook} + testResults := []*release.TestRun{} + ts := &TestSuite{ + TestManifests: testManifests, + Results: testResults, + } + + return ts +} + +func testEnvFixture() *Environment { + tillerEnv := mockTillerEnvironment() + + return &Environment{ + Namespace: "default", + KubeClient: tillerEnv.KubeClient, + Timeout: 5, + Stream: mockStream{}, + } +} + +func mockTillerEnvironment() *tillerEnv.Environment { + e := tillerEnv.New() + e.Releases = storage.Init(driver.NewMemory()) + e.KubeClient = newPodSucceededKubeClient() + return e +} + +type mockStream struct { + stream grpc.ServerStream +} + +func (rs mockStream) Send(m *services.TestReleaseResponse) error { + return nil +} +func (rs mockStream) SetHeader(m metadata.MD) error { return nil } +func (rs mockStream) SendHeader(m metadata.MD) error { return nil } +func (rs mockStream) SetTrailer(m metadata.MD) {} +func (rs mockStream) SendMsg(v interface{}) error { return nil } +func (rs mockStream) RecvMsg(v interface{}) error { return nil } +func (rs mockStream) Context() context.Context { return helm.NewContext() } + +func newPodSucceededKubeClient() *podSucceededKubeClient { + return &podSucceededKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: os.Stdout}, + } +} + +type podSucceededKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (api.PodPhase, error) { + return api.PodSucceeded, nil +} diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go index d79ea5a77..b6bb87b0a 100644 --- a/pkg/releaseutil/manifest.go +++ b/pkg/releaseutil/manifest.go @@ -21,6 +21,7 @@ import ( "strings" ) +// SimpleHead defines what the structure of the head of a manifest file type SimpleHead struct { Version string `json:"apiVersion"` Kind string `json:"kind,omitempty"` @@ -30,6 +31,7 @@ type SimpleHead struct { } `json:"metadata,omitempty"` } +// SplitManifests takes a string of manifest and returns a map contains individual manifests func SplitManifests(bigfile string) map[string]string { // This is not the best way of doing things, but it's how k8s itself does it. // Basically, we're quickly splitting a stream of YAML documents into an diff --git a/pkg/tiller/hooks_test.go b/pkg/tiller/hooks_test.go index 8c83c1468..823e7469c 100644 --- a/pkg/tiller/hooks_test.go +++ b/pkg/tiller/hooks_test.go @@ -22,8 +22,8 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" ) func TestSortManifests(t *testing.T) { diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index 568daf300..212b8aefc 100644 --- a/pkg/tiller/kind_sorter_test.go +++ b/pkg/tiller/kind_sorter_test.go @@ -19,7 +19,7 @@ package tiller import ( "testing" - "k8s.io/helm/pkg/hooks" + util "k8s.io/helm/pkg/releaseutil" ) func TestKindSorter(t *testing.T) { diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 33ce48c97..6403c7314 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -1051,7 +1051,7 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro return err } -// RunTestRelease runs a pre-defined test on a given release +// RunReleaseTest runs pre-defined tests stored as hooks on a given release func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { if !ValidName.MatchString(req.Name) { @@ -1071,7 +1071,11 @@ func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream Stream: stream, } - tSuite, err := reltesting.NewTestSuite(rel, testEnv) + tSuite, err := reltesting.NewTestSuite(rel) + if err != nil { + return err + } + if err := tSuite.Run(testEnv); err != nil { return err } diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 1b84e4999..f03e5d981 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -83,7 +83,7 @@ data: func rsFixture() *ReleaseServer { return &ReleaseServer{ - env: mockEnvironment(), + env: MockEnvironment(), clientset: fake.NewSimpleClientset(), } } @@ -1411,7 +1411,7 @@ func TestListReleasesFilter(t *testing.T) { } } -func mockEnvironment() *environment.Environment { +func MockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.Init(driver.NewMemory()) e.KubeClient = &environment.PrintingKubeClient{Out: os.Stdout} From bf9ae52e1c888c0697e35c486f63369b6146f764 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 2 Feb 2017 12:23:52 -0500 Subject: [PATCH 6/8] ref(*): change test hook to be test-success --- _proto/hapi/release/hook.proto | 2 +- .../nginx/templates/service-test.yaml | 13 +++ .../nginx/templates/tests/service-test.yaml | 27 ------ pkg/proto/hapi/release/hook.pb.go | 87 ++++++++++--------- pkg/releasetesting/test_suite.go | 2 +- pkg/releasetesting/test_suite_test.go | 2 +- pkg/tiller/hooks.go | 36 ++++---- pkg/tiller/release_server_test.go | 54 +++++++++++- 8 files changed, 130 insertions(+), 93 deletions(-) create mode 100644 docs/examples/nginx/templates/service-test.yaml delete mode 100644 docs/examples/nginx/templates/tests/service-test.yaml diff --git a/_proto/hapi/release/hook.proto b/_proto/hapi/release/hook.proto index a871f25ee..6ae2a71e5 100644 --- a/_proto/hapi/release/hook.proto +++ b/_proto/hapi/release/hook.proto @@ -32,7 +32,7 @@ message Hook { POST_UPGRADE = 6; PRE_ROLLBACK = 7; POST_ROLLBACK = 8; - RELEASE_TEST = 9; + RELEASE_TEST_SUCCESS = 9; } string name = 1; // Kind is the Kubernetes kind. diff --git a/docs/examples/nginx/templates/service-test.yaml b/docs/examples/nginx/templates/service-test.yaml new file mode 100644 index 000000000..0accd4c2a --- /dev/null +++ b/docs/examples/nginx/templates/service-test.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-service-test" + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: curl + image: radial/busyboxplus:curl + command: ['curl'] + args: [ '{{ template "fullname" .}}:{{default 80 .Values.httpPort}}' ] + restartPolicy: Never diff --git a/docs/examples/nginx/templates/tests/service-test.yaml b/docs/examples/nginx/templates/tests/service-test.yaml deleted file mode 100644 index a5a9400fa..000000000 --- a/docs/examples/nginx/templates/tests/service-test.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{.Release.Name}}-service-test" - annotations: - "helm.sh/hook": test -spec: - containers: - - name: curl - image: radial/busyboxplus:curl - command: ['curl'] - args: [ '{{ template "fullname" .}}:{{default 80 .Values.httpPort}}' ] - restartPolicy: Never ---- -apiVersion: v1 -kind: Pod -metadata: - name: "{{.Release.Name}}-service-failing-test" - annotations: - "helm.sh/hook": test -spec: - containers: - - name: curl - image: radial/busyboxplus:curl - command: ['curl'] - args: [ '{{ template "fullname" .}}-service:{{default 80 .Values.httpPort}}' ] - restartPolicy: Never diff --git a/pkg/proto/hapi/release/hook.pb.go b/pkg/proto/hapi/release/hook.pb.go index c90e0b59e..810df99ff 100644 --- a/pkg/proto/hapi/release/hook.pb.go +++ b/pkg/proto/hapi/release/hook.pb.go @@ -42,16 +42,16 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Hook_Event int32 const ( - Hook_UNKNOWN Hook_Event = 0 - Hook_PRE_INSTALL Hook_Event = 1 - Hook_POST_INSTALL Hook_Event = 2 - Hook_PRE_DELETE Hook_Event = 3 - Hook_POST_DELETE Hook_Event = 4 - Hook_PRE_UPGRADE Hook_Event = 5 - Hook_POST_UPGRADE Hook_Event = 6 - Hook_PRE_ROLLBACK Hook_Event = 7 - Hook_POST_ROLLBACK Hook_Event = 8 - Hook_RELEASE_TEST Hook_Event = 9 + Hook_UNKNOWN Hook_Event = 0 + Hook_PRE_INSTALL Hook_Event = 1 + Hook_POST_INSTALL Hook_Event = 2 + Hook_PRE_DELETE Hook_Event = 3 + Hook_POST_DELETE Hook_Event = 4 + Hook_PRE_UPGRADE Hook_Event = 5 + Hook_POST_UPGRADE Hook_Event = 6 + Hook_PRE_ROLLBACK Hook_Event = 7 + Hook_POST_ROLLBACK Hook_Event = 8 + Hook_RELEASE_TEST_SUCCESS Hook_Event = 9 ) var Hook_Event_name = map[int32]string{ @@ -64,19 +64,19 @@ var Hook_Event_name = map[int32]string{ 6: "POST_UPGRADE", 7: "PRE_ROLLBACK", 8: "POST_ROLLBACK", - 9: "RELEASE_TEST", + 9: "RELEASE_TEST_SUCCESS", } var Hook_Event_value = map[string]int32{ - "UNKNOWN": 0, - "PRE_INSTALL": 1, - "POST_INSTALL": 2, - "PRE_DELETE": 3, - "POST_DELETE": 4, - "PRE_UPGRADE": 5, - "POST_UPGRADE": 6, - "PRE_ROLLBACK": 7, - "POST_ROLLBACK": 8, - "RELEASE_TEST": 9, + "UNKNOWN": 0, + "PRE_INSTALL": 1, + "POST_INSTALL": 2, + "PRE_DELETE": 3, + "POST_DELETE": 4, + "PRE_UPGRADE": 5, + "POST_UPGRADE": 6, + "PRE_ROLLBACK": 7, + "POST_ROLLBACK": 8, + "RELEASE_TEST_SUCCESS": 9, } func (x Hook_Event) String() string { @@ -119,26 +119,27 @@ func init() { func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 334 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0xcd, 0x6e, 0xe2, 0x30, - 0x14, 0x85, 0x27, 0x10, 0x12, 0x30, 0x0c, 0xe3, 0xf1, 0x66, 0x2c, 0x36, 0x83, 0x58, 0xb1, 0x72, - 0x2a, 0xaa, 0x3e, 0x40, 0x28, 0x56, 0x5b, 0x11, 0x05, 0xe4, 0x04, 0x55, 0xea, 0x26, 0x32, 0xaa, - 0x81, 0x08, 0x12, 0x47, 0xc4, 0xf4, 0xc1, 0xfa, 0x7c, 0x5d, 0x54, 0x76, 0x7e, 0xd4, 0xdd, 0xcd, - 0x77, 0xbf, 0x1c, 0xfb, 0x18, 0xfc, 0x3b, 0xf1, 0x22, 0xf5, 0xae, 0xe2, 0x22, 0x78, 0x29, 0xbc, - 0x93, 0x94, 0x67, 0x52, 0x5c, 0xa5, 0x92, 0x68, 0xa4, 0x17, 0xa4, 0x5e, 0x4c, 0xfe, 0x1f, 0xa5, - 0x3c, 0x5e, 0x84, 0x67, 0x76, 0xfb, 0xdb, 0xc1, 0x53, 0x69, 0x26, 0x4a, 0xc5, 0xb3, 0xa2, 0xd2, - 0x67, 0x5f, 0x1d, 0x60, 0x3f, 0x4b, 0x79, 0x46, 0x08, 0xd8, 0x39, 0xcf, 0x04, 0xb6, 0xa6, 0xd6, - 0x7c, 0xc0, 0xcc, 0xac, 0xd9, 0x39, 0xcd, 0xdf, 0x71, 0xa7, 0x62, 0x7a, 0xd6, 0xac, 0xe0, 0xea, - 0x84, 0xbb, 0x15, 0xd3, 0x33, 0x9a, 0x80, 0x7e, 0xc6, 0xf3, 0xf4, 0x20, 0x4a, 0x85, 0x6d, 0xc3, - 0xdb, 0x6f, 0x74, 0x07, 0x1c, 0xf1, 0x21, 0x72, 0x55, 0xe2, 0xde, 0xb4, 0x3b, 0x1f, 0x2f, 0x30, - 0xf9, 0x79, 0x41, 0xa2, 0xcf, 0x26, 0x54, 0x0b, 0xac, 0xf6, 0xd0, 0x03, 0xe8, 0x5f, 0x78, 0xa9, - 0x92, 0xeb, 0x2d, 0xc7, 0xce, 0xd4, 0x9a, 0x0f, 0x17, 0x13, 0x52, 0xd5, 0x20, 0x4d, 0x0d, 0x12, - 0x37, 0x35, 0x98, 0xab, 0x5d, 0x76, 0xcb, 0x67, 0x9f, 0x16, 0xe8, 0x99, 0x20, 0x34, 0x04, 0xee, - 0x2e, 0x5c, 0x87, 0x9b, 0xd7, 0x10, 0xfe, 0x42, 0x7f, 0xc0, 0x70, 0xcb, 0x68, 0xf2, 0x12, 0x46, - 0xb1, 0x1f, 0x04, 0xd0, 0x42, 0x10, 0x8c, 0xb6, 0x9b, 0x28, 0x6e, 0x49, 0x07, 0x8d, 0x01, 0xd0, - 0xca, 0x8a, 0x06, 0x34, 0xa6, 0xb0, 0x6b, 0x7e, 0xd1, 0x46, 0x0d, 0xec, 0x26, 0x63, 0xb7, 0x7d, - 0x62, 0xfe, 0x8a, 0xc2, 0x5e, 0x9b, 0xd1, 0x10, 0xc7, 0x10, 0x46, 0x13, 0xb6, 0x09, 0x82, 0xa5, - 0xff, 0xb8, 0x86, 0x2e, 0xfa, 0x0b, 0x7e, 0x1b, 0xa7, 0x45, 0x7d, 0x2d, 0x31, 0x1a, 0x50, 0x3f, - 0xa2, 0x49, 0x4c, 0xa3, 0x18, 0x0e, 0x96, 0x83, 0x37, 0xb7, 0x7e, 0x89, 0xbd, 0x63, 0xca, 0xdd, - 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x35, 0xb7, 0x2a, 0x22, 0xda, 0x01, 0x00, 0x00, + // 343 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0xdf, 0x6e, 0xa2, 0x40, + 0x14, 0xc6, 0x17, 0x41, 0xd0, 0xa3, 0xeb, 0xb2, 0x93, 0x4d, 0x76, 0xe2, 0x4d, 0x8d, 0x57, 0x5e, + 0x0d, 0x8d, 0x4d, 0x1f, 0x00, 0x75, 0xd2, 0x36, 0x12, 0x34, 0x03, 0xa6, 0x49, 0x6f, 0x08, 0xa6, + 0xa3, 0x12, 0x85, 0x21, 0x82, 0x7d, 0x82, 0x3e, 0x55, 0x9f, 0xae, 0x99, 0xe1, 0x4f, 0x7a, 0x77, + 0xf8, 0x9d, 0x1f, 0xdf, 0xcc, 0x37, 0xf0, 0xff, 0x14, 0xe7, 0x89, 0x73, 0xe5, 0x17, 0x1e, 0x17, + 0xdc, 0x39, 0x09, 0x71, 0x26, 0xf9, 0x55, 0x94, 0x02, 0x0d, 0xe5, 0x82, 0xd4, 0x8b, 0xf1, 0xdd, + 0x51, 0x88, 0xe3, 0x85, 0x3b, 0x6a, 0xb7, 0xbf, 0x1d, 0x9c, 0x32, 0x49, 0x79, 0x51, 0xc6, 0x69, + 0x5e, 0xe9, 0xd3, 0x4f, 0x1d, 0x8c, 0x67, 0x21, 0xce, 0x08, 0x81, 0x91, 0xc5, 0x29, 0xc7, 0xda, + 0x44, 0x9b, 0xf5, 0x99, 0x9a, 0x25, 0x3b, 0x27, 0xd9, 0x3b, 0xee, 0x54, 0x4c, 0xce, 0x92, 0xe5, + 0x71, 0x79, 0xc2, 0x7a, 0xc5, 0xe4, 0x8c, 0xc6, 0xd0, 0x4b, 0xe3, 0x2c, 0x39, 0xf0, 0xa2, 0xc4, + 0x86, 0xe2, 0xed, 0x37, 0xba, 0x07, 0x93, 0x7f, 0xf0, 0xac, 0x2c, 0x70, 0x77, 0xa2, 0xcf, 0x46, + 0x73, 0x4c, 0x7e, 0x5e, 0x90, 0xc8, 0xb3, 0x09, 0x95, 0x02, 0xab, 0x3d, 0xf4, 0x08, 0xbd, 0x4b, + 0x5c, 0x94, 0xd1, 0xf5, 0x96, 0x61, 0x73, 0xa2, 0xcd, 0x06, 0xf3, 0x31, 0xa9, 0x6a, 0x90, 0xa6, + 0x06, 0x09, 0x9b, 0x1a, 0xcc, 0x92, 0x2e, 0xbb, 0x65, 0xd3, 0x2f, 0x0d, 0xba, 0x2a, 0x08, 0x0d, + 0xc0, 0xda, 0xf9, 0x6b, 0x7f, 0xf3, 0xea, 0xdb, 0xbf, 0xd0, 0x1f, 0x18, 0x6c, 0x19, 0x8d, 0x5e, + 0xfc, 0x20, 0x74, 0x3d, 0xcf, 0xd6, 0x90, 0x0d, 0xc3, 0xed, 0x26, 0x08, 0x5b, 0xd2, 0x41, 0x23, + 0x00, 0xa9, 0xac, 0xa8, 0x47, 0x43, 0x6a, 0xeb, 0xea, 0x17, 0x69, 0xd4, 0xc0, 0x68, 0x32, 0x76, + 0xdb, 0x27, 0xe6, 0xae, 0xa8, 0xdd, 0x6d, 0x33, 0x1a, 0x62, 0x2a, 0xc2, 0x68, 0xc4, 0x36, 0x9e, + 0xb7, 0x70, 0x97, 0x6b, 0xdb, 0x42, 0x7f, 0xe1, 0xb7, 0x72, 0x5a, 0xd4, 0x43, 0x18, 0xfe, 0x31, + 0xea, 0x51, 0x37, 0xa0, 0x51, 0x48, 0x83, 0x30, 0x0a, 0x76, 0xcb, 0x25, 0x0d, 0x02, 0xbb, 0xbf, + 0xe8, 0xbf, 0x59, 0xf5, 0x8b, 0xec, 0x4d, 0x55, 0xf2, 0xe1, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xdf, + 0xef, 0x1c, 0xfd, 0xe2, 0x01, 0x00, 0x00, } diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index bd43655eb..d5c9f6346 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -127,7 +127,7 @@ func filterHooksForTestHooks(hooks []*release.Hook, releaseName string) ([]*rele for _, h := range hooks { for _, e := range h.Events { - if e == release.Hook_RELEASE_TEST { + if e == release.Hook_RELEASE_TEST_SUCCESS { testHooks = append(testHooks, h) continue } diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go index 329346bd9..446b63e2a 100644 --- a/pkg/releasetesting/test_suite_test.go +++ b/pkg/releasetesting/test_suite_test.go @@ -165,7 +165,7 @@ func releaseStub() *release.Release { Path: "finding-nemo", Manifest: manifestWithTestHook, Events: []release.Hook_Event{ - release.Hook_RELEASE_TEST, + release.Hook_RELEASE_TEST_SUCCESS, }, }, { diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 225da94df..ed151602b 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -33,27 +33,27 @@ import ( const hookAnno = "helm.sh/hook" const ( - preInstall = "pre-install" - postInstall = "post-install" - preDelete = "pre-delete" - postDelete = "post-delete" - preUpgrade = "pre-upgrade" - postUpgrade = "post-upgrade" - preRollback = "pre-rollback" - postRollback = "post-rollback" - releaseTest = "test" + preInstall = "pre-install" + postInstall = "post-install" + preDelete = "pre-delete" + postDelete = "post-delete" + preUpgrade = "pre-upgrade" + postUpgrade = "post-upgrade" + preRollback = "pre-rollback" + postRollback = "post-rollback" + releaseTestSuccess = "test-success" ) var events = map[string]release.Hook_Event{ - preInstall: release.Hook_PRE_INSTALL, - postInstall: release.Hook_POST_INSTALL, - preDelete: release.Hook_PRE_DELETE, - postDelete: release.Hook_POST_DELETE, - preUpgrade: release.Hook_PRE_UPGRADE, - postUpgrade: release.Hook_POST_UPGRADE, - preRollback: release.Hook_PRE_ROLLBACK, - postRollback: release.Hook_POST_ROLLBACK, - releaseTest: release.Hook_RELEASE_TEST, + preInstall: release.Hook_PRE_INSTALL, + postInstall: release.Hook_POST_INSTALL, + preDelete: release.Hook_PRE_DELETE, + postDelete: release.Hook_POST_DELETE, + preUpgrade: release.Hook_PRE_UPGRADE, + postUpgrade: release.Hook_POST_UPGRADE, + preRollback: release.Hook_PRE_ROLLBACK, + postRollback: release.Hook_POST_ROLLBACK, + releaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS, } // manifest represents a manifest file, which has a name and some content. diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index f03e5d981..da11ef02e 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -27,6 +27,7 @@ import ( "github.com/golang/protobuf/ptypes/timestamp" "golang.org/x/net/context" + grpc "google.golang.org/grpc" "google.golang.org/grpc/metadata" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" @@ -51,6 +52,20 @@ data: name: value ` +var manifestWithTestHook = ` +apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command +` + var manifestWithKeep = `apiVersion: v1 kind: ConfigMap metadata: @@ -136,6 +151,15 @@ func namedReleaseStub(name string, status release.Status_Code) *release.Release release.Hook_PRE_DELETE, }, }, + { + Name: "finding-nemo", + Kind: "Pod", + Path: "finding-nemo", + Manifest: manifestWithTestHook, + Events: []release.Hook_Event{ + release.Hook_RELEASE_TEST_SUCCESS, + }, + }, }, } } @@ -950,8 +974,8 @@ func TestRollbackRelease(t *testing.T) { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } - if len(updated.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) + if len(updated.Hooks) != 2 { + t.Fatalf("Expected 2 hooks, got %d", len(updated.Hooks)) } if updated.Hooks[0].Manifest != manifestWithHook { @@ -1411,6 +1435,18 @@ func TestListReleasesFilter(t *testing.T) { } } +func TestRunReleaseTest(t *testing.T) { + rs := rsFixture() + rel := namedReleaseStub("nemo", release.Status_DEPLOYED) + rs.env.Releases.Create(rel) + + req := &services.TestReleaseRequest{Name: "nemo", Timeout: 2} + err := rs.RunReleaseTest(req, mockRunReleaseTestServer{}) + if err != nil { + t.Fatalf("failed to run release tests on %s: %s", rel.Name, err) + } +} + func MockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.Init(driver.NewMemory()) @@ -1462,3 +1498,17 @@ func (l *mockListServer) RecvMsg(v interface{}) error { return nil } func (l *mockListServer) SendHeader(m metadata.MD) error { return nil } func (l *mockListServer) SetTrailer(m metadata.MD) {} func (l *mockListServer) SetHeader(m metadata.MD) error { return nil } + +type mockRunReleaseTestServer struct { + stream grpc.ServerStream +} + +func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error { + return nil +} +func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil } +func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil } +func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {} +func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil } +func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil } +func (rs mockRunReleaseTestServer) Context() context.Context { return helm.NewContext() } From 538e8297ef1440bac69c218a1233ace6f2106c36 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 6 Feb 2017 10:39:57 -0500 Subject: [PATCH 7/8] ref(*): add namespace info to failing test msg * also cleanup comments --- _proto/hapi/services/tiller.proto | 6 ++---- cmd/helm/release_testing.go | 2 +- pkg/releasetesting/environment.go | 4 ++-- pkg/releasetesting/test_suite.go | 4 ++-- pkg/releasetesting/test_suite_test.go | 2 +- pkg/tiller/release_server_test.go | 2 +- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 572e39be0..09799a240 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -79,8 +79,7 @@ service ReleaseService { rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) { } - //TODO: move this to a test release service or rename to RunReleaseTest - // TestRelease runs the tests for a given release + // RunReleaseTest executes the tests defined of a named release rpc RunReleaseTest(TestReleaseRequest) returns (stream TestReleaseResponse) { } } @@ -318,8 +317,7 @@ message TestReleaseRequest { int64 timeout = 2; } -// TestReleaseResponse +// TestReleaseResponse represents a message from executing a test message TestReleaseResponse { - // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) string msg = 1; } diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 13af3641d..0a2ce683d 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -26,7 +26,7 @@ import ( ) const releaseTestDesc = ` -Th test command runs the tests for a release. +The test command runs the tests for a release. The argument this command takes is the name of a deployed release. The tests to be run are defined in the chart that was installed. diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index 565d4ab0e..0b409856f 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -43,8 +43,8 @@ func streamError(info string, stream services.ReleaseService_RunReleaseTestServe return err } -func streamFailed(name string, stream services.ReleaseService_RunReleaseTestServer) error { - msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s` for more info", name, name) +func streamFailed(name, namespace string, stream services.ReleaseService_RunReleaseTestServer) error { + msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, namespace) err := streamMessage(msg, stream) return err } diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index d5c9f6346..6bcf1c96d 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -103,10 +103,10 @@ func (t *TestSuite) Run(env *Environment) error { } } else if resourceCreated && resourceCleanExit && status == api.PodFailed { test.result.Status = release.TestRun_FAILURE - if streamErr := streamFailed(test.result.Name, env.Stream); streamErr != nil { + if streamErr := streamFailed(test.result.Name, env.Namespace, env.Stream); streamErr != nil { return err } - } //else if resourceCreated && resourceCleanExit && status == api.PodUnkown { + } test.result.CompletedAt = timeconv.Now() t.Results = append(t.Results, test.result) diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go index 446b63e2a..5f1151e5f 100644 --- a/pkg/releasetesting/test_suite_test.go +++ b/pkg/releasetesting/test_suite_test.go @@ -128,7 +128,7 @@ kind: Pod metadata: name: finding-nemo, annotations: - "helm.sh/hook": test + "helm.sh/hook": test-success spec: containers: - name: nemo-test diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index da11ef02e..5cf6024f8 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -58,7 +58,7 @@ kind: Pod metadata: name: finding-nemo, annotations: - "helm.sh/hook": test + "helm.sh/hook": test-success spec: containers: - name: nemo-test From 4896ea7cf5a2f41074aab7168ae4da8265b2a7ca Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 7 Feb 2017 09:07:36 -0600 Subject: [PATCH 8/8] chore(pkg/tiller): add logs on RunReleaseTesting --- pkg/tiller/release_server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 6403c7314..daae0df1b 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -1073,10 +1073,12 @@ func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream tSuite, err := reltesting.NewTestSuite(rel) if err != nil { + log.Printf("Error creating test suite for %s", rel.Name) return err } if err := tSuite.Run(testEnv); err != nil { + log.Printf("Error running test suite for %s", rel.Name) return err }