diff --git a/test/e2e/framework.go b/test/e2e/framework.go new file mode 100644 index 00000000000..a5f8b0fec15 --- /dev/null +++ b/test/e2e/framework.go @@ -0,0 +1,96 @@ +/* +Copyright 2015 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 e2e + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you. +// Eventual goal is to merge this with integration test framework. +type Framework struct { + BaseName string + + Namespace *api.Namespace + Client *client.Client +} + +// NewFramework makes a new framework and sets up a BeforeEach/AfterEach for +// you (you can write additional before/after each functions). +func NewFramework(baseName string) *Framework { + f := &Framework{ + BaseName: baseName, + } + + BeforeEach(f.beforeEach) + AfterEach(f.afterEach) + + return f +} + +// beforeEach gets a client and makes a namespace. +func (f *Framework) beforeEach() { + By("Creating a kubernetes client") + c, err := loadClient() + Expect(err).NotTo(HaveOccurred()) + + f.Client = c + + By("Building a namespace api object") + namespace, err := createTestingNS(f.BaseName, f.Client) + Expect(err).NotTo(HaveOccurred()) + + f.Namespace = namespace +} + +// afterEach deletes the namespace, after reading its events. +func (f *Framework) afterEach() { + // Print events if the test failed. + if CurrentGinkgoTestDescription().Failed { + By(fmt.Sprintf("Collecting events from namespace %q.", f.Namespace.Name)) + events, err := f.Client.Events(f.Namespace.Name).List(labels.Everything(), fields.Everything()) + Expect(err).NotTo(HaveOccurred()) + + for _, e := range events.Items { + Logf("event for %v: %v %v: %v", e.InvolvedObject.Name, e.Source, e.Reason, e.Message) + } + // Note that we don't wait for any cleanup to propagate, which means + // that if you delete a bunch of pods right before ending your test, + // you may or may not see the killing/deletion/cleanup events. + } + + By(fmt.Sprintf("Destroying namespace %q for this suite.", f.Namespace.Name)) + if err := f.Client.Namespaces().Delete(f.Namespace.Name); err != nil { + Failf("Couldn't delete ns %q: %s", f.Namespace.Name, err) + } + // Paranoia-- prevent reuse! + f.Namespace = nil + f.Client = nil +} + +// WaitForPodRunning waits for the pod to run in the namespace. +func (f *Framework) WaitForPodRunning(podName string) error { + return waitForPodRunningInNamespace(f.Client, podName, f.Namespace.Name) +} diff --git a/test/e2e/networking.go b/test/e2e/networking.go index 7319d29d36b..b507eff7a24 100644 --- a/test/e2e/networking.go +++ b/test/e2e/networking.go @@ -19,10 +19,10 @@ package e2e import ( "fmt" "net/http" + "strings" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -31,20 +31,17 @@ import ( . "github.com/onsi/gomega" ) -func LaunchNetTestPodPerNode(nodes *api.NodeList, name string, c *client.Client, ns string) []string { +func LaunchNetTestPodPerNode(f *Framework, nodes *api.NodeList, name string) []string { podNames := []string{} totalPods := len(nodes.Items) Expect(totalPods).NotTo(Equal(0)) - for i, node := range nodes.Items { - podName := fmt.Sprintf("%s-%d", name, i) - podNames = append(podNames, podName) - Logf("Creating pod %s on node %s", podName, node.Name) - _, err := c.Pods(ns).Create(&api.Pod{ + for _, node := range nodes.Items { + pod, err := f.Client.Pods(f.Namespace.Name).Create(&api.Pod{ ObjectMeta: api.ObjectMeta{ - Name: podName, + GenerateName: name + "-", Labels: map[string]string{ "name": name, }, @@ -59,7 +56,7 @@ func LaunchNetTestPodPerNode(nodes *api.NodeList, name string, c *client.Client, //peers >= totalPods should be asserted by the container. //the nettest container finds peers by looking up list of svc endpoints. fmt.Sprintf("-peers=%d", totalPods), - "-namespace=" + ns}, + "-namespace=" + f.Namespace.Name}, Ports: []api.ContainerPort{{ContainerPort: 8080}}, }, }, @@ -68,16 +65,16 @@ func LaunchNetTestPodPerNode(nodes *api.NodeList, name string, c *client.Client, }, }) Expect(err).NotTo(HaveOccurred()) + Logf("Created pod %s on node %s", pod.ObjectMeta.Name, node.Name) + podNames = append(podNames, pod.ObjectMeta.Name) } return podNames } var _ = Describe("Networking", func() { + f := NewFramework("nettest") - //This namespace is modified throughout the course of the test. - var namespace *api.Namespace var svcname = "nettest" - var c *client.Client = nil BeforeEach(func() { //Assert basic external connectivity. @@ -91,22 +88,6 @@ var _ = Describe("Networking", func() { if resp.StatusCode != http.StatusOK { Failf("Unexpected error code, expected 200, got, %v (%v)", resp.StatusCode, resp) } - - By("Creating a kubernetes client") - c, err = loadClient() - Expect(err).NotTo(HaveOccurred()) - - By("Building a namespace api object") - namespace, err = createTestingNS("nettest", c) - Expect(err).NotTo(HaveOccurred()) - - }) - - AfterEach(func() { - By(fmt.Sprintf("Destroying namespace for this suite %v", namespace.Name)) - if err := c.Namespaces().Delete(namespace.Name); err != nil { - Failf("Couldn't delete ns %s", err) - } }) // First test because it has no dependencies on variables created later on. @@ -120,8 +101,8 @@ var _ = Describe("Networking", func() { } for _, test := range tests { By(fmt.Sprintf("testing: %s", test.path)) - data, err := c.RESTClient.Get(). - Namespace(namespace.Name). + data, err := f.Client.RESTClient.Get(). + Namespace(f.Namespace.Name). AbsPath(test.path). DoRaw() if err != nil { @@ -139,8 +120,8 @@ var _ = Describe("Networking", func() { return } - By(fmt.Sprintf("Creating a service named [%s] in namespace %s", svcname, namespace.Name)) - svc, err := c.Services(namespace.Name).Create(&api.Service{ + By(fmt.Sprintf("Creating a service named %q in namespace %q", svcname, f.Namespace.Name)) + svc, err := f.Client.Services(f.Namespace.Name).Create(&api.Service{ ObjectMeta: api.ObjectMeta{ Name: svcname, Labels: map[string]string{ @@ -166,26 +147,26 @@ var _ = Describe("Networking", func() { defer func() { defer GinkgoRecover() By("Cleaning up the service") - if err = c.Services(namespace.Name).Delete(svc.Name); err != nil { + if err = f.Client.Services(f.Namespace.Name).Delete(svc.Name); err != nil { Failf("unable to delete svc %v: %v", svc.Name, err) } }() By("Creating a webserver (pending) pod on each node") - nodes, err := c.Nodes().List(labels.Everything(), fields.Everything()) + nodes, err := f.Client.Nodes().List(labels.Everything(), fields.Everything()) if err != nil { Failf("Failed to list nodes: %v", err) } - podNames := LaunchNetTestPodPerNode(nodes, svcname, c, namespace.Name) + podNames := LaunchNetTestPodPerNode(f, nodes, svcname) // Clean up the pods defer func() { defer GinkgoRecover() By("Cleaning up the webserver pods") for _, podName := range podNames { - if err = c.Pods(namespace.Name).Delete(podName, nil); err != nil { + if err = f.Client.Pods(f.Namespace.Name).Delete(podName, nil); err != nil { Logf("Failed to delete pod %s: %v", podName, err) } } @@ -193,63 +174,67 @@ var _ = Describe("Networking", func() { By("Waiting for the webserver pods to transition to Running state") for _, podName := range podNames { - err = waitForPodRunningInNamespace(c, podName, namespace.Name) + err = f.WaitForPodRunning(podName) Expect(err).NotTo(HaveOccurred()) } By("Waiting for connectivity to be verified") - const maxAttempts = 60 passed := false //once response OK, evaluate response body for pass/fail. var body []byte + getDetails := func() ([]byte, error) { + return f.Client.Get(). + Namespace(f.Namespace.Name). + Prefix("proxy"). + Resource("services"). + Name(svc.Name). + Suffix("read"). + DoRaw() + } - for i := 0; i < maxAttempts && !passed; i++ { - time.Sleep(2 * time.Second) - Logf("About to make a proxy status call") - start := time.Now() - body, err = c.Get(). - Namespace(namespace.Name). + getStatus := func() ([]byte, error) { + return f.Client.Get(). + Namespace(f.Namespace.Name). Prefix("proxy"). Resource("services"). Name(svc.Name). Suffix("status"). DoRaw() + } + + for i := 0; !passed; i++ { // Timeout will keep us from going forever. + time.Sleep(2 * time.Second) + Logf("About to make a proxy status call") + start := time.Now() + body, err = getStatus() Logf("Proxy status call returned in %v", time.Since(start)) if err != nil { - Logf("Attempt %v/%v: service/pod still starting. (error: '%v')", i, maxAttempts, err) + Logf("Attempt %v: service/pod still starting. (error: '%v')", i, err) continue } - //Finally, we pass/fail the test based on if the container's response body, as to wether or not it was able to find peers. - switch string(body) { - case "pass": + // Finally, we pass/fail the test based on if the container's response body, as to wether or not it was able to find peers. + switch { + case string(body) == "pass": Logf("Passed on attempt %v. Cleaning up.", i) passed = true - case "running": - Logf("Attempt %v/%v: test still running", i, maxAttempts) - case "fail": - if body, err = c.Get(). - Namespace(namespace.Name).Prefix("proxy"). - Resource("services"). - Name(svc.Name).Suffix("read"). - DoRaw(); err != nil { + case string(body) == "running": + Logf("Attempt %v: test still running", i) + case string(body) == "fail": + if body, err = getDetails(); err != nil { Failf("Failed on attempt %v. Cleaning up. Error reading details: %v", i, err) } else { Failf("Failed on attempt %v. Cleaning up. Details:\n%s", i, string(body)) } + case strings.Contains(string(body), "no endpoints available"): + Logf("Attempt %v: waiting on service/endpoints", i) default: - Logf("Unexpected response: %q", body) + Logf("Unexpected response:\n%s", body) } } if !passed { - if body, err = c.Get(). - Namespace(namespace.Name). - Prefix("proxy"). - Resource("services"). - Name(svc.Name). - Suffix("read"). - DoRaw(); err != nil { + if body, err = getDetails(); err != nil { Failf("Timed out. Cleaning up. Error reading details: %v", err) } else { Failf("Timed out. Cleaning up. Details:\n%s", string(body)) diff --git a/test/e2e/reboot.go b/test/e2e/reboot.go index 8053f879632..da40a003dfc 100644 --- a/test/e2e/reboot.go +++ b/test/e2e/reboot.go @@ -55,8 +55,7 @@ const ( ) var _ = Describe("Reboot", func() { - - var c *client.Client = nil + var c *client.Client BeforeEach(func() { var err error diff --git a/test/e2e/ssh.go b/test/e2e/ssh.go index ec41cf067ba..effd6c82e66 100644 --- a/test/e2e/ssh.go +++ b/test/e2e/ssh.go @@ -27,8 +27,7 @@ import ( ) var _ = Describe("SSH", func() { - - var c *client.Client = nil + var c *client.Client BeforeEach(func() { var err error