diff --git a/cmd/e2e/e2e.go b/cmd/e2e/e2e.go new file mode 100644 index 00000000000..1135ef22e0b --- /dev/null +++ b/cmd/e2e/e2e.go @@ -0,0 +1,89 @@ +/* +Copyright 2015 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 main + +import ( + "fmt" + "os" + goruntime "runtime" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/test/e2e" + "github.com/golang/glog" + flag "github.com/spf13/pflag" +) + +var ( + context = &e2e.TestContextType{} + cloudConfig = &context.CloudConfig + + orderseed = flag.Int64("orderseed", 0, "If non-zero, seed of random test shuffle order. (Otherwise random.)") + reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.") + times = flag.Int("times", 1, "Number of times each test is eligible to be run. Individual order is determined by shuffling --times instances of each test using --orderseed (like a multi-deck shoe of cards).") + testList util.StringList +) + +func init() { + flag.VarP(&testList, "test", "t", "Test to execute (may be repeated or comma separated list of tests.) Defaults to running all tests.") + + flag.StringVar(&context.KubeConfig, clientcmd.RecommendedConfigPathFlag, "", "Path to kubeconfig containing embeded authinfo.") + flag.StringVar(&context.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'") + flag.StringVar(&context.AuthConfig, "auth-config", "", "Path to the auth info file.") + flag.StringVar(&context.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.") + flag.StringVar(&context.Host, "host", "", "The host, or apiserver, to connect to") + flag.StringVar(&context.RepoRoot, "repo-root", "./", "Root directory of kubernetes repository, for finding test files. Default assumes working directory is repository root") + flag.StringVar(&context.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, vagrant, etc.)") + + // TODO: Flags per provider? Rename gce_project/gce_zone? + flag.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke") + flag.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable") + flag.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable") +} + +func main() { + util.InitFlags() + goruntime.GOMAXPROCS(goruntime.NumCPU()) + if context.Provider == "" { + glog.Info("The --provider flag is not set. Treating as a conformance test. Some tests may not be run.") + os.Exit(1) + } + if *times <= 0 { + glog.Error("Invalid --times (negative or no testing requested)!") + os.Exit(1) + } + + if context.Provider == "aws" { + awsConfig := "[Global]\n" + if cloudConfig.Zone == "" { + glog.Error("--gce-zone must be specified for AWS") + os.Exit(1) + } + awsConfig += fmt.Sprintf("Zone=%s\n", cloudConfig.Zone) + + var err error + cloudConfig.Provider, err = cloudprovider.GetCloudProvider(context.Provider, strings.NewReader(awsConfig)) + if err != nil { + glog.Error("Error building AWS provider: ", err) + os.Exit(1) + } + } + + e2e.RunE2ETests(context, *orderseed, *times, *reportDir, testList) +} diff --git a/hack/ginkgo-e2e.sh b/hack/ginkgo-e2e.sh index e4d0411884f..6044b54f546 100755 --- a/hack/ginkgo-e2e.sh +++ b/hack/ginkgo-e2e.sh @@ -18,19 +18,54 @@ set -o errexit set -o nounset set -o pipefail -GINKGO_PARALLEL=${GINKGO_PARALLEL:-n} # set to 'y' to run tests in parallel -KUBE_ROOT=$(readlink -f $(dirname "${BASH_SOURCE}")/..) - +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. source "${KUBE_ROOT}/cluster/common.sh" -source "${KUBE_ROOT}/hack/lib/init.sh" -# Ginkgo will build the e2e tests, so we need to make sure that the environment -# is set up correctly (including Godeps, etc). -kube::golang::setup_env +# --- Find local test binaries. -# --- Build the Ginkgo test runner from Godeps -godep go install github.com/onsi/ginkgo/ginkgo -ginkgo=$(godep path)/bin/ginkgo +# Detect the OS name/arch so that we can find our binary +case "$(uname -s)" in + Darwin) + host_os=darwin + ;; + Linux) + host_os=linux + ;; + *) + echo "Unsupported host OS. Must be Linux or Mac OS X." >&2 + exit 1 + ;; +esac + +case "$(uname -m)" in + x86_64*) + host_arch=amd64 + ;; + i?86_64*) + host_arch=amd64 + ;; + amd64*) + host_arch=amd64 + ;; + arm*) + host_arch=arm + ;; + i?86*) + host_arch=x86 + ;; + *) + echo "Unsupported host arch. Must be x86_64, 386 or arm." >&2 + exit 1 + ;; +esac + +# Gather up the list of likely places and use ls to find the latest one. +locations=( + "${KUBE_ROOT}/_output/dockerized/bin/${host_os}/${host_arch}/e2e" + "${KUBE_ROOT}/_output/local/bin/${host_os}/${host_arch}/e2e" + "${KUBE_ROOT}/platforms/${host_os}/${host_arch}/e2e" +) +e2e=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 ) # --- Setup some env vars. @@ -62,8 +97,8 @@ if [[ -z "${AUTH_CONFIG:-}" ]]; then ) elif [[ "${KUBERNETES_PROVIDER}" == "conformance_test" ]]; then auth_config=( - "--auth-config=${KUBERNETES_CONFORMANCE_TEST_AUTH_CONFIG:-}" - "--cert-dir=${KUBERNETES_CONFORMANCE_TEST_CERT_DIR:-}" + "--auth_config=${KUBERNETES_CONFORMANCE_TEST_AUTH_CONFIG:-}" + "--cert_dir=${KUBERNETES_CONFORMANCE_TEST_CERT_DIR:-}" ) else auth_config=( @@ -74,26 +109,21 @@ else echo "Conformance Test. No cloud-provider-specific preparation." KUBERNETES_PROVIDER="" auth_config=( - "--auth-config=${AUTH_CONFIG:-}" - "--cert-dir=${CERT_DIR:-}" + "--auth_config=${AUTH_CONFIG:-}" + "--cert_dir=${CERT_DIR:-}" ) fi -ginkgo_args=() -if [[ ${GINKGO_PARALLEL} =~ ^[yY]$ ]]; then - ginkgo_args+=("-p") -fi - +# Use the kubectl binary from the same directory as the e2e binary. # The --host setting is used only when providing --auth_config # If --kubeconfig is used, the host to use is retrieved from the .kubeconfig # file and the one provided with --host is ignored. -"${ginkgo}" "${ginkgo_args[@]:+${ginkgo_args[@]}}" test/e2e -- \ - "${auth_config[@]:+${auth_config[@]}}" \ +export PATH=$(dirname "${e2e}"):"${PATH}" +"${e2e}" "${auth_config[@]:+${auth_config[@]}}" \ --host="https://${KUBE_MASTER_IP-}" \ --provider="${KUBERNETES_PROVIDER}" \ - --gce-project="${PROJECT:-}" \ - --gce-zone="${ZONE:-}" \ - --kube-master="${KUBE_MASTER:-}" \ - --repo-root="${KUBE_VERSION_ROOT}" \ - ${E2E_REPORT_DIR+"--report-dir=${E2E_REPORT_DIR}"} \ + --gce_project="${PROJECT:-}" \ + --gce_zone="${ZONE:-}" \ + --kube_master="${KUBE_MASTER:-}" \ + ${E2E_REPORT_DIR+"--report_dir=${E2E_REPORT_DIR}"} \ "${@:-}" diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 45f3bad5bba..00b13088729 100644 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -44,6 +44,7 @@ readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}") # The set of test targets that we are building for all platforms readonly KUBE_TEST_TARGETS=( + cmd/e2e cmd/integration cmd/gendocs cmd/genman @@ -58,7 +59,6 @@ readonly KUBE_TEST_PORTABLE=( hack/e2e-suite hack/e2e-internal hack/ginkgo-e2e.sh - hack/lib ) # If we update this we need to also update the set of golang compilers we build diff --git a/hack/test-go.sh b/hack/test-go.sh index c95ed5836ec..ddac88fe34c 100755 --- a/hack/test-go.sh +++ b/hack/test-go.sh @@ -35,7 +35,6 @@ kube::test::find_dirs() { -o -wholename '*/third_party/*' \ -o -wholename '*/Godeps/*' \ -o -wholename '*/contrib/podex/*' \ - -o -wholename '*/test/e2e/*' \ -o -wholename '*/test/integration/*' \ \) -prune \ \) -name '*_test.go' -print0 | xargs -0n1 dirname | sed 's|^\./||' | sort -u diff --git a/test/e2e/driver.go b/test/e2e/driver.go new file mode 100644 index 00000000000..13f518ebf1b --- /dev/null +++ b/test/e2e/driver.go @@ -0,0 +1,98 @@ +/* +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" + "path" + "regexp" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/golang/glog" + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/reporters" + "github.com/onsi/gomega" +) + +type testResult bool + +type CloudConfig struct { + ProjectID string + Zone string + MasterName string + + Provider cloudprovider.Interface +} + +func init() { + // Turn on verbose by default to get spec names + config.DefaultReporterConfig.Verbose = true + + // Turn on EmitSpecProgress to get spec progress (especially on interrupt) + config.GinkgoConfig.EmitSpecProgress = true + + // Randomize specs as well as suites + config.GinkgoConfig.RandomizeAllSpecs = true +} + +func (t *testResult) Fail() { *t = false } + +// Run each Go end-to-end-test. This function assumes the +// creation of a test cluster. +func RunE2ETests(context *TestContextType, orderseed int64, times int, reportDir string, testList []string) { + testContext = *context + util.ReallyCrash = true + util.InitLogs() + defer util.FlushLogs() + + if len(testList) != 0 { + if config.GinkgoConfig.FocusString != "" || config.GinkgoConfig.SkipString != "" { + glog.Fatal("Either specify --test/-t or --ginkgo.focus/--ginkgo.skip but not both.") + } + var testRegexps []string + for _, t := range testList { + testRegexps = append(testRegexps, regexp.QuoteMeta(t)) + } + config.GinkgoConfig.FocusString = `\b(` + strings.Join(testRegexps, "|") + `)\b` + } + + // Disable density test unless it's explicitly requested. + if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" { + config.GinkgoConfig.SkipString = "Skipped" + } + + // TODO: Make orderseed work again. + var passed testResult = true + gomega.RegisterFailHandler(ginkgo.Fail) + // Run the existing tests with output to console + JUnit for Jenkins + for i := 0; i < times && passed; i++ { + var r []ginkgo.Reporter + if reportDir != "" { + r = append(r, reporters.NewJUnitReporter(path.Join(reportDir, fmt.Sprintf("junit_%d.xml", i+1)))) + } + ginkgo.RunSpecsWithDefaultAndCustomReporters(&passed, fmt.Sprintf("Kubernetes e2e Suite run %d of %d", i+1, times), r) + } + + if !passed { + glog.Fatalf("At least one test failed") + } else { + glog.Infof("All tests pass") + } +} diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go deleted file mode 100644 index 8802111f08c..00000000000 --- a/test/e2e/e2e_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -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 ( - "flag" - "fmt" - "os" - "path" - goruntime "runtime" - "strings" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" - "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/config" - "github.com/onsi/ginkgo/reporters" - "github.com/onsi/gomega" -) - -type testResult bool - -var ( - cloudConfig = &testContext.CloudConfig - - reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.") -) - -func init() { - // Turn on verbose by default to get spec names - config.DefaultReporterConfig.Verbose = true - - // Turn on EmitSpecProgress to get spec progress (especially on interrupt) - config.GinkgoConfig.EmitSpecProgress = true - - // Randomize specs as well as suites - config.GinkgoConfig.RandomizeAllSpecs = true - - flag.StringVar(&testContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, "", "Path to kubeconfig containing embeded authinfo.") - flag.StringVar(&testContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'") - flag.StringVar(&testContext.AuthConfig, "auth-config", "", "Path to the auth info file.") - flag.StringVar(&testContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.") - flag.StringVar(&testContext.Host, "host", "", "The host, or apiserver, to connect to") - flag.StringVar(&testContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.") - flag.StringVar(&testContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, vagrant, etc.)") - - // TODO: Flags per provider? Rename gce-project/gce-zone? - flag.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke") - flag.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable") - flag.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable") -} - -func (t *testResult) Fail() { *t = false } - -func TestE2E(t *testing.T) { - defer util.FlushLogs() - - // Disable density test unless it's explicitly requested. - if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" { - config.GinkgoConfig.SkipString = "Skipped" - } - - gomega.RegisterFailHandler(ginkgo.Fail) - // Run tests through the Ginkgo runner with output to console + JUnit for Jenkins - var r []ginkgo.Reporter - if *reportDir != "" { - r = append(r, reporters.NewJUnitReporter(path.Join(*reportDir, fmt.Sprintf("junit_%02d.xml", config.GinkgoConfig.ParallelNode)))) - } - ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "Kubernetes e2e suite", r) -} - -func TestMain(m *testing.M) { - flag.Parse() - util.ReallyCrash = true - util.InitLogs() - goruntime.GOMAXPROCS(goruntime.NumCPU()) - - // TODO: possibly clean up or refactor this functionality. - if testContext.Provider == "" { - glog.Info("The --provider flag is not set. Treating as a conformance test. Some tests may not be run.") - os.Exit(1) - } - - if testContext.Provider == "aws" { - awsConfig := "[Global]\n" - if cloudConfig.Zone == "" { - glog.Error("gce-zone must be specified for AWS") - os.Exit(1) - } - awsConfig += fmt.Sprintf("Zone=%s\n", cloudConfig.Zone) - - var err error - cloudConfig.Provider, err = cloudprovider.GetCloudProvider(testContext.Provider, strings.NewReader(awsConfig)) - if err != nil { - glog.Error("Error building AWS provider: ", err) - os.Exit(1) - } - } - - os.Exit(m.Run()) -} diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go index f07b973cb48..ac438a2487b 100644 --- a/test/e2e/kubectl.go +++ b/test/e2e/kubectl.go @@ -45,7 +45,6 @@ const ( ) var _ = Describe("kubectl", func() { - defer GinkgoRecover() var c *client.Client var ns string var testingNs *api.Namespace @@ -66,12 +65,11 @@ var _ = Describe("kubectl", func() { }) Describe("update-demo", func() { - var updateDemoRoot, nautilusPath, kittenPath string - BeforeEach(func() { + var ( updateDemoRoot = filepath.Join(testContext.RepoRoot, "examples/update-demo") - nautilusPath = filepath.Join(updateDemoRoot, "nautilus-rc.yaml") - kittenPath = filepath.Join(updateDemoRoot, "kitten-rc.yaml") - }) + nautilusPath = filepath.Join(updateDemoRoot, "nautilus-rc.yaml") + kittenPath = filepath.Join(updateDemoRoot, "kitten-rc.yaml") + ) It("should create and stop a replication controller", func() { defer cleanup(nautilusPath, updateDemoSelector) @@ -109,10 +107,7 @@ var _ = Describe("kubectl", func() { }) Describe("guestbook", func() { - var guestbookPath string - BeforeEach(func() { - guestbookPath = filepath.Join(testContext.RepoRoot, "examples/guestbook") - }) + var guestbookPath = filepath.Join(testContext.RepoRoot, "examples/guestbook") It("should create and stop a working application", func() { if !providerIs("gce", "gke") { diff --git a/test/e2e/shell.go b/test/e2e/shell.go index a05af72d404..6640d0a4b8e 100644 --- a/test/e2e/shell.go +++ b/test/e2e/shell.go @@ -22,19 +22,20 @@ import ( "io/ioutil" "os" "os/exec" + "path" "path/filepath" . "github.com/onsi/ginkgo" ) var ( - root = kubeRootOrDie() + root = absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))) ) var _ = Describe("Shell", func() { + defer GinkgoRecover() // Slurp up all the tests in hack/e2e-suite - // This should be using testContext.RepoRoot, but this is evaluated before flags are evaluated, so we are stuck. bashE2ERoot := filepath.Join(root, "hack/e2e-suite") files, err := ioutil.ReadDir(bashE2ERoot) if err != nil { @@ -55,13 +56,8 @@ var _ = Describe("Shell", func() { } }) -// Returns the root directory of the Kubernetes source tree, assuming the test lives inside test/e2e. -func kubeRootOrDie() string { - cwd, err := os.Getwd() - if err != nil { - panic(err) - } - out, err := filepath.Abs(filepath.Join(cwd, "..", "..")) +func absOrDie(path string) string { + out, err := filepath.Abs(path) if err != nil { panic(err) } diff --git a/test/e2e/util.go b/test/e2e/util.go index cfbb1b11853..3647b508685 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -34,7 +34,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" @@ -51,14 +50,6 @@ const ( podStartTimeout = 5 * time.Minute ) -type CloudConfig struct { - ProjectID string - Zone string - MasterName string - - Provider cloudprovider.Interface -} - type TestContextType struct { KubeConfig string KubeContext string @@ -313,7 +304,8 @@ func validateController(c *client.Client, containerImage string, replicas int, c Failf("Timed out after %v seconds waiting for %s pods to reach valid state", podStartTimeout.Seconds(), testname) } -// kubectlCmd runs the kubectl executable through the helper script. +// kubectlCmd runs the kubectl executable. +// kubectlCmd runs the kubectl executable. func kubectlCmd(args ...string) *exec.Cmd { defaultArgs := []string{} @@ -341,7 +333,7 @@ func kubectlCmd(args ...string) *exec.Cmd { kubectlArgs := append(defaultArgs, args...) //TODO: the "kubectl" path string might be worth externalizing into an (optional) ginko arg. - cmd := exec.Command(filepath.Join(testContext.RepoRoot, "cluster/kubectl.sh"), kubectlArgs...) + cmd := exec.Command("kubectl", kubectlArgs...) Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args, " ")) return cmd }