Commit c5da63f5 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #16979 from bprashanth/IngressE2E

Auto commit by PR queue bot
parents 5e20dcfd eb4106fe
...@@ -62,6 +62,13 @@ var ( ...@@ -62,6 +62,13 @@ var (
expectedLBCreationTime = 7 * time.Minute expectedLBCreationTime = 7 * time.Minute
expectedLBHealthCheckTime = 7 * time.Minute expectedLBHealthCheckTime = 7 * time.Minute
// Name of the loadbalancer controller within the cluster addon
lbContainerName = "l7-lb-controller"
// If set, the test tries to perform an HTTP GET on each url endpoint of
// the Ingress. Only set to false to short-circuit test runs in debugging.
verifyHTTPGET = true
// On average it takes ~6 minutes for a single backend to come online. // On average it takes ~6 minutes for a single backend to come online.
// We *don't* expect this poll to consistently take 15 minutes for every // We *don't* expect this poll to consistently take 15 minutes for every
// Ingress as GCE is creating/checking backends in parallel, but at the // Ingress as GCE is creating/checking backends in parallel, but at the
...@@ -170,7 +177,9 @@ func createApp(c *client.Client, ns string, i int) { ...@@ -170,7 +177,9 @@ func createApp(c *client.Client, ns string, i int) {
// gcloudUnmarshal unmarshals json output of gcloud into given out interface. // gcloudUnmarshal unmarshals json output of gcloud into given out interface.
func gcloudUnmarshal(resource, regex string, out interface{}) { func gcloudUnmarshal(resource, regex string, out interface{}) {
output, err := exec.Command("gcloud", "compute", resource, "list", output, err := exec.Command("gcloud", "compute", resource, "list",
fmt.Sprintf("--regex=%v", regex), "-q", "--format=json").CombinedOutput() fmt.Sprintf("--regex=%v", regex),
fmt.Sprintf("--project=%v", testContext.CloudConfig.ProjectID),
"-q", "--format=json").CombinedOutput()
if err != nil { if err != nil {
Failf("Error unmarshalling gcloud output: %v", err) Failf("Error unmarshalling gcloud output: %v", err)
} }
...@@ -201,6 +210,25 @@ func checkLeakedResources() error { ...@@ -201,6 +210,25 @@ func checkLeakedResources() error {
return nil return nil
} }
// kubectlLogLBController logs kubectl debug output for the L7 controller pod.
func kubectlLogLBController(c *client.Client) {
selector := labels.SelectorFromSet(labels.Set(map[string]string{"name": "glbc"}))
podList, err := c.Pods(api.NamespaceAll).List(selector, fields.Everything())
if err != nil {
Logf("Cannot log L7 controller output, error listing pods %v", err)
return
}
if len(podList.Items) == 0 {
Logf("Loadbalancer controller pod not found")
return
}
for _, p := range podList.Items {
Logf("\nLast 100 log lines of %v\n", p.Name)
l, _ := runKubectl("logs", p.Name, fmt.Sprintf("--namespace=%v", api.NamespaceSystem), "-c", lbContainerName, "--tail=100")
Logf(l)
}
}
var _ = Describe("GCE L7 LoadBalancer Controller", func() { var _ = Describe("GCE L7 LoadBalancer Controller", func() {
// These variables are initialized after framework's beforeEach. // These variables are initialized after framework's beforeEach.
var ns string var ns string
...@@ -222,6 +250,12 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() { ...@@ -222,6 +250,12 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() {
}) })
AfterEach(func() { AfterEach(func() {
if CurrentGinkgoTestDescription().Failed {
kubectlLogLBController(client)
Logf("\nOutput of kubectl describe ing:\n")
desc, _ := runKubectl("describe", "ing", fmt.Sprintf("--namespace=%v", ns))
Logf(desc)
}
framework.afterEach() framework.afterEach()
err := wait.Poll(lbPollInterval, lbPollTimeout, func() (bool, error) { err := wait.Poll(lbPollInterval, lbPollTimeout, func() (bool, error) {
if err := checkLeakedResources(); err != nil { if err := checkLeakedResources(); err != nil {
...@@ -268,11 +302,18 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() { ...@@ -268,11 +302,18 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() {
// Wait for the loadbalancer IP. // Wait for the loadbalancer IP.
start := time.Now() start := time.Now()
address, err := waitForIngressAddress(client, ing.Namespace, ing.Name, lbPollTimeout) address, err := waitForIngressAddress(client, ing.Namespace, ing.Name, lbPollTimeout)
if err != nil {
Failf("Ingress failed to acquire an IP address within %v", lbPollTimeout)
}
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By(fmt.Sprintf("Found address %v for ingress %v, took %v to come online", By(fmt.Sprintf("Found address %v for ingress %v, took %v to come online",
address, ing.Name, time.Since(start))) address, ing.Name, time.Since(start)))
creationTimes = append(creationTimes, time.Since(start)) creationTimes = append(creationTimes, time.Since(start))
if !verifyHTTPGET {
continue
}
// Check that all rules respond to a simple GET. // Check that all rules respond to a simple GET.
for _, rules := range ing.Spec.Rules { for _, rules := range ing.Spec.Rules {
// As of Kubernetes 1.1 we only support HTTP Ingress. // As of Kubernetes 1.1 we only support HTTP Ingress.
...@@ -312,6 +353,9 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() { ...@@ -312,6 +353,9 @@ var _ = Describe("GCE L7 LoadBalancer Controller", func() {
if perc50 > expectedLBCreationTime { if perc50 > expectedLBCreationTime {
Failf("Average creation time is too high: %+v", creationTimes) Failf("Average creation time is too high: %+v", creationTimes)
} }
if !verifyHTTPGET {
return
}
sort.Sort(timeSlice(responseTimes)) sort.Sort(timeSlice(responseTimes))
perc50 = responseTimes[len(responseTimes)/2] perc50 = responseTimes[len(responseTimes)/2]
if perc50 > expectedLBHealthCheckTime { if perc50 > expectedLBHealthCheckTime {
......
...@@ -116,7 +116,7 @@ func runKubectlWithTimeout(timeout time.Duration, args ...string) string { ...@@ -116,7 +116,7 @@ func runKubectlWithTimeout(timeout time.Duration, args ...string) string {
logOutput := make(chan string) logOutput := make(chan string)
go func() { go func() {
defer GinkgoRecover() defer GinkgoRecover()
logOutput <- runKubectl(args...) logOutput <- runKubectlOrDie(args...)
}() }()
select { select {
case <-time.After(timeout): case <-time.After(timeout):
......
...@@ -926,14 +926,14 @@ func cleanup(filePath string, ns string, selectors ...string) { ...@@ -926,14 +926,14 @@ func cleanup(filePath string, ns string, selectors ...string) {
if ns != "" { if ns != "" {
nsArg = fmt.Sprintf("--namespace=%s", ns) nsArg = fmt.Sprintf("--namespace=%s", ns)
} }
runKubectl("stop", "--grace-period=0", "-f", filePath, nsArg) runKubectlOrDie("stop", "--grace-period=0", "-f", filePath, nsArg)
for _, selector := range selectors { for _, selector := range selectors {
resources := runKubectl("get", "rc,svc", "-l", selector, "--no-headers", nsArg) resources := runKubectlOrDie("get", "rc,svc", "-l", selector, "--no-headers", nsArg)
if resources != "" { if resources != "" {
Failf("Resources left running after stop:\n%s", resources) Failf("Resources left running after stop:\n%s", resources)
} }
pods := runKubectl("get", "pods", "-l", selector, nsArg, "-o", "go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}{{ \"\\n\" }}{{ end }}{{ end }}") pods := runKubectlOrDie("get", "pods", "-l", selector, nsArg, "-o", "go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}{{ \"\\n\" }}{{ end }}{{ end }}")
if pods != "" { if pods != "" {
Failf("Pods left unterminated after stop:\n%s", pods) Failf("Pods left unterminated after stop:\n%s", pods)
} }
...@@ -967,7 +967,7 @@ func validateController(c *client.Client, containerImage string, replicas int, c ...@@ -967,7 +967,7 @@ func validateController(c *client.Client, containerImage string, replicas int, c
By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector
waitLoop: waitLoop:
for start := time.Now(); time.Since(start) < podStartTimeout; time.Sleep(5 * time.Second) { for start := time.Now(); time.Since(start) < podStartTimeout; time.Sleep(5 * time.Second) {
getPodsOutput := runKubectl("get", "pods", "-o", "template", getPodsTemplate, "--api-version=v1", "-l", testname, fmt.Sprintf("--namespace=%v", ns)) getPodsOutput := runKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "--api-version=v1", "-l", testname, fmt.Sprintf("--namespace=%v", ns))
pods := strings.Fields(getPodsOutput) pods := strings.Fields(getPodsOutput)
if numPods := len(pods); numPods != replicas { if numPods := len(pods); numPods != replicas {
By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods)) By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods))
...@@ -975,13 +975,13 @@ waitLoop: ...@@ -975,13 +975,13 @@ waitLoop:
} }
var runningPods []string var runningPods []string
for _, podID := range pods { for _, podID := range pods {
running := runKubectl("get", "pods", podID, "-o", "template", getContainerStateTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns)) running := runKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns))
if running != "true" { if running != "true" {
Logf("%s is created but not running", podID) Logf("%s is created but not running", podID)
continue waitLoop continue waitLoop
} }
currentImage := runKubectl("get", "pods", podID, "-o", "template", getImageTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns)) currentImage := runKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns))
if currentImage != containerImage { if currentImage != containerImage {
Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage) Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage)
continue waitLoop continue waitLoop
...@@ -1062,29 +1062,39 @@ func (b kubectlBuilder) withStdinReader(reader io.Reader) *kubectlBuilder { ...@@ -1062,29 +1062,39 @@ func (b kubectlBuilder) withStdinReader(reader io.Reader) *kubectlBuilder {
return &b return &b
} }
func (b kubectlBuilder) exec() string { func (b kubectlBuilder) execOrDie() string {
str, err := b.exec()
Expect(err).NotTo(HaveOccurred())
return str
}
func (b kubectlBuilder) exec() (string, error) {
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd := b.cmd cmd := b.cmd
cmd.Stdout, cmd.Stderr = &stdout, &stderr cmd.Stdout, cmd.Stderr = &stdout, &stderr
Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
Failf("Error running %v:\nCommand stdout:\n%v\nstderr:\n%v\n", cmd, cmd.Stdout, cmd.Stderr) return "", fmt.Errorf("Error running %v:\nCommand stdout:\n%v\nstderr:\n%v\n", cmd, cmd.Stdout, cmd.Stderr)
return ""
} }
Logf(stdout.String()) Logf(stdout.String())
// TODO: trimspace should be unnecessary after switching to use kubectl binary directly // TODO: trimspace should be unnecessary after switching to use kubectl binary directly
return strings.TrimSpace(stdout.String()) return strings.TrimSpace(stdout.String()), nil
}
// runKubectlOrDie is a convenience wrapper over kubectlBuilder
func runKubectlOrDie(args ...string) string {
return newKubectlCommand(args...).execOrDie()
} }
// runKubectl is a convenience wrapper over kubectlBuilder // runKubectl is a convenience wrapper over kubectlBuilder
func runKubectl(args ...string) string { func runKubectl(args ...string) (string, error) {
return newKubectlCommand(args...).exec() return newKubectlCommand(args...).exec()
} }
// runKubectlInput is a convenience wrapper over kubectlBuilder that takes input to stdin // runKubectlOrDieInput is a convenience wrapper over kubectlBuilder that takes input to stdin
func runKubectlInput(data string, args ...string) string { func runKubectlOrDieInput(data string, args ...string) string {
return newKubectlCommand(args...).withStdinData(data).exec() return newKubectlCommand(args...).withStdinData(data).execOrDie()
} }
func startCmdAndStreamOutput(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) { func startCmdAndStreamOutput(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) {
...@@ -1973,7 +1983,7 @@ func NewHostExecPodSpec(ns, name string) *api.Pod { ...@@ -1973,7 +1983,7 @@ func NewHostExecPodSpec(ns, name string) *api.Pod {
// RunHostCmd runs the given cmd in the context of the given pod using `kubectl exec` // RunHostCmd runs the given cmd in the context of the given pod using `kubectl exec`
// inside of a shell. // inside of a shell.
func RunHostCmd(ns, name, cmd string) string { func RunHostCmd(ns, name, cmd string) string {
return runKubectl("exec", fmt.Sprintf("--namespace=%v", ns), name, "--", "/bin/sh", "-c", cmd) return runKubectlOrDie("exec", fmt.Sprintf("--namespace=%v", ns), name, "--", "/bin/sh", "-c", cmd)
} }
// LaunchHostExecPod launches a hostexec pod in the given namespace and waits // LaunchHostExecPod launches a hostexec pod in the given namespace and waits
......
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