Unverified Commit 024f168b authored by Matthew Fisher's avatar Matthew Fisher Committed by GitHub

Merge pull request #6612 from bacongobbler/rebase-6167

feat(test): add `--logs` to `helm test`
parents 66e0c0b4 9b9dcebe
......@@ -352,6 +352,8 @@ message TestReleaseRequest {
bool parallel = 4;
// maximum number of test pods to run in parallel
uint32 max_parallel = 5;
// logs specifies whether or not to dump the logs from the test pods
bool logs = 6;
}
// TestReleaseResponse represents a message from executing a test
......
......@@ -41,6 +41,7 @@ type releaseTestCmd struct {
cleanup bool
parallel bool
maxParallel uint32
logs bool
}
func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
......@@ -71,6 +72,7 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&rlsTest.cleanup, "cleanup", false, "Delete test pods upon completion")
f.BoolVar(&rlsTest.parallel, "parallel", false, "Run test pods in parallel")
f.Uint32Var(&rlsTest.maxParallel, "max", 20, "Maximum number of test pods to run in parallel")
f.BoolVar(&rlsTest.logs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup")
// set defaults from environment
settings.InitTLS(f)
......@@ -85,6 +87,7 @@ func (t *releaseTestCmd) run() (err error) {
helm.ReleaseTestCleanup(t.cleanup),
helm.ReleaseTestParallel(t.parallel),
helm.ReleaseTestMaxParallel(t.maxParallel),
helm.ReleaseTestLogs(t.logs),
)
testErr := &testErr{}
......
......@@ -20,6 +20,7 @@ helm test [RELEASE] [flags]
```
--cleanup Delete test pods upon completion
-h, --help help for test
--logs Dump the logs from test pods (this runs after all tests are complete, but before any cleanup
--max uint32 Maximum number of test pods to run in parallel (default 20)
--parallel Run test pods in parallel
--timeout int Time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300)
......@@ -47,4 +48,4 @@ helm test [RELEASE] [flags]
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 25-Jul-2019
###### Auto generated by spf13/cobra on 8-Oct-2019
......@@ -241,6 +241,13 @@ func ReleaseTestMaxParallel(max uint32) ReleaseTestOption {
}
}
// ReleaseTestLogs is a boolean value representing whether to dump the logs from test pods
func ReleaseTestLogs(logs bool) ReleaseTestOption {
return func(opts *options) {
opts.testReq.Logs = logs
}
}
// RollbackTimeout specifies the number of seconds before kubernetes calls timeout
func RollbackTimeout(timeout int64) RollbackOption {
return func(opts *options) {
......
......@@ -31,7 +31,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
......@@ -935,7 +935,7 @@ func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader,
}
func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, fields.Everything())
lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", info.Name)))
c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout)
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
......@@ -947,6 +947,20 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf
return err
}
// GetPodLogs takes pod name and namespace and returns the current logs (streaming is NOT enabled).
func (c *Client) GetPodLogs(name, ns string) (io.ReadCloser, error) {
client, err := c.KubernetesClientSet()
if err != nil {
return nil, err
}
req := client.CoreV1().Pods(ns).GetLogs(name, &v1.PodLogOptions{})
logReader, err := req.Stream()
if err != nil {
return nil, fmt.Errorf("error in opening log stream, got: %s", err)
}
return logReader, nil
}
func isPodComplete(event watch.Event) (bool, error) {
o, ok := event.Object.(*v1.Pod)
if !ok {
......
This diff is collapsed.
......@@ -19,17 +19,22 @@ package releasetesting
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"strings"
"sync"
"time"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/tiller/environment"
)
// logEscaper is necessary for escaping control characters found in the log stream.
var logEscaper = strings.NewReplacer("%", "%%")
// Environment encapsulates information about where test suite executes and returns results
type Environment struct {
Namespace string
......@@ -126,3 +131,31 @@ func (env *Environment) DeleteTestPods(testManifests []string) {
}
}
}
// GetLogs collects the logs from the pods created in testManifests
func (env *Environment) GetLogs(testManifests []string) {
for _, testManifest := range testManifests {
infos, err := env.KubeClient.Build(env.Namespace, bytes.NewBufferString(testManifest))
if err != nil {
env.streamError(err.Error())
continue
}
if len(infos) == 0 {
env.streamError(fmt.Sprint("Pod manifest is invalid. Unable to obtain the logs"))
continue
}
podName := infos[0].Object.(*v1.Pod).Name
lr, err := env.KubeClient.GetPodLogs(podName, env.Namespace)
if err != nil {
env.streamError(err.Error())
continue
}
logs, err := ioutil.ReadAll(lr)
if err != nil {
env.streamError(err.Error())
continue
}
msg := fmt.Sprintf("\nPOD LOGS: %s\n%s", podName, logEscaper.Replace(string(logs)))
env.streamMessage(msg, release.TestRun_RUNNING)
}
}
......@@ -89,6 +89,24 @@ func TestDeleteTestPodsFailingDelete(t *testing.T) {
}
}
func TestGetTestPodLogs(t *testing.T) {
mockTestSuite := testSuiteFixture([]string{manifestWithTestSuccessHook})
mockTestEnv := newMockTestingEnvironment()
mockTestEnv.KubeClient = newGetLogKubeClient()
mockTestEnv.GetLogs(mockTestSuite.TestManifests)
expectedMessage := "ERROR: Pod manifest is invalid. Unable to obtain the logs"
stream := mockTestEnv.Stream.(*mockStream)
if len(stream.messages) != 1 {
t.Errorf("Expected 1 message, got: %v", len(stream.messages))
}
if stream.messages[0].Msg != expectedMessage {
t.Errorf("Expected message '%s', got: %v", expectedMessage, stream.messages[0].Msg)
}
}
func TestStreamMessage(t *testing.T) {
mockTestEnv := newMockTestingEnvironment()
......@@ -181,3 +199,13 @@ func newCreateFailingKubeClient() *createFailingKubeClient {
func (p *createFailingKubeClient) Create(ns string, r io.Reader, t int64, shouldWait bool) error {
return errors.New("We ran out of budget and couldn't create finding-nemo")
}
type getLogKubeClient struct {
tillerEnv.PrintingKubeClient
}
func newGetLogKubeClient() *getLogKubeClient {
return &getLogKubeClient{
PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard},
}
}
......@@ -175,6 +175,8 @@ type KubeClient interface {
// and returns said phase (PodSucceeded or PodFailed qualify).
WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error)
GetPodLogs(name, namespace string) (io.ReadCloser, error)
WaitUntilCRDEstablished(reader io.Reader, timeout time.Duration) error
}
......@@ -255,6 +257,11 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reade
return v1.PodUnknown, err
}
// GetPodLogs implements KubeClient GetPodLogs.
func (p *PrintingKubeClient) GetPodLogs(name, ns string) (io.ReadCloser, error) {
return nil, nil
}
// WaitUntilCRDEstablished implements KubeClient WaitUntilCRDEstablished.
func (p *PrintingKubeClient) WaitUntilCRDEstablished(reader io.Reader, timeout time.Duration) error {
_, err := io.Copy(p.Out, reader)
......
......@@ -78,6 +78,10 @@ func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader i
return "", nil
}
func (k *mockKubeClient) GetPodLogs(name, namespace string) (io.ReadCloser, error) {
return nil, nil
}
func (k *mockKubeClient) WaitUntilCRDEstablished(reader io.Reader, timeout time.Duration) error {
return nil
}
......
......@@ -679,6 +679,9 @@ func (kc *mockHooksKubeClient) Validate(ns string, reader io.Reader) error {
func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) {
return v1.PodUnknown, nil
}
func (kc *mockHooksKubeClient) GetPodLogs(name, namespace string) (io.ReadCloser, error) {
return nil, nil
}
func (kc *mockHooksKubeClient) WaitUntilCRDEstablished(reader io.Reader, timeout time.Duration) error {
return nil
......
......@@ -69,6 +69,10 @@ func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream
Results: tSuite.Results,
}
if req.Logs {
testEnv.GetLogs(tSuite.TestManifests)
}
if req.Cleanup {
testEnv.DeleteTestPods(tSuite.TestManifests)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment