diff --git a/build/build-image/common.sh b/build/build-image/common.sh index 6abd6cbf064..15df56bac43 100644 --- a/build/build-image/common.sh +++ b/build/build-image/common.sh @@ -34,6 +34,7 @@ server_targets=( client_targets=( cmd/kubecfg cmd/kubectl + cmd/e2e ) mkdir -p "${KUBE_TARGET}" diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 5483f29780f..e8e62b78c4e 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -518,7 +518,6 @@ function test-build-release { # # Assumed vars: # PROJECT -# ALREADY_UP # Variables from config.sh function test-setup { @@ -526,18 +525,15 @@ function test-setup { # gce specific detect-project - if [[ ${ALREADY_UP} -ne 1 ]]; then - # Open up port 80 & 8080 so common containers on minions can be reached - gcutil addfirewall \ - --project "${PROJECT}" \ - --norespect_terminal_width \ - --sleep_between_polls "${POLL_SLEEP_INTERVAL}" \ - --target_tags "${MINION_TAG}" \ - --allowed tcp:80,tcp:8080 \ - --network "${NETWORK}" \ - "${MINION_TAG}-${INSTANCE_PREFIX}-http-alt" - fi - + # Open up port 80 & 8080 so common containers on minions can be reached + gcutil addfirewall \ + --project "${PROJECT}" \ + --norespect_terminal_width \ + --sleep_between_polls "${POLL_SLEEP_INTERVAL}" \ + --target_tags "${MINION_TAG}" \ + --allowed tcp:80,tcp:8080 \ + --network "${NETWORK}" \ + "${MINION_TAG}-${INSTANCE_PREFIX}-http-alt" } # Execute after running tests to perform any required clean-up. This is called diff --git a/hack/e2e-test.sh b/hack/e2e-test.sh index 08e49305349..70ffbdbe548 100755 --- a/hack/e2e-test.sh +++ b/hack/e2e-test.sh @@ -14,88 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Starts a Kubernetes cluster, runs the e2e test suite, and shuts it -# down. -# -# Environment flags: -# TEST_PATTERN: A pattern to match test filenames against. -# Example: "TEST_PATTERN=up" would match tests named "update.sh" and -# "hiccup.sh". +# Provided for backwards compatibility +go run e2e.go -v -build -up -tests="*" -down -set -o errexit -set -o nounset -set -o pipefail - -# Use testing config -export KUBE_CONFIG_FILE="config-test.sh" -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. - -# TODO(jbeda): This will break on usage if there is a space in -# ${KUBE_ROOT}. Covert to an array? Or an exported function? -export KUBECFG="${KUBE_ROOT}/cluster/kubecfg.sh -expect_version_match" - -source "${KUBE_ROOT}/cluster/kube-env.sh" -source "${KUBE_ROOT}/cluster/$KUBERNETES_PROVIDER/util.sh" - -# For debugging of this test's components, it's helpful to leave the test -# cluster running. -ALREADY_UP=${1:-0} -LEAVE_UP=${2:-0} -TEAR_DOWN=${3:-0} - -if [[ $TEAR_DOWN -ne 0 ]]; then - detect-project - trap test-teardown EXIT - exit 0 -fi - -# Build a release required by the test provider [if any] -test-build-release - -if [[ ${ALREADY_UP} -ne 1 ]]; then - # Now bring a test cluster up with that release. - "${KUBE_ROOT}/cluster/kube-up.sh" -else - # Just push instead - "${KUBE_ROOT}/cluster/kube-push.sh" -fi - -# Perform any required setup of the cluster -test-setup - -set +e - -if [[ ${LEAVE_UP} -ne 1 ]]; then - trap test-teardown EXIT -fi - -TEST_PATTERN="${TEST_PATTERN:-}" -failed=() -for test_file in $(ls "${KUBE_ROOT}/hack/e2e-suite/"); do - if [[ "${TEST_PATTERN}" != "" ]]; then - check=".*${TEST_PATTERN}.*" - if [[ ! "$test_file" =~ $check ]]; then - echo "skipping $test_file" - continue - fi - fi - - echo "+++ Running $test_file" - result=0 - "${KUBE_ROOT}/hack/e2e-suite/${test_file}" || result="$?" - if [[ "${result}" -eq "0" ]]; then - echo "${test_file} returned ${result}; passed!" - else - echo "${test_file} returned ${result}; FAIL!" - failed+=(${test_file}) - fi -done - -echo -if [[ "${#failed[*]}" -eq 0 ]]; then - echo "Final: All tests passed." -else - echo "Final: ${#failed[@]} tests failed: ${failed[@]}." -fi - -exit "${#failed[@]}" +exit $? diff --git a/hack/e2e.go b/hack/e2e.go new file mode 100644 index 00000000000..093b2475ad4 --- /dev/null +++ b/hack/e2e.go @@ -0,0 +1,203 @@ +/* +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. +*/ + +// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run". +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "path" + "path/filepath" + "strings" +) + +var ( + build = flag.Bool("build", false, "If true, build a new release. Otherwise, use whatever is there.") + up = flag.Bool("up", false, "If true, start the the e2e cluster. If cluster is already up, recreate it.") + push = flag.Bool("push", false, "If true, push to e2e cluster. Has no effect if -up is true.") + down = flag.Bool("down", false, "If true, tear down the cluster before exiting.") + tests = flag.String("tests", "*", "Run tests in hack/e2e-suite matching this glob.") + root = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.") + verbose = flag.Bool("v", false, "If true, print all command output.") +) + +var signals = make(chan os.Signal, 100) + +func absOrDie(path string) string { + out, err := filepath.Abs(path) + if err != nil { + panic(err) + } + return out +} + +func main() { + flag.Parse() + signal.Notify(signals, os.Interrupt) + + if *build { + if runBash("build", `test-build-release`) { + log.Fatal("Error building. Aborting.") + } + } + + if *up { + if !Up() { + log.Fatal("Error starting e2e cluster. Aborting.") + } + } else if *push { + if !runBash("push", path.Join(*root, "/cluster/kube-push.sh")) { + log.Fatal("Error pushing e2e cluster. Aborting.") + } + } + + failed, passed := []string{}, []string{} + if *tests != "" { + failed, passed = Test() + } + + if *down { + TearDown() + } + + log.Printf("Passed tests: %v", passed) + log.Printf("Failed tests: %v", failed) + if len(failed) > 0 { + os.Exit(1) + } +} + +func TearDown() { + runBash("teardown", "test-teardown") +} + +func Up() bool { + if !tryUp() { + log.Printf("kube-up failed; will tear down and retry. (Possibly your cluster was in some partially created state?)") + TearDown() + return tryUp() + } + return true +} + +func tryUp() bool { + return runBash("up", path.Join(*root, "/cluster/kube-up.sh; test-setup;")) +} + +func Test() (failed, passed []string) { + // run tests! + dir, err := os.Open(filepath.Join(*root, "hack", "e2e-suite")) + if err != nil { + log.Fatal("Couldn't open e2e-suite dir") + } + defer dir.Close() + names, err := dir.Readdirnames(0) + if err != nil { + log.Fatal("Couldn't read names in e2e-suite dir") + } + + for i := range names { + name := names[i] + if name == "." || name == ".." { + continue + } + if match, err := path.Match(*tests, name); !match && err == nil { + continue + } + absName := filepath.Join(*root, "hack", "e2e-suite", name) + log.Printf("%v matches %v. Starting test.", name, *tests) + if runBash(name, absName) { + log.Printf("%v passed", name) + passed = append(passed, name) + } else { + log.Printf("%v failed", name) + failed = append(failed, name) + } + } + + return +} + +// All nonsense below is temporary until we have go versions of these things. + +func runBash(stepName, bashFragment string) bool { + log.Printf("Running: %v", stepName) + cmd := exec.Command("bash", "-s") + cmd.Stdin = strings.NewReader(bashWrap(bashFragment)) + stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + if *verbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = stdout + cmd.Stderr = stderr + } + + done := make(chan struct{}) + defer close(done) + go func() { + for { + select { + case <-done: + return + case s := <-signals: + cmd.Process.Signal(s) + } + } + }() + + if err := cmd.Run(); err != nil { + log.Printf("Error running %v: %v", stepName, err) + if !*verbose { + fmt.Printf("stdout:\n------\n%v\n------\n", string(stdout.Bytes())) + fmt.Printf("stderr:\n------\n%v\n------\n", string(stderr.Bytes())) + } + return false + } + return true +} + +var bashCommandPrefix = ` +set -o errexit +set -o nounset +set -o pipefail + +export KUBE_CONFIG_FILE="config-test.sh" + +# TODO(jbeda): This will break on usage if there is a space in +# ${KUBE_ROOT}. Covert to an array? Or an exported function? +export KUBECFG="` + *root + `/cluster/kubecfg.sh -expect_version_match" + +source "` + *root + `/cluster/kube-env.sh" +source "` + *root + `/cluster/${KUBERNETES_PROVIDER}/util.sh" + +detect-project + +` + +var bashCommandSuffix = ` + +` + +func bashWrap(cmd string) string { + return bashCommandPrefix + cmd + bashCommandSuffix +}