diff --git a/cluster/gce/config-test.sh b/cluster/gce/config-test.sh index 99b73bf4ea9..907a095998b 100755 --- a/cluster/gce/config-test.sh +++ b/cluster/gce/config-test.sh @@ -49,6 +49,10 @@ ENABLE_NODE_MONITORING=true ENABLE_NODE_LOGGING=true LOGGING_DESTINATION=elasticsearch # options: elasticsearch, gcp +# Optional: When set to true, Elasticsearch and Kibana will be setup as part of the cluster bring up. +ENABLE_CLUSTER_LOGGING=false +ELASTICSEARCH_LOGGING_REPLICAS=1 + ENABLE_CLUSTER_MONITORING=false # Don't require https for registries in our local RFC1918 network diff --git a/cmd/e2e/e2e.go b/cmd/e2e/e2e.go index b6afc16a2b4..9c9141c85f3 100644 --- a/cmd/e2e/e2e.go +++ b/cmd/e2e/e2e.go @@ -17,24 +17,11 @@ limitations under the License. package main import ( - "fmt" - "io/ioutil" "os" - "path/filepath" goruntime "runtime" - "strconv" - "strings" - "time" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" - "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" - + "github.com/GoogleCloudPlatform/kubernetes/test/e2e" flag "github.com/spf13/pflag" ) @@ -45,705 +32,8 @@ var ( repoRoot = flag.String("repo_root", "./", "Root directory of kubernetes repository, for finding test files. Default assumes working directory is repository root") ) -func waitForPodRunning(c *client.Client, id string) { - for { - time.Sleep(5 * time.Second) - pod, err := c.Pods(api.NamespaceDefault).Get(id) - if err != nil { - glog.Warningf("Get pod failed: %v", err) - continue - } - if pod.Status.Phase == api.PodRunning { - break - } - glog.Infof("Waiting for pod status to be %q (found %q)", api.PodRunning, pod.Status.Phase) - } -} - -// waitForPodSuccess returns true if the pod reached state success, or false if it reached failure or ran too long. -func waitForPodSuccess(c *client.Client, podName string, contName string) bool { - for i := 0; i < 10; i++ { - if i > 0 { - time.Sleep(5 * time.Second) - } - pod, err := c.Pods(api.NamespaceDefault).Get(podName) - if err != nil { - glog.Warningf("Get pod failed: %v", err) - continue - } - // Cannot use pod.Status.Phase == api.PodSucceeded/api.PodFailed due to #2632 - ci, ok := pod.Status.Info[contName] - if !ok { - glog.Infof("No Status.Info for container %s in pod %s yet", contName, podName) - } else { - if ci.State.Termination != nil { - if ci.State.Termination.ExitCode == 0 { - glog.Infof("Saw pod success") - return true - } else { - glog.Infof("Saw pod failure: %+v", ci.State.Termination) - } - glog.Infof("Waiting for pod %q status to be success or failure", podName) - } else { - glog.Infof("Nil State.Termination for container %s in pod %s so far", contName, podName) - } - } - } - glog.Warningf("Gave up waiting for pod %q status to be success or failure", podName) - return false -} - -// assetPath returns a path to the requested file; safe on all -// OSes. NOTE: If you use an asset in this test, you MUST add it to -// the KUBE_TEST_PORTABLE array in hack/lib/golang.sh. -func assetPath(pathElements ...string) string { - return filepath.Join(*repoRoot, filepath.Join(pathElements...)) -} - -func loadObjectOrDie(filePath string) runtime.Object { - data, err := ioutil.ReadFile(filePath) - if err != nil { - glog.Fatalf("Failed to read object: %v", err) - } - return decodeObjectOrDie(data) -} - -func decodeObjectOrDie(data []byte) runtime.Object { - obj, err := latest.Codec.Decode(data) - if err != nil { - glog.Fatalf("Failed to decode object: %v", err) - } - return obj -} - -func loadPodOrDie(filePath string) *api.Pod { - obj := loadObjectOrDie(filePath) - pod, ok := obj.(*api.Pod) - if !ok { - glog.Fatalf("Failed to load pod: %v", obj) - } - return pod -} - -func loadClientOrDie() *client.Client { - config := client.Config{ - Host: *host, - } - info, err := clientauth.LoadFromFile(*authConfig) - if err != nil { - glog.Fatalf("Error loading auth: %v", err) - } - // If the certificate directory is provided, set the cert paths to be there. - if *certDir != "" { - glog.Infof("Expecting certs in %v.", *certDir) - info.CAFile = filepath.Join(*certDir, "ca.crt") - info.CertFile = filepath.Join(*certDir, "kubecfg.crt") - info.KeyFile = filepath.Join(*certDir, "kubecfg.key") - } - config, err = info.MergeWithConfig(config) - if err != nil { - glog.Fatalf("Error creating client") - } - c, err := client.New(&config) - if err != nil { - glog.Fatalf("Error creating client") - } - return c -} - -func parsePodOrDie(json string) *api.Pod { - obj := decodeObjectOrDie([]byte(json)) - pod, ok := obj.(*api.Pod) - if !ok { - glog.Fatalf("Failed to cast pod: %v", obj) - } - return pod -} - -func parseServiceOrDie(json string) *api.Service { - obj := decodeObjectOrDie([]byte(json)) - service, ok := obj.(*api.Service) - if !ok { - glog.Fatalf("Failed to cast service: %v", obj) - } - return service -} - -func TestKubernetesROService(c *client.Client) bool { - svc := api.ServiceList{} - err := c.Get(). - Namespace("default"). - AbsPath("/api/v1beta1/proxy/services/kubernetes-ro/api/v1beta1/services"). - Do(). - Into(&svc) - if err != nil { - glog.Errorf("unexpected error listing services using ro service: %v", err) - return false - } - var foundRW, foundRO bool - for i := range svc.Items { - if svc.Items[i].Name == "kubernetes" { - foundRW = true - } - if svc.Items[i].Name == "kubernetes-ro" { - foundRO = true - } - } - if !foundRW { - glog.Error("no RW service found") - } - if !foundRO { - glog.Error("no RO service found") - } - if !foundRW || !foundRO { - return false - } - return true -} - -func TestPodUpdate(c *client.Client) bool { - podClient := c.Pods(api.NamespaceDefault) - - pod := loadPodOrDie(assetPath("api", "examples", "pod.json")) - value := strconv.Itoa(time.Now().Nanosecond()) - pod.Labels["time"] = value - - _, err := podClient.Create(pod) - if err != nil { - glog.Errorf("Failed to create pod: %v", err) - return false - } - defer podClient.Delete(pod.Name) - waitForPodRunning(c, pod.Name) - pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) - if len(pods.Items) != 1 { - glog.Errorf("Failed to find the correct pod") - return false - } - - podOut, err := podClient.Get(pod.Name) - if err != nil { - glog.Errorf("Failed to get pod: %v", err) - return false - } - value = "time" + value - pod.Labels["time"] = value - pod.ResourceVersion = podOut.ResourceVersion - pod.UID = podOut.UID - pod, err = podClient.Update(pod) - if err != nil { - glog.Errorf("Failed to update pod: %v", err) - return false - } - waitForPodRunning(c, pod.Name) - pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) - if len(pods.Items) != 1 { - glog.Errorf("Failed to find the correct pod after update.") - return false - } - glog.Infof("pod update OK") - return true -} - -// TestImportantURLs validates that URLs that people depend on haven't moved. -// ***IMPORTANT*** Do *not* fix this test just by changing the path. If you moved a URL -// you can break upstream dependencies. -func TestImportantURLs(c *client.Client) bool { - tests := []struct { - path string - }{ - {path: "/validate"}, - {path: "/healthz"}, - // TODO: test proxy links here - } - ok := true - for _, test := range tests { - glog.Infof("testing: %s", test.path) - data, err := c.RESTClient.Get(). - AbsPath(test.path). - Do(). - Raw() - if err != nil { - glog.Errorf("Failed: %v\nBody: %s", err, string(data)) - ok = false - } - } - return ok -} - -// TestKubeletSendsEvent checks that kubelets and scheduler send events about pods scheduling and running. -func TestKubeletSendsEvent(c *client.Client) bool { - provider := os.Getenv("KUBERNETES_PROVIDER") - if len(provider) > 0 && provider != "gce" && provider != "gke" { - glog.Infof("skipping TestKubeletSendsEvent on cloud provider %s", provider) - return true - } - if provider == "" { - glog.Info("KUBERNETES_PROVIDER is unset; assuming \"gce\"") - } - - podClient := c.Pods(api.NamespaceDefault) - - pod := loadPodOrDie(assetPath("cmd", "e2e", "pod.json")) - value := strconv.Itoa(time.Now().Nanosecond()) - pod.Labels["time"] = value - - _, err := podClient.Create(pod) - if err != nil { - glog.Errorf("Failed to create pod: %v", err) - return false - } - defer podClient.Delete(pod.Name) - waitForPodRunning(c, pod.Name) - pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) - if len(pods.Items) != 1 { - glog.Errorf("Failed to find the correct pod") - return false - } - - podWithUid, err := podClient.Get(pod.Name) - if err != nil { - glog.Errorf("Failed to get pod: %v", err) - return false - } - - // Check for scheduler event about the pod. - glog.Infof("%+v", podWithUid) - events, err := c.Events(api.NamespaceDefault).List( - labels.Everything(), - labels.Set{ - "involvedObject.kind": "Pod", - "involvedObject.uid": string(podWithUid.UID), - "involvedObject.namespace": api.NamespaceDefault, - "source": "scheduler", - }.AsSelector(), - ) - if err != nil { - glog.Error("Error while listing events:", err) - return false - } - if len(events.Items) == 0 { - glog.Error("Didn't see any scheduler events even though pod was running.") - return false - } - glog.Info("Saw scheduler event for our pod.") - - // Check for kubelet event about the pod. - events, err = c.Events(api.NamespaceDefault).List( - labels.Everything(), - labels.Set{ - "involvedObject.uid": string(podWithUid.UID), - "involvedObject.kind": "BoundPod", - "involvedObject.namespace": api.NamespaceDefault, - "source": "kubelet", - }.AsSelector(), - ) - if err != nil { - glog.Error("Error while listing events:", err) - return false - } - if len(events.Items) == 0 { - glog.Error("Didn't see any kubelet events even though pod was running.") - return false - } - glog.Info("Saw kubelet event for our pod.") - return true -} - -func TestNetwork(c *client.Client) bool { - ns := api.NamespaceDefault - svc, err := c.Services(ns).Create(loadObjectOrDie(assetPath( - "contrib", "for-tests", "network-tester", "service.json", - )).(*api.Service)) - if err != nil { - glog.Errorf("unable to create test service: %v", err) - return false - } - // Clean up service - defer func() { - if err = c.Services(ns).Delete(svc.Name); err != nil { - glog.Errorf("unable to delete svc %v: %v", svc.Name, err) - } - }() - - rc, err := c.ReplicationControllers(ns).Create(loadObjectOrDie(assetPath( - "contrib", "for-tests", "network-tester", "rc.json", - )).(*api.ReplicationController)) - if err != nil { - glog.Errorf("unable to create test rc: %v", err) - return false - } - // Clean up rc - defer func() { - rc.Spec.Replicas = 0 - rc, err = c.ReplicationControllers(ns).Update(rc) - if err != nil { - glog.Errorf("unable to modify replica count for rc %v: %v", rc.Name, err) - return - } - if err = c.ReplicationControllers(ns).Delete(rc.Name); err != nil { - glog.Errorf("unable to delete rc %v: %v", rc.Name, err) - } - }() - - const maxAttempts = 60 - for i := 0; i < maxAttempts; i++ { - time.Sleep(time.Second) - body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("status").Do().Raw() - if err != nil { - glog.Infof("Attempt %v/%v: service/pod still starting. (error: '%v')", i, maxAttempts, err) - continue - } - switch string(body) { - case "pass": - glog.Infof("Passed on attempt %v. Cleaning up.", i) - return true - case "running": - glog.Infof("Attempt %v/%v: test still running", i, maxAttempts) - case "fail": - if body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("read").Do().Raw(); err != nil { - glog.Infof("Failed on attempt %v. Cleaning up. Error reading details: %v", i, err) - } else { - glog.Infof("Failed on attempt %v. Cleaning up. Details:\n%v", i, string(body)) - } - return false - } - } - - if body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("read").Do().Raw(); err != nil { - glog.Infof("Timed out. Cleaning up. Error reading details: %v", err) - } else { - glog.Infof("Timed out. Cleaning up. Details:\n%v", string(body)) - } - - return false -} - -type TestSpec struct { - // The test to run - test func(c *client.Client) bool - // The human readable name of this test - name string - // The id for this test. It should be constant for the life of the test. - id int -} - -type TestInfo struct { - passed bool - spec TestSpec -} - -// Output a summary in the TAP (test anything protocol) format for automated processing. -// See http://testanything.org/ for more info -func outputTAPSummary(infoList []TestInfo) { - glog.Infof("1..%d", len(infoList)) - for _, info := range infoList { - if info.passed { - glog.Infof("ok %d - %s", info.spec.id, info.spec.name) - } else { - glog.Infof("not ok %d - %s", info.spec.id, info.spec.name) - } - } -} - -// TestClusterDNS checks that cluster DNS works. -func TestClusterDNS(c *client.Client) bool { - // TODO: - // https://github.com/GoogleCloudPlatform/kubernetes/issues/3305 - // (but even if it's fixed, this will need a version check for - // skewed version tests) - if os.Getenv("KUBERNETES_PROVIDER") == "gke" { - glog.Infof("skipping TestClusterDNS on gke") - return true - } - - podClient := c.Pods(api.NamespaceDefault) - - //TODO: Wait for skyDNS - - // All the names we need to be able to resolve. - namesToResolve := []string{ - "kubernetes-ro", - "kubernetes-ro.default", - "kubernetes-ro.default.kubernetes.local", - "google.com", - } - - probeCmd := "for i in `seq 1 600`; do " - for _, name := range namesToResolve { - probeCmd += fmt.Sprintf("wget -O /dev/null %s && echo OK > /results/%s;", name, name) - } - probeCmd += "sleep 1; done" - - // Run a pod which probes DNS and exposes the results by HTTP. - pod := &api.Pod{ - TypeMeta: api.TypeMeta{ - Kind: "Pod", - APIVersion: "v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: "dns-test", - }, - Spec: api.PodSpec{ - Volumes: []api.Volume{ - { - Name: "results", - Source: &api.VolumeSource{ - EmptyDir: &api.EmptyDir{}, - }, - }, - }, - Containers: []api.Container{ - { - Name: "webserver", - Image: "kubernetes/test-webserver", - VolumeMounts: []api.VolumeMount{ - { - Name: "results", - MountPath: "/results", - }, - }, - }, - { - Name: "pinger", - Image: "busybox", - Command: []string{"sh", "-c", probeCmd}, - VolumeMounts: []api.VolumeMount{ - { - Name: "results", - MountPath: "/results", - }, - }, - }, - }, - }, - } - _, err := podClient.Create(pod) - if err != nil { - glog.Errorf("Failed to create dns-test pod: %v", err) - return false - } - defer podClient.Delete(pod.Name) - - waitForPodRunning(c, pod.Name) - pod, err = podClient.Get(pod.Name) - if err != nil { - glog.Errorf("Failed to get pod: %v", err) - return false - } - - // Try to find results for each expected name. - var failed []string - for try := 1; try < 100; try++ { - failed = []string{} - for _, name := range namesToResolve { - _, err := c.Get(). - Prefix("proxy"). - Resource("pods"). - Namespace("default"). - Name(pod.Name). - Suffix("results", name). - Do().Raw() - if err != nil { - failed = append(failed, name) - } - } - if len(failed) == 0 { - break - } - glog.Infof("lookups failed for: %v", failed) - time.Sleep(3 * time.Second) - } - if len(failed) != 0 { - glog.Errorf("DNS failed for: %v", failed) - return false - } - - // TODO: probe from the host, too. - - glog.Info("DNS probes succeeded") - return true -} - -// TestPodHasServiceEnvVars checks that kubelets and scheduler send events about pods scheduling and running. -func TestPodHasServiceEnvVars(c *client.Client) bool { - // Make a pod that will be a service. - // This pod serves its hostname via HTTP. - serverPod := parsePodOrDie(`{ - "kind": "Pod", - "apiVersion": "v1beta1", - "id": "srv", - "desiredState": { - "manifest": { - "version": "v1beta1", - "id": "srv", - "containers": [{ - "name": "srv", - "image": "kubernetes/serve_hostname", - "ports": [{ - "containerPort": 80, - "hostPort": 8080 - }] - }] - } - }, - "labels": { - "name": "srv" - } - }`) - _, err := c.Pods(api.NamespaceDefault).Create(serverPod) - if err != nil { - glog.Errorf("Failed to create serverPod: %v", err) - return false - } - defer c.Pods(api.NamespaceDefault).Delete(serverPod.Name) - waitForPodRunning(c, serverPod.Name) - - // This service exposes pod p's port 8080 as a service on port 8765 - svc := parseServiceOrDie(`{ - "id": "fooservice", - "kind": "Service", - "apiVersion": "v1beta1", - "port": 8765, - "containerPort": 8080, - "selector": { - "name": "p" - } - }`) - if err != nil { - glog.Errorf("Failed to delete service: %v", err) - return false - } - time.Sleep(2) - _, err = c.Services(api.NamespaceDefault).Create(svc) - if err != nil { - glog.Errorf("Failed to create service: %v", err) - return false - } - defer c.Services(api.NamespaceDefault).Delete(svc.Name) - // TODO: we don't have a way to wait for a service to be "running". - // If this proves flaky, then we will need to retry the clientPod or insert a sleep. - - // Make a client pod that verifies that it has the service environment variables. - clientPod := parsePodOrDie(`{ - "apiVersion": "v1beta1", - "kind": "Pod", - "id": "env3", - "desiredState": { - "manifest": { - "version": "v1beta1", - "id": "env3", - "restartPolicy": { "never": {} }, - "containers": [{ - "name": "env3cont", - "image": "busybox", - "command": ["sh", "-c", "env"] - }] - } - }, - "labels": { "name": "env3" } - }`) - _, err = c.Pods(api.NamespaceDefault).Create(clientPod) - if err != nil { - glog.Errorf("Failed to create pod: %v", err) - return false - } - defer c.Pods(api.NamespaceDefault).Delete(clientPod.Name) - - // Wait for client pod to complete. - success := waitForPodSuccess(c, clientPod.Name, clientPod.Spec.Containers[0].Name) - if !success { - glog.Errorf("Failed to run client pod to detect service env vars.") - } - - // Grab its logs. Get host first. - clientPodStatus, err := c.Pods(api.NamespaceDefault).Get(clientPod.Name) - if err != nil { - glog.Errorf("Failed to get clientPod to know host: %v", err) - return false - } - glog.Infof("Trying to get logs from host %s pod %s container %s: %v", - clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, err) - logs, err := c.Get(). - Prefix("proxy"). - Resource("minions"). - Name(clientPodStatus.Status.Host). - Suffix("containerLogs", api.NamespaceDefault, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name). - Do(). - Raw() - if err != nil { - glog.Errorf("Failed to get logs from host %s pod %s container %s: %v", - clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, err) - return false - } - glog.Info("clientPod logs:", string(logs)) - - toFind := []string{ - "FOOSERVICE_SERVICE_HOST=", - "FOOSERVICE_SERVICE_PORT=", - "FOOSERVICE_PORT=", - "FOOSERVICE_PORT_8765_TCP_PORT=", - "FOOSERVICE_PORT_8765_TCP_PROTO=", - "FOOSERVICE_PORT_8765_TCP=", - "FOOSERVICE_PORT_8765_TCP_ADDR=", - } - - for _, m := range toFind { - if !strings.Contains(string(logs), m) { - glog.Errorf("Unable to find env var %q in client env vars.", m) - success = false - } - } - - // We could try a wget the service from the client pod. But services.sh e2e test covers that pretty well. - return success -} - func main() { util.InitFlags() goruntime.GOMAXPROCS(goruntime.NumCPU()) - util.ReallyCrash = true - util.InitLogs() - defer util.FlushLogs() - - go func() { - defer util.FlushLogs() - time.Sleep(5 * time.Minute) - glog.Fatalf("This test has timed out. Cleanup not guaranteed.") - }() - - c := loadClientOrDie() - - // Define the tests. Important: for a clean test grid, please keep ids for a test constant. - tests := []TestSpec{ - {TestKubernetesROService, "TestKubernetesROService", 1}, - {TestKubeletSendsEvent, "TestKubeletSendsEvent", 2}, - {TestImportantURLs, "TestImportantURLs", 3}, - {TestPodUpdate, "TestPodUpdate", 4}, - {TestNetwork, "TestNetwork", 5}, - {TestClusterDNS, "TestClusterDNS", 6}, - {TestPodHasServiceEnvVars, "TestPodHasServiceEnvVars", 7}, - } - - info := []TestInfo{} - passed := true - for i, test := range tests { - glog.Infof("Running test %d", i+1) - testPassed := test.test(c) - if !testPassed { - glog.Infof(" test %d failed", i+1) - passed = false - } else { - glog.Infof(" test %d passed", i+1) - } - // TODO: clean up objects created during a test after the test, so cases - // are independent. - info = append(info, TestInfo{testPassed, test}) - } - outputTAPSummary(info) - if !passed { - glog.Fatalf("At least one test failed") - } else { - glog.Infof("All tests pass") - } + e2e.RunE2ETests(*authConfig, *certDir, *host, *repoRoot) } diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index ec96cbc2205..d7a9bbb7486 100644 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -52,7 +52,7 @@ readonly KUBE_TEST_BINARIES=("${KUBE_TEST_TARGETS[@]##*/}") readonly KUBE_TEST_BINARIES_WIN=("${KUBE_TEST_BINARIES[@]/%/.exe}") readonly KUBE_TEST_PORTABLE=( api/examples/pod.json - cmd/e2e/pod.json + test/e2e/pod.json contrib/for-tests/network-tester/rc.json contrib/for-tests/network-tester/service.json hack/e2e.go diff --git a/test/e2e/basic.go b/test/e2e/basic.go new file mode 100644 index 00000000000..9d6fe36be64 --- /dev/null +++ b/test/e2e/basic.go @@ -0,0 +1,154 @@ +/* +Copyright 2014 Google Inc. 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" + "io/ioutil" + "net/http" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/golang/glog" +) + +func TestBasic(c *client.Client) bool { + ns := api.NamespaceDefault + name := "my-hostname" + replicas := 2 + + // Attmept to delete a controller that might have been + // left lying around from a previously aborted test. + controllers, err := c.ReplicationControllers(ns).List(labels.Everything()) + if err != nil { + glog.Infof("Failed to list replication controllers: %v", err) + return false + } + for _, cnt := range controllers.Items { + if cnt.Name == name { + glog.Infof("Found a straggler %s controller", name) + // Delete any pods controlled by this replicaiton controller. + cnt.Spec.Replicas = 0 + c.ReplicationControllers(ns).Update(&cnt) + // Delete the controller + c.ReplicationControllers(ns).Delete(name) + break + } + } + + // Create a replication controller for a service + // that serves its hostname on port 8080. + // The source for the Docker containter kubernetes/serve_hostname is + // in contrib/for-demos/serve_hostname + controller, err := c.ReplicationControllers(ns).Create(&api.ReplicationController{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.ReplicationControllerSpec{ + Replicas: replicas, + Selector: map[string]string{ + "name": name, + }, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"name": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: "kubernetes/serve_hostname", + Ports: []api.Port{{ContainerPort: 9376, HostPort: 8080}}, + }, + }, + }, + }, + }, + }) + if err != nil { + glog.Infof("Failed to create replication controller for %s: %v", name, err) + return false + } + + // List the pods. + // pods, err := c.Pods(ns).List(labels.Set{"name": name}.AsSelector()) + pods, err := c.Pods(ns).List(labels.SelectorFromSet(labels.Set(map[string]string{"name": name}))) + if err != nil { + glog.Errorf("Failed to list pods before wait for running check: %v", err) + return false + } + for i, pod := range pods.Items { + glog.Infof("Replica %d: %s\n", i+1, pod.Name) + } + + // Wait for the pods to enter the running state. Waiting loops until the pods + // are running so non-running pods cause a timeout for this test. + for _, pod := range pods.Items { + waitForPodRunning(c, pod.Name) + } + + // List the pods again to get the host IP information. + pods, err = c.Pods(ns).List(labels.SelectorFromSet(labels.Set(map[string]string{"name": name}))) + if err != nil { + glog.Errorf("Failed to list pods after wait for running check: %v", err) + return false + } + + // Verify that something is listening. + for i, pod := range pods.Items { + glog.Infof("Pod %s hostIP %s", pod.Name, pod.Status.HostIP) + resp, err := http.Get(fmt.Sprintf("http://%s:8080", pod.Status.HostIP)) + if err != nil { + glog.Errorf("Failed to GET from replica %d: %v", i+1, err) + return false + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + glog.Errorf("Expected OK status code for replica %d but got %d", i+1, resp.StatusCode) + return false + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + glog.Errorf("Failed to read the body of the GET response from replica %d: %v", i+1, err) + return false + } + // The body should be the pod name although we may need to skip a newline + // character at the end of the response. + if !strings.HasPrefix(string(body), pod.Name) { + glog.Errorf("From replica %d expected response %s but got %s", i+1, pod.Name, string(body)) + return false + } + glog.Infof("Got expected result from replica %d: %s", i+1, string(body)) + } + + // Resize the replication controller to zero to get rid of pods. + controller.Spec.Replicas = 0 + if _, err = c.ReplicationControllers(ns).Update(controller); err != nil { + glog.Errorf("Failed to resize replication controllert to zero: %v", err) + return false + } + + // Delete the replication controller. + if err = c.ReplicationControllers(ns).Delete(name); err != nil { + glog.Errorf("Failed to delete replication controller %s: %v", name, err) + return false + } + + return true +} diff --git a/test/e2e/cluster_dns.go b/test/e2e/cluster_dns.go new file mode 100644 index 00000000000..084afed86bc --- /dev/null +++ b/test/e2e/cluster_dns.go @@ -0,0 +1,146 @@ +/* +Copyright 2014 Google Inc. 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" + "os" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/golang/glog" +) + +// TestClusterDNS checks that cluster DNS works. +func TestClusterDNS(c *client.Client) bool { + // TODO: + // https://github.com/GoogleCloudPlatform/kubernetes/issues/3305 + // (but even if it's fixed, this will need a version check for + // skewed version tests) + if os.Getenv("KUBERNETES_PROVIDER") == "gke" { + glog.Infof("skipping TestClusterDNS on gke") + return true + } + + podClient := c.Pods(api.NamespaceDefault) + + //TODO: Wait for skyDNS + + // All the names we need to be able to resolve. + namesToResolve := []string{ + "kubernetes-ro", + "kubernetes-ro.default", + "kubernetes-ro.default.kubernetes.local", + "google.com", + } + + probeCmd := "for i in `seq 1 600`; do " + for _, name := range namesToResolve { + probeCmd += fmt.Sprintf("wget -O /dev/null %s && echo OK > /results/%s;", name, name) + } + probeCmd += "sleep 1; done" + + // Run a pod which probes DNS and exposes the results by HTTP. + pod := &api.Pod{ + TypeMeta: api.TypeMeta{ + Kind: "Pod", + APIVersion: "v1beta1", + }, + ObjectMeta: api.ObjectMeta{ + Name: "dns-test", + }, + Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "results", + Source: &api.VolumeSource{ + EmptyDir: &api.EmptyDir{}, + }, + }, + }, + Containers: []api.Container{ + { + Name: "webserver", + Image: "kubernetes/test-webserver", + VolumeMounts: []api.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + { + Name: "pinger", + Image: "busybox", + Command: []string{"sh", "-c", probeCmd}, + VolumeMounts: []api.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + }, + }, + } + _, err := podClient.Create(pod) + if err != nil { + glog.Errorf("Failed to create dns-test pod: %v", err) + return false + } + defer podClient.Delete(pod.Name) + + waitForPodRunning(c, pod.Name) + pod, err = podClient.Get(pod.Name) + if err != nil { + glog.Errorf("Failed to get pod: %v", err) + return false + } + + // Try to find results for each expected name. + var failed []string + for try := 1; try < 100; try++ { + failed = []string{} + for _, name := range namesToResolve { + _, err := c.Get(). + Prefix("proxy"). + Resource("pods"). + Namespace("default"). + Name(pod.Name). + Suffix("results", name). + Do().Raw() + if err != nil { + failed = append(failed, name) + } + } + if len(failed) == 0 { + break + } + glog.Infof("lookups failed for: %v", failed) + time.Sleep(3 * time.Second) + } + if len(failed) != 0 { + glog.Errorf("DNS failed for: %v", failed) + return false + } + + // TODO: probe from the host, too. + + glog.Info("DNS probes succeeded") + return true +} diff --git a/test/e2e/driver.go b/test/e2e/driver.go new file mode 100644 index 00000000000..8b4c91011af --- /dev/null +++ b/test/e2e/driver.go @@ -0,0 +1,101 @@ +/* +Copyright 2014 Google Inc. 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 ( + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/golang/glog" +) + +type testSpec struct { + // The test to run + test func(c *client.Client) bool + // The human readable name of this test + name string + // The id for this test. It should be constant for the life of the test. + id int +} + +type testInfo struct { + passed bool + spec testSpec +} + +// Output a summary in the TAP (test anything protocol) format for automated processing. +// See http://testanything.org/ for more info +func outputTAPSummary(infoList []testInfo) { + glog.Infof("1..%d", len(infoList)) + for _, info := range infoList { + if info.passed { + glog.Infof("ok %d - %s", info.spec.id, info.spec.name) + } else { + glog.Infof("not ok %d - %s", info.spec.id, info.spec.name) + } + } +} + +func RunE2ETests(authConfig, certDir, host, repoRoot string) { + testContext = testContextType{authConfig, certDir, host, repoRoot} + util.ReallyCrash = true + util.InitLogs() + defer util.FlushLogs() + + go func() { + defer util.FlushLogs() + time.Sleep(5 * time.Minute) + glog.Fatalf("This test has timed out. Cleanup not guaranteed.") + }() + + c := loadClientOrDie() + + // Define the tests. Important: for a clean test grid, please keep ids for a test constant. + tests := []testSpec{ + {TestKubernetesROService, "TestKubernetesROService", 1}, + {TestKubeletSendsEvent, "TestKubeletSendsEvent", 2}, + {TestImportantURLs, "TestImportantURLs", 3}, + {TestPodUpdate, "TestPodUpdate", 4}, + {TestNetwork, "TestNetwork", 5}, + {TestClusterDNS, "TestClusterDNS", 6}, + {TestPodHasServiceEnvVars, "TestPodHasServiceEnvVars", 7}, + {TestBasic, "TestBasic", 8}, + } + + info := []testInfo{} + passed := true + for i, test := range tests { + glog.Infof("Running test %d %s", i+1, test.name) + testPassed := test.test(c) + if !testPassed { + glog.Infof(" test %d failed", i+1) + passed = false + } else { + glog.Infof(" test %d passed", i+1) + } + // TODO: clean up objects created during a test after the test, so cases + // are independent. + info = append(info, testInfo{testPassed, test}) + } + outputTAPSummary(info) + if !passed { + glog.Fatalf("At least one test failed") + } else { + glog.Infof("All tests pass") + } +} diff --git a/test/e2e/important_urls.go b/test/e2e/important_urls.go new file mode 100644 index 00000000000..c8399aaa6cc --- /dev/null +++ b/test/e2e/important_urls.go @@ -0,0 +1,48 @@ +/* +Copyright 2014 Google Inc. 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 ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/golang/glog" +) + +// TestImportantURLs validates that URLs that people depend on haven't moved. +// ***IMPORTANT*** Do *not* fix this test just by changing the path. If you moved a URL +// you can break upstream dependencies. +func TestImportantURLs(c *client.Client) bool { + tests := []struct { + path string + }{ + {path: "/validate"}, + {path: "/healthz"}, + // TODO: test proxy links here + } + ok := true + for _, test := range tests { + glog.Infof("testing: %s", test.path) + data, err := c.RESTClient.Get(). + AbsPath(test.path). + Do(). + Raw() + if err != nil { + glog.Errorf("Failed: %v\nBody: %s", err, string(data)) + ok = false + } + } + return ok +} diff --git a/test/e2e/kubelet_sends_events.go b/test/e2e/kubelet_sends_events.go new file mode 100644 index 00000000000..7ee662c89fc --- /dev/null +++ b/test/e2e/kubelet_sends_events.go @@ -0,0 +1,107 @@ +/* +Copyright 2014 Google Inc. 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 ( + "os" + "strconv" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/golang/glog" +) + +// TestKubeletSendsEvent checks that kubelets and scheduler send events about pods scheduling and running. +func TestKubeletSendsEvent(c *client.Client) bool { + provider := os.Getenv("KUBERNETES_PROVIDER") + if len(provider) > 0 && provider != "gce" && provider != "gke" { + glog.Infof("skipping TestKubeletSendsEvent on cloud provider %s", provider) + return true + } + if provider == "" { + glog.Info("KUBERNETES_PROVIDER is unset; assuming \"gce\"") + } + + podClient := c.Pods(api.NamespaceDefault) + + pod := loadPodOrDie(assetPath("test", "e2e", "pod.json")) + value := strconv.Itoa(time.Now().Nanosecond()) + pod.Labels["time"] = value + + _, err := podClient.Create(pod) + if err != nil { + glog.Errorf("Failed to create pod: %v", err) + return false + } + defer podClient.Delete(pod.Name) + waitForPodRunning(c, pod.Name) + pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + if len(pods.Items) != 1 { + glog.Errorf("Failed to find the correct pod") + return false + } + + podWithUid, err := podClient.Get(pod.Name) + if err != nil { + glog.Errorf("Failed to get pod: %v", err) + return false + } + + // Check for scheduler event about the pod. + glog.Infof("%+v", podWithUid) + events, err := c.Events(api.NamespaceDefault).List( + labels.Everything(), + labels.Set{ + "involvedObject.kind": "Pod", + "involvedObject.uid": string(podWithUid.UID), + "involvedObject.namespace": api.NamespaceDefault, + "source": "scheduler", + }.AsSelector(), + ) + if err != nil { + glog.Error("Error while listing events:", err) + return false + } + if len(events.Items) == 0 { + glog.Error("Didn't see any scheduler events even though pod was running.") + return false + } + glog.Info("Saw scheduler event for our pod.") + + // Check for kubelet event about the pod. + events, err = c.Events(api.NamespaceDefault).List( + labels.Everything(), + labels.Set{ + "involvedObject.uid": string(podWithUid.UID), + "involvedObject.kind": "BoundPod", + "involvedObject.namespace": api.NamespaceDefault, + "source": "kubelet", + }.AsSelector(), + ) + if err != nil { + glog.Error("Error while listing events:", err) + return false + } + if len(events.Items) == 0 { + glog.Error("Didn't see any kubelet events even though pod was running.") + return false + } + glog.Info("Saw kubelet event for our pod.") + return true +} diff --git a/test/e2e/network.go b/test/e2e/network.go new file mode 100644 index 00000000000..dec1a0df846 --- /dev/null +++ b/test/e2e/network.go @@ -0,0 +1,93 @@ +/* +Copyright 2014 Google Inc. 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 ( + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/golang/glog" +) + +func TestNetwork(c *client.Client) bool { + ns := api.NamespaceDefault + svc, err := c.Services(ns).Create(loadObjectOrDie(assetPath( + "contrib", "for-tests", "network-tester", "service.json", + )).(*api.Service)) + if err != nil { + glog.Errorf("unable to create test service: %v", err) + return false + } + // Clean up service + defer func() { + if err = c.Services(ns).Delete(svc.Name); err != nil { + glog.Errorf("unable to delete svc %v: %v", svc.Name, err) + } + }() + + rc, err := c.ReplicationControllers(ns).Create(loadObjectOrDie(assetPath( + "contrib", "for-tests", "network-tester", "rc.json", + )).(*api.ReplicationController)) + if err != nil { + glog.Errorf("unable to create test rc: %v", err) + return false + } + // Clean up rc + defer func() { + rc.Spec.Replicas = 0 + rc, err = c.ReplicationControllers(ns).Update(rc) + if err != nil { + glog.Errorf("unable to modify replica count for rc %v: %v", rc.Name, err) + return + } + if err = c.ReplicationControllers(ns).Delete(rc.Name); err != nil { + glog.Errorf("unable to delete rc %v: %v", rc.Name, err) + } + }() + const maxAttempts = 60 + for i := 0; i < maxAttempts; i++ { + time.Sleep(time.Second) + body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("status").Do().Raw() + if err != nil { + glog.Infof("Attempt %v/%v: service/pod still starting. (error: '%v')", i, maxAttempts, err) + continue + } + switch string(body) { + case "pass": + glog.Infof("Passed on attempt %v. Cleaning up.", i) + return true + case "running": + glog.Infof("Attempt %v/%v: test still running", i, maxAttempts) + case "fail": + if body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("read").Do().Raw(); err != nil { + glog.Infof("Failed on attempt %v. Cleaning up. Error reading details: %v", i, err) + } else { + glog.Infof("Failed on attempt %v. Cleaning up. Details:\n%v", i, string(body)) + } + return false + } + } + + if body, err := c.Get().Prefix("proxy").Resource("services").Name(svc.Name).Suffix("read").Do().Raw(); err != nil { + glog.Infof("Timed out. Cleaning up. Error reading details: %v", err) + } else { + glog.Infof("Timed out. Cleaning up. Details:\n%v", string(body)) + } + + return false +} diff --git a/cmd/e2e/pod.json b/test/e2e/pod.json similarity index 100% rename from cmd/e2e/pod.json rename to test/e2e/pod.json diff --git a/test/e2e/pod_has_service_env_vars.go b/test/e2e/pod_has_service_env_vars.go new file mode 100644 index 00000000000..3c8fb5108c8 --- /dev/null +++ b/test/e2e/pod_has_service_env_vars.go @@ -0,0 +1,161 @@ +/* +Copyright 2014 Google Inc. 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 ( + "strings" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/golang/glog" +) + +// TestPodHasServiceEnvVars checks that kubelets and scheduler send events about pods scheduling and running. +func TestPodHasServiceEnvVars(c *client.Client) bool { + // Make a pod that will be a service. + // This pod serves its hostname via HTTP. + serverPod := parsePodOrDie(`{ + "kind": "Pod", + "apiVersion": "v1beta1", + "id": "srv", + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "srv", + "containers": [{ + "name": "srv", + "image": "kubernetes/serve_hostname", + "ports": [{ + "containerPort": 9376, + "hostPort": 8080 + }] + }] + } + }, + "labels": { + "name": "srv" + } + }`) + _, err := c.Pods(api.NamespaceDefault).Create(serverPod) + if err != nil { + glog.Errorf("Failed to create serverPod: %v", err) + return false + } + defer c.Pods(api.NamespaceDefault).Delete(serverPod.Name) + waitForPodRunning(c, serverPod.Name) + + // This service exposes pod p's port 8080 as a service on port 8765 + svc := parseServiceOrDie(`{ + "id": "fooservice", + "kind": "Service", + "apiVersion": "v1beta1", + "port": 8765, + "containerPort": 8080, + "selector": { + "name": "p" + } + }`) + if err != nil { + glog.Errorf("Failed to delete service: %v", err) + return false + } + time.Sleep(2) + _, err = c.Services(api.NamespaceDefault).Create(svc) + if err != nil { + glog.Errorf("Failed to create service: %v", err) + return false + } + defer c.Services(api.NamespaceDefault).Delete(svc.Name) + + // TODO: we don't have a way to wait for a service to be "running". + // If this proves flaky, then we will need to retry the clientPod or insert a sleep. + + // Make a client pod that verifies that it has the service environment variables. + clientPod := parsePodOrDie(`{ + "apiVersion": "v1beta1", + "kind": "Pod", + "id": "env3", + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "env3", + "restartPolicy": { "never": {} }, + "containers": [{ + "name": "env3cont", + "image": "busybox", + "command": ["sh", "-c", "env"] + }] + } + }, + "labels": { "name": "env3" } + }`) + _, err = c.Pods(api.NamespaceDefault).Create(clientPod) + if err != nil { + glog.Errorf("Failed to create pod: %v", err) + return false + } + defer c.Pods(api.NamespaceDefault).Delete(clientPod.Name) + + // Wait for client pod to complete. + success := waitForPodSuccess(c, clientPod.Name, clientPod.Spec.Containers[0].Name) + if !success { + glog.Errorf("Failed to run client pod to detect service env vars.") + } + + // Grab its logs. Get host first. + clientPodStatus, err := c.Pods(api.NamespaceDefault).Get(clientPod.Name) + if err != nil { + glog.Errorf("Failed to get clientPod to know host: %v", err) + return false + } + glog.Infof("Trying to get logs from host %s pod %s container %s: %v", + clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, err) + logs, err := c.Get(). + Prefix("proxy"). + Resource("minions"). + Name(clientPodStatus.Status.Host). + Suffix("containerLogs", api.NamespaceDefault, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name). + Do(). + Raw() + if err != nil { + glog.Errorf("Failed to get logs from host %s pod %s container %s: %v", + clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, err) + return false + } + glog.Info("clientPod logs:", string(logs)) + + toFind := []string{ + "FOOSERVICE_SERVICE_HOST=", + "FOOSERVICE_SERVICE_PORT=", + "FOOSERVICE_PORT=", + "FOOSERVICE_PORT_8765_TCP_PORT=", + "FOOSERVICE_PORT_8765_TCP_PROTO=", + "FOOSERVICE_PORT_8765_TCP=", + "FOOSERVICE_PORT_8765_TCP_ADDR=", + } + + for _, m := range toFind { + if !strings.Contains(string(logs), m) { + glog.Errorf("Unable to find env var %q in client env vars.", m) + success = false + } + } + + // We could try a wget the service from the client pod. But services.sh e2e test covers that pretty well. + return success +} diff --git a/test/e2e/pod_update.go b/test/e2e/pod_update.go new file mode 100644 index 00000000000..dcaa10f943c --- /dev/null +++ b/test/e2e/pod_update.go @@ -0,0 +1,71 @@ +/* +Copyright 2014 Google Inc. 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 ( + "strconv" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/golang/glog" +) + +func TestPodUpdate(c *client.Client) bool { + podClient := c.Pods(api.NamespaceDefault) + + pod := loadPodOrDie(assetPath("api", "examples", "pod.json")) + value := strconv.Itoa(time.Now().Nanosecond()) + pod.Labels["time"] = value + + _, err := podClient.Create(pod) + if err != nil { + glog.Errorf("Failed to create pod: %v", err) + return false + } + defer podClient.Delete(pod.Name) + waitForPodRunning(c, pod.Name) + pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + if len(pods.Items) != 1 { + glog.Errorf("Failed to find the correct pod") + return false + } + + podOut, err := podClient.Get(pod.Name) + if err != nil { + glog.Errorf("Failed to get pod: %v", err) + return false + } + value = "time" + value + pod.Labels["time"] = value + pod.ResourceVersion = podOut.ResourceVersion + pod.UID = podOut.UID + pod, err = podClient.Update(pod) + if err != nil { + glog.Errorf("Failed to update pod: %v", err) + return false + } + waitForPodRunning(c, pod.Name) + pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + if len(pods.Items) != 1 { + glog.Errorf("Failed to find the correct pod after update.") + return false + } + glog.Infof("pod update OK") + return true +} diff --git a/test/e2e/roservice.go b/test/e2e/roservice.go new file mode 100644 index 00000000000..845d01654a7 --- /dev/null +++ b/test/e2e/roservice.go @@ -0,0 +1,55 @@ +/* +Copyright 2014 Google Inc. 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 ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/golang/glog" +) + +func TestKubernetesROService(c *client.Client) bool { + svc := api.ServiceList{} + err := c.Get(). + Namespace("default"). + AbsPath("/api/v1beta1/proxy/services/kubernetes-ro/api/v1beta1/services"). + Do(). + Into(&svc) + if err != nil { + glog.Errorf("unexpected error listing services using ro service: %v", err) + return false + } + var foundRW, foundRO bool + for i := range svc.Items { + if svc.Items[i].Name == "kubernetes" { + foundRW = true + } + if svc.Items[i].Name == "kubernetes-ro" { + foundRO = true + } + } + if !foundRW { + glog.Error("no RW service found") + } + if !foundRO { + glog.Error("no RO service found") + } + if !foundRW || !foundRO { + return false + } + return true +} diff --git a/test/e2e/util.go b/test/e2e/util.go new file mode 100644 index 00000000000..fdb7d056ad2 --- /dev/null +++ b/test/e2e/util.go @@ -0,0 +1,163 @@ +/* +Copyright 2014 Google Inc. 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 ( + "io/ioutil" + "path/filepath" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/golang/glog" +) + +type testContextType struct { + authConfig string + certDir string + host string + repoRoot string +} + +var testContext testContextType + +func waitForPodRunning(c *client.Client, id string) { + for { + time.Sleep(5 * time.Second) + pod, err := c.Pods(api.NamespaceDefault).Get(id) + if err != nil { + glog.Warningf("Get pod %s failed: %v", id, err) + continue + } + if pod.Status.Phase == api.PodRunning { + break + } + glog.Infof("Waiting for pod %s status to be %q (found %q)", id, api.PodRunning, pod.Status.Phase) + } +} + +// waitForPodSuccess returns true if the pod reached state success, or false if it reached failure or ran too long. +func waitForPodSuccess(c *client.Client, podName string, contName string) bool { + for i := 0; i < 10; i++ { + if i > 0 { + time.Sleep(5 * time.Second) + } + pod, err := c.Pods(api.NamespaceDefault).Get(podName) + if err != nil { + glog.Warningf("Get pod failed: %v", err) + continue + } + // Cannot use pod.Status.Phase == api.PodSucceeded/api.PodFailed due to #2632 + ci, ok := pod.Status.Info[contName] + if !ok { + glog.Infof("No Status.Info for container %s in pod %s yet", contName, podName) + } else { + if ci.State.Termination != nil { + if ci.State.Termination.ExitCode == 0 { + glog.Infof("Saw pod success") + return true + } else { + glog.Infof("Saw pod failure: %+v", ci.State.Termination) + } + glog.Infof("Waiting for pod %q status to be success or failure", podName) + } else { + glog.Infof("Nil State.Termination for container %s in pod %s so far", contName, podName) + } + } + } + glog.Warningf("Gave up waiting for pod %q status to be success or failure", podName) + return false +} + +// assetPath returns a path to the requested file; safe on all +// OSes. NOTE: If you use an asset in this test, you MUST add it to +// the KUBE_TEST_PORTABLE array in hack/lib/golang.sh. +func assetPath(pathElements ...string) string { + return filepath.Join(testContext.repoRoot, filepath.Join(pathElements...)) +} + +func loadObjectOrDie(filePath string) runtime.Object { + data, err := ioutil.ReadFile(filePath) + if err != nil { + glog.Fatalf("Failed to read object: %v", err) + } + return decodeObjectOrDie(data) +} + +func decodeObjectOrDie(data []byte) runtime.Object { + obj, err := latest.Codec.Decode(data) + if err != nil { + glog.Fatalf("Failed to decode object: %v", err) + } + return obj +} + +func loadPodOrDie(filePath string) *api.Pod { + obj := loadObjectOrDie(filePath) + pod, ok := obj.(*api.Pod) + if !ok { + glog.Fatalf("Failed to load pod: %v", obj) + } + return pod +} + +func loadClientOrDie() *client.Client { + config := client.Config{ + Host: testContext.host, + } + info, err := clientauth.LoadFromFile(testContext.authConfig) + if err != nil { + glog.Fatalf("Error loading auth: %v", err) + } + // If the certificate directory is provided, set the cert paths to be there. + if testContext.certDir != "" { + glog.Infof("Expecting certs in %v.", testContext.certDir) + info.CAFile = filepath.Join(testContext.certDir, "ca.crt") + info.CertFile = filepath.Join(testContext.certDir, "kubecfg.crt") + info.KeyFile = filepath.Join(testContext.certDir, "kubecfg.key") + } + config, err = info.MergeWithConfig(config) + if err != nil { + glog.Fatalf("Error creating client") + } + c, err := client.New(&config) + if err != nil { + glog.Fatalf("Error creating client") + } + return c +} + +func parsePodOrDie(json string) *api.Pod { + obj := decodeObjectOrDie([]byte(json)) + pod, ok := obj.(*api.Pod) + if !ok { + glog.Fatalf("Failed to cast pod: %v", obj) + } + return pod +} + +func parseServiceOrDie(json string) *api.Service { + obj := decodeObjectOrDie([]byte(json)) + service, ok := obj.(*api.Service) + if !ok { + glog.Fatalf("Failed to cast service: %v", obj) + } + return service +}