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