diff --git a/test/e2e/command.go b/test/e2e/command.go new file mode 100644 index 000000000..fba57c812 --- /dev/null +++ b/test/e2e/command.go @@ -0,0 +1,79 @@ +// build +e2e + +package e2e + +import ( + "bytes" + "fmt" + "os/exec" + "regexp" + "strings" + "testing" +) + +type Cmd struct { + t *testing.T + path string + args []string + ran bool + status error + stdout, stderr bytes.Buffer +} + +func (h *Cmd) String() string { + return fmt.Sprintf("%s %s", h.path, strings.Join(h.args, " ")) +} + +func (h *Cmd) exec() error { + cmd := exec.Command(h.path, h.args...) + h.stdout.Reset() + h.stderr.Reset() + cmd.Stdout = &h.stdout + cmd.Stderr = &h.stderr + + h.t.Logf("Executing command: %s", h) + h.status = cmd.Run() + + if h.stdout.Len() > 0 { + h.t.Logf("standard output:\n%s", h.stdout.String()) + } + if h.stderr.Len() > 0 { + h.t.Log("standard error: %s\n", h.stderr.String()) + } + + h.ran = true + return h.status +} + +// Stdout returns standard output of the Cmd run as a string. +func (h *Cmd) Stdout() string { + if !h.ran { + h.t.Fatal("internal testsuite error: stdout called before run") + } + return h.stdout.String() +} + +// Stderr returns standard error of the Cmd run as a string. +func (h *Cmd) Stderr() string { + if !h.ran { + h.t.Fatal("internal testsuite error: stdout called before run") + } + return h.stderr.String() +} + +func (c *Cmd) Match(exp string) bool { + re := regexp.MustCompile(exp) + return re.MatchString(c.Stdout()) +} + +func (h *Cmd) StdoutContains(substring string) bool { + return strings.Contains(h.Stdout(), substring) +} + +func (h *Cmd) StderrContains(substring string) bool { + return strings.Contains(h.Stderr(), substring) +} + +func (h *Cmd) Contains(substring string) bool { + return h.StdoutContains(substring) || h.StderrContains(substring) +} diff --git a/test/e2e/helm.go b/test/e2e/helm.go index 43b6b274a..c1bd743a8 100644 --- a/test/e2e/helm.go +++ b/test/e2e/helm.go @@ -3,12 +3,10 @@ package e2e import ( - "bytes" + "net/http" "os" - "os/exec" "path" "path/filepath" - "strings" "testing" "time" ) @@ -33,88 +31,50 @@ func NewHelmContext(t *testing.T) *HelmContext { } } -func (h *HelmContext) MustRun(args ...string) *HelmCmd { - cmd := h.newCmd() - if status := cmd.exec(args...); status != nil { - h.t.Fatalf("helm %v failed unexpectedly: %v", args, status) +func (h *HelmContext) MustRun(args ...string) *Cmd { + cmd := h.newCmd(args...) + if status := cmd.exec(); status != nil { + h.t.Errorf("helm %v failed unexpectedly: %v", args, status) + h.t.Errorf("%s", cmd.Stderr()) + h.t.FailNow() } return cmd } -func (h *HelmContext) Run(args ...string) *HelmCmd { - cmd := h.newCmd() - cmd.exec(args...) +func (h *HelmContext) Run(args ...string) *Cmd { + cmd := h.newCmd(args...) + cmd.exec() return cmd } -func (h *HelmContext) RunFail(args ...string) *HelmCmd { - cmd := h.newCmd() - if status := cmd.exec(args...); status == nil { +func (h *HelmContext) RunFail(args ...string) *Cmd { + cmd := h.newCmd(args...) + if status := cmd.exec(); status == nil { h.t.Fatalf("helm unexpected to fail: %v", args, status) } return cmd } -func (h *HelmContext) newCmd() *HelmCmd { - return &HelmCmd{ - ctx: h, +func (h *HelmContext) newCmd(args ...string) *Cmd { + args = append([]string{"--host", h.Host}, args...) + return &Cmd{ + t: h.t, + path: h.Path, + args: args, } } -type HelmCmd struct { - ctx *HelmContext - path string - ran bool - status error - stdout, stderr bytes.Buffer -} +func (h *HelmContext) Running() bool { + endpoint := h.Host + "healthz" -func (h *HelmCmd) exec(args ...string) error { - args = append([]string{"--host", h.ctx.Host}, args...) - cmd := exec.Command(h.ctx.Path, args...) - h.stdout.Reset() - h.stderr.Reset() - cmd.Stdout = &h.stdout - cmd.Stderr = &h.stderr - h.status = cmd.Run() - if h.stdout.Len() > 0 { - h.ctx.t.Log("standard output:") - h.ctx.t.Log(h.stdout.String()) - } - if h.stderr.Len() > 0 { - h.ctx.t.Log("standard error:") - h.ctx.t.Log(h.stderr.String()) + resp, err := http.Get(endpoint) + if err != nil { + h.t.Errorf("Could not GET %s: %s", endpoint, err) } - h.ran = true - return h.status -} - -// Stdout returns standard output of the helmCmd run as a string. -func (h *HelmCmd) Stdout() string { - if !h.ran { - h.ctx.t.Fatal("internal testsuite error: stdout called before run") - } - return h.stdout.String() -} - -// Stderr returns standard error of the helmCmd run as a string. -func (h *HelmCmd) Stderr() string { - if !h.ran { - h.ctx.t.Fatal("internal testsuite error: stdout called before run") - } - return h.stderr.String() -} - -func (h *HelmCmd) StdoutContains(substring string) bool { - return strings.Contains(h.Stdout(), substring) -} - -func (h *HelmCmd) StderrContains(substring string) bool { - return strings.Contains(h.Stderr(), substring) -} + return resp.StatusCode == 200 -func (h *HelmCmd) Contains(substring string) bool { - return h.StdoutContains(substring) || h.StderrContains(substring) + //out := h.MustRun("server", "status").Stdout() + //return strings.Count(out, "Running") == 5 } func RepoRoot() string { diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go index 4ac389e98..b0b090dd0 100644 --- a/test/e2e/helm_test.go +++ b/test/e2e/helm_test.go @@ -7,7 +7,6 @@ import ( "fmt" "math/rand" "os" - "strings" "testing" "time" ) @@ -32,34 +31,43 @@ var ( managerImage = "quay.io/adamreese/manager:latest" ) +func logKubeEnv(k *KubeContext) { + config := k.Run("config", "view", "--flattend", "--minified").Stdout() + k.t.Logf("Kubernetes Environment\n%s", config) +} + func TestHelm(t *testing.T) { - kube := NewKubeContext() + kube := NewKubeContext(t) helm := NewHelmContext(t) - t.Logf("Kubenetes context: %s", kube.CurrentContext()) - t.Logf("Cluster: %s", kube.Cluster()) - t.Logf("Server: %s", kube.Server()) + logKubeEnv(kube) if !kube.Running() { t.Fatal("Not connected to kubernetes") } - t.Log(kube.Version()) - //TODO: skip check if running local binaries - if !helmRunning(helm) { - t.Error("Helm is not installed") - helm.MustRun("server", "install", "--resourcifier-image", resourcifierImage, "--expandybird-image", expandybirdImage, "--manager-image", managerImage) - //TODO: wait for pods to be ready - } - helm.Host = helmHost() - if helm.Host == "" { helm.Host = fmt.Sprintf("%s%s", kube.Server(), apiProxy) } t.Logf("Using host: %v", helm.Host) + //TODO: skip check if running local binaries + if !helm.Running() { + t.Error("Helm is not installed") + helm.MustRun( + "server", + "install", + "--resourcifier-image", resourcifierImage, + "--expandybird-image", expandybirdImage, + "--manager-image", managerImage, + ) + wait(func() bool { + return helm.Running() + }) + } + // Add repo if it does not exsit if !helm.MustRun("repo", "list").Contains(*repoURL) { t.Logf("Adding repo %s %s", *repoName, *repoURL) @@ -70,7 +78,19 @@ func TestHelm(t *testing.T) { deploymentName := genName() t.Log("Executing deploy") - helm.MustRun("deploy", "--properties", "container_port=6379,image=kubernetes/redis:v1,replicas=2", "--name", deploymentName, *chart) + helm.MustRun("deploy", + "--properties", "namespace=e2e", + "--name", deploymentName, + *chart, + ) + + err := wait(func() bool { + return kube.Run("get", "pods").Match("redis.*Running") + }) + if err != nil { + t.Fatal(err) + } + t.Log(kube.Run("get", "pods").Stdout()) t.Log("Executing deployment list") if !helm.MustRun("deployment", "list").Contains(deploymentName) { @@ -82,12 +102,27 @@ func TestHelm(t *testing.T) { t.Fatal("Could not deploy") } + t.Log("Executing deployment describe") + helm.MustRun("deployment", "describe", deploymentName) + t.Log("Executing deployment delete") if !helm.MustRun("deployment", "rm", deploymentName).Contains("Deleted") { t.Fatal("Could not delete deployment") } } +func wait(fn func() bool) error { + for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) { + if fn() { + continue + } + } + if !fn() { + return fmt.Errorf("Polling timeout") + } + return nil +} + func genName() string { return fmt.Sprintf("e2e-%d", rand.Uint32()) } @@ -98,8 +133,3 @@ func helmHost() string { } return os.Getenv("HELM_HOST") } - -func helmRunning(h *HelmContext) bool { - out := h.MustRun("server", "status").Stdout() - return strings.Count(out, "Running") == 5 -} diff --git a/test/e2e/kubernetes.go b/test/e2e/kubernetes.go index 8a697f488..a450e1f2c 100644 --- a/test/e2e/kubernetes.go +++ b/test/e2e/kubernetes.go @@ -3,43 +3,59 @@ package e2e import ( - "os/exec" "strings" + "testing" ) const defaultKubectlPath = "kubectl" type KubeContext struct { + t *testing.T Path string } -func NewKubeContext() *KubeContext { +func NewKubeContext(t *testing.T) *KubeContext { return &KubeContext{ + t: t, Path: defaultKubectlPath, } } +func (k *KubeContext) Run(args ...string) *Cmd { + cmd := k.newCmd(args...) + cmd.exec() + return cmd +} + +func (k *KubeContext) newCmd(args ...string) *Cmd { + return &Cmd{ + t: k.t, + path: k.Path, + args: args, + } +} + +func (k *KubeContext) getConfigValue(jsonpath string) string { + return strings.Replace(k.Run("config", "view", "--flatten=true", "--minify=true", "-o", "jsonpath="+jsonpath).Stdout(), "'", "", -1) +} + func (k *KubeContext) Cluster() string { - out, _ := exec.Command(k.Path, "config", "view", "--flatten=true", "--minify=true", "-o", "jsonpath='{.clusters[0].name}'").Output() - return string(out) + return k.getConfigValue("'{.clusters[0].name}'") } func (k *KubeContext) Server() string { - out, _ := exec.Command(k.Path, "config", "view", "--flatten=true", "--minify=true", "-o", "jsonpath='{.clusters[0].cluster.server}'").Output() - return strings.Replace(string(out), "'", "", -1) + return k.getConfigValue("'{.clusters[0].cluster.server}'") } func (k *KubeContext) CurrentContext() string { - out, _ := exec.Command(k.Path, "config", "view", "--flatten=true", "--minify=true", "-o", "jsonpath='{.current-context}'").Output() - return string(out) + return k.getConfigValue("'{.current-context}'") } func (k *KubeContext) Running() bool { - _, err := exec.Command(k.Path, "cluster-info").CombinedOutput() + err := k.Run("cluster-info").exec() return err == nil } func (k *KubeContext) Version() string { - out, _ := exec.Command(k.Path, "version").Output() - return string(out) + return k.Run("version").Stdout() }