mirror of https://github.com/helm/helm
parent
9bd12953a9
commit
e132191275
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in new issue