mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Refactor node e2e tests
- Add Makefile targets - Start services in the test harness and connect locally - Build test into binary and copy to remote host to start services - Use tar to copy binaries to remote hosts to simplify design
This commit is contained in:
parent
423215fdc6
commit
a3623c0c14
9
Makefile
9
Makefile
@ -101,6 +101,15 @@ test_e2e:
|
|||||||
hack/e2e-test.sh
|
hack/e2e-test.sh
|
||||||
.PHONY: test_e2e
|
.PHONY: test_e2e
|
||||||
|
|
||||||
|
# Build and run node end-to-end tests.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# make test_e2e_node
|
||||||
|
test_e2e_node:
|
||||||
|
hack/e2e-node-test.sh
|
||||||
|
.PHONY: test_e2e_node
|
||||||
|
|
||||||
|
|
||||||
# Remove all build artifacts.
|
# Remove all build artifacts.
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
|
21
hack/e2e-node-test.sh
Executable file
21
hack/e2e-node-test.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2016 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.
|
||||||
|
|
||||||
|
# Provided for backwards compatibility
|
||||||
|
sudo -v
|
||||||
|
ginkgo "$(dirname $0)/../test/e2e_node/" -- --alsologtostderr --v 2 --node-name $(hostname) --build-services=true --start-services=true --stop-services=true
|
||||||
|
|
||||||
|
exit $?
|
@ -87,6 +87,7 @@ kube::golang::test_targets() {
|
|||||||
examples/k8petstore/web-server/src
|
examples/k8petstore/web-server/src
|
||||||
github.com/onsi/ginkgo/ginkgo
|
github.com/onsi/ginkgo/ginkgo
|
||||||
test/e2e/e2e.test
|
test/e2e/e2e.test
|
||||||
|
test/e2e_node/e2e_node.test
|
||||||
)
|
)
|
||||||
if [ -n "${KUBERNETES_CONTRIB:-}" ]; then
|
if [ -n "${KUBERNETES_CONTRIB:-}" ]; then
|
||||||
for contrib in "${KUBERNETES_CONTRIB}"; do
|
for contrib in "${KUBERNETES_CONTRIB}"; do
|
||||||
|
@ -28,6 +28,7 @@ bench-workers
|
|||||||
bind-address
|
bind-address
|
||||||
bind-pods-burst
|
bind-pods-burst
|
||||||
bind-pods-qps
|
bind-pods-qps
|
||||||
|
build-services
|
||||||
cadvisor-port
|
cadvisor-port
|
||||||
cert-dir
|
cert-dir
|
||||||
certificate-authority
|
certificate-authority
|
||||||
@ -161,6 +162,7 @@ ir-user
|
|||||||
jenkins-host
|
jenkins-host
|
||||||
jenkins-jobs
|
jenkins-jobs
|
||||||
k8s-build-output
|
k8s-build-output
|
||||||
|
k8s-bin-dir
|
||||||
keep-gogoproto
|
keep-gogoproto
|
||||||
km-path
|
km-path
|
||||||
kube-api-burst
|
kube-api-burst
|
||||||
@ -322,6 +324,7 @@ scheduler-name
|
|||||||
schema-cache-dir
|
schema-cache-dir
|
||||||
secure-port
|
secure-port
|
||||||
serialize-image-pulls
|
serialize-image-pulls
|
||||||
|
server-start-timeout
|
||||||
service-account-key-file
|
service-account-key-file
|
||||||
service-account-lookup
|
service-account-lookup
|
||||||
service-account-private-key-file
|
service-account-private-key-file
|
||||||
@ -343,10 +346,14 @@ skip-generated-rewrite
|
|||||||
skip-munges
|
skip-munges
|
||||||
sort-by
|
sort-by
|
||||||
source-file
|
source-file
|
||||||
|
ssh-env
|
||||||
ssh-keyfile
|
ssh-keyfile
|
||||||
|
ssh-options
|
||||||
ssh-user
|
ssh-user
|
||||||
|
start-services
|
||||||
static-pods-config
|
static-pods-config
|
||||||
stats-port
|
stats-port
|
||||||
|
stop-services
|
||||||
storage-version
|
storage-version
|
||||||
storage-versions
|
storage-versions
|
||||||
streaming-connection-idle-timeout
|
streaming-connection-idle-timeout
|
||||||
|
@ -14,4 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// e2e_node contains e2e tests specific to the node
|
||||||
|
// TODO: rename this package e2e-node
|
||||||
package e2e_node
|
package e2e_node
|
||||||
|
131
test/e2e_node/e2e_build.go
Normal file
131
test/e2e_node/e2e_build.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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_node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var k8sBinDir = flag.String("k8s-bin-dir", "", "Directory containing k8s kubelet and kube-apiserver binaries.")
|
||||||
|
|
||||||
|
func buildGo() {
|
||||||
|
glog.Infof("Building k8s binaries...")
|
||||||
|
k8sRoot, err := getK8sRootDir()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to locate kubernetes root directory %v.", err)
|
||||||
|
}
|
||||||
|
out, err := exec.Command(filepath.Join(k8sRoot, "hack/build-go.sh")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to build go packages %v. Output:\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getK8sBin(bin string) (string, error) {
|
||||||
|
// Use commandline specified path
|
||||||
|
if *k8sBinDir != "" {
|
||||||
|
absPath, err := filepath.Abs(*k8sBinDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(*k8sBinDir, bin)); err != nil {
|
||||||
|
return "", fmt.Errorf("Could not find kube-apiserver under directory %s.", absPath)
|
||||||
|
}
|
||||||
|
return filepath.Join(absPath, bin), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Could not find absolute path of directory containing the tests %s.", filepath.Dir(os.Args[0]))
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(path, bin)); err == nil {
|
||||||
|
return filepath.Join(path, bin), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buildOutputDir, err := getK8sBuildOutputDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(buildOutputDir, bin)); err == nil {
|
||||||
|
return filepath.Join(buildOutputDir, bin), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give up with error
|
||||||
|
return "", fmt.Errorf("Unable to locate %s. Can be defined using --k8s-path.", bin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dedup / merge this with comparable utilities in e2e/util.go
|
||||||
|
func getK8sRootDir() (string, error) {
|
||||||
|
// Get the directory of the current executable
|
||||||
|
_, testExec, _, _ := runtime.Caller(0)
|
||||||
|
path := filepath.Dir(testExec)
|
||||||
|
|
||||||
|
// Look for the kubernetes source root directory
|
||||||
|
if strings.Contains(path, "k8s.io/kubernetes") {
|
||||||
|
splitPath := strings.Split(path, "k8s.io/kubernetes")
|
||||||
|
return filepath.Join(splitPath[0], "k8s.io/kubernetes/"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Could not find kubernetes source root directory.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getK8sBuildOutputDir() (string, error) {
|
||||||
|
k8sRoot, err := getK8sRootDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buildOutputDir := filepath.Join(k8sRoot, "_output/local/go/bin")
|
||||||
|
if _, err := os.Stat(buildOutputDir); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buildOutputDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getK8sNodeTestDir() (string, error) {
|
||||||
|
k8sRoot, err := getK8sRootDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buildOutputDir := filepath.Join(k8sRoot, "test/e2e_node")
|
||||||
|
if _, err := os.Stat(buildOutputDir); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buildOutputDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKubeletServerBin() string {
|
||||||
|
bin, err := getK8sBin("kubelet")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not locate kubelet binary."))
|
||||||
|
}
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
|
||||||
|
func getApiServerBin() string {
|
||||||
|
bin, err := getK8sBin("kube-apiserver")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not locate kube-apiserver binary."))
|
||||||
|
}
|
||||||
|
return bin
|
||||||
|
}
|
@ -15,21 +15,31 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// To run tests in this suite
|
// To run tests in this suite
|
||||||
// Local: `$ ginkgo -- --logtostderr -v 2`
|
// NOTE: This test suite requires sudo capabilities to run the kubelet and kube-apiserver.
|
||||||
// Remote: `$ ginkgo -- --node-name <hostname> --api-server-address=<hostname:api_port> --kubelet-address=<hostname=kubelet_port> --logtostderr -v 2`
|
// $ sudo -v && ginkgo test/e2e_node/ -- --logtostderr --v 2 --node-name `hostname` --start-services
|
||||||
package e2e_node
|
package e2e_node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var kubeletAddress = flag.String("kubelet-address", "http://127.0.0.1:10255", "Host and port of the kubelet")
|
var kubeletAddress = flag.String("kubelet-address", "http://127.0.0.1:10255", "Host and port of the kubelet")
|
||||||
var apiServerAddress = flag.String("api-server-address", "http://127.0.0.1:8080", "Host and port of the api server")
|
var apiServerAddress = flag.String("api-server-address", "http://127.0.0.1:8080", "Host and port of the api server")
|
||||||
var nodeName = flag.String("node-name", "127.0.0.1", "Name of the node")
|
var nodeName = flag.String("node-name", "", "Name of the node")
|
||||||
|
var buildServices = flag.Bool("build-services", true, "If true, build local executables")
|
||||||
|
var startServices = flag.Bool("start-services", true, "If true, start local node services")
|
||||||
|
var stopServices = flag.Bool("stop-services", true, "If true, stop local node services after running tets")
|
||||||
|
|
||||||
|
var e2es *e2eService
|
||||||
|
|
||||||
func TestE2eNode(t *testing.T) {
|
func TestE2eNode(t *testing.T) {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@ -39,8 +49,42 @@ func TestE2eNode(t *testing.T) {
|
|||||||
|
|
||||||
// Setup the kubelet on the node
|
// Setup the kubelet on the node
|
||||||
var _ = BeforeSuite(func() {
|
var _ = BeforeSuite(func() {
|
||||||
|
if *buildServices {
|
||||||
|
buildGo()
|
||||||
|
}
|
||||||
|
if *nodeName == "" {
|
||||||
|
output, err := exec.Command("hostname").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal("Could not get node name from hostname %v. Output:\n%s", err, output)
|
||||||
|
}
|
||||||
|
*nodeName = strings.TrimSpace(fmt.Sprintf("%s", output))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *startServices {
|
||||||
|
e2es = newE2eService()
|
||||||
|
if err := e2es.start(); err != nil {
|
||||||
|
Fail(fmt.Sprintf("Unable to start node services.\n%v", err))
|
||||||
|
}
|
||||||
|
glog.Infof("Node services started. Running tests...")
|
||||||
|
} else {
|
||||||
|
glog.Infof("Running tests without starting services.")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Tear down the kubelet on the node
|
// Tear down the kubelet on the node
|
||||||
var _ = AfterSuite(func() {
|
var _ = AfterSuite(func() {
|
||||||
|
if e2es != nil && *startServices && *stopServices {
|
||||||
|
glog.Infof("Stopping node services...")
|
||||||
|
e2es.stop()
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
b.WriteString("-------------------------------------------------------------\n")
|
||||||
|
b.WriteString(fmt.Sprintf("kubelet output:\n%s\n", e2es.kubeletCombinedOut.String()))
|
||||||
|
b.WriteString("-------------------------------------------------------------\n")
|
||||||
|
b.WriteString(fmt.Sprintf("apiserver output:\n%s", e2es.apiServerCombinedOut.String()))
|
||||||
|
b.WriteString("-------------------------------------------------------------\n")
|
||||||
|
b.WriteString(fmt.Sprintf("etcd output:\n%s", e2es.etcdCombinedOut.String()))
|
||||||
|
b.WriteString("-------------------------------------------------------------\n")
|
||||||
|
glog.V(2).Infof(b.String())
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
182
test/e2e_node/e2e_remote.go
Normal file
182
test/e2e_node/e2e_remote.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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_node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sshOptions = flag.String("ssh-options", "", "Commandline options passed to ssh.")
|
||||||
|
var sshEnv = flag.String("ssh-env", "", "Use predefined ssh options for environment. Options: gce")
|
||||||
|
|
||||||
|
var sshOptionsMap map[string]string
|
||||||
|
|
||||||
|
const archiveName = "e2e_node_test.tar.gz"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal(err)
|
||||||
|
}
|
||||||
|
sshOptionsMap = map[string]string{
|
||||||
|
"gce": fmt.Sprintf("-i %s/.ssh/google_compute_engine -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o CheckHostIP=no -o StrictHostKeyChecking=no", usr.HomeDir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestArchive builds the local source and creates a tar archive e2e_node_test.tar.gz containing
|
||||||
|
// the binaries k8s required for node e2e tests
|
||||||
|
func CreateTestArchive() string {
|
||||||
|
// Build the executables
|
||||||
|
buildGo()
|
||||||
|
|
||||||
|
// Build the e2e tests into an executable
|
||||||
|
glog.Infof("Building ginkgo k8s test binaries...")
|
||||||
|
testDir, err := getK8sNodeTestDir()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to locate test/e2e_node directory %v.", err)
|
||||||
|
}
|
||||||
|
out, err := exec.Command("ginkgo", "build", testDir).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to build e2e tests under %s %v. Output:\n%s", testDir, err, out)
|
||||||
|
}
|
||||||
|
ginkgoTest := filepath.Join(testDir, "e2e_node.test")
|
||||||
|
if _, err := os.Stat(ginkgoTest); err != nil {
|
||||||
|
glog.Fatalf("Failed to locate test binary %s", ginkgoTest)
|
||||||
|
}
|
||||||
|
defer os.Remove(ginkgoTest)
|
||||||
|
|
||||||
|
// Make sure we can find the newly built binaries
|
||||||
|
buildOutputDir, err := getK8sBuildOutputDir()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to locate kubernetes build output directory %v", err)
|
||||||
|
}
|
||||||
|
kubelet := filepath.Join(buildOutputDir, "kubelet")
|
||||||
|
if _, err := os.Stat(kubelet); err != nil {
|
||||||
|
glog.Fatalf("Failed to locate binary %s", kubelet)
|
||||||
|
}
|
||||||
|
apiserver := filepath.Join(buildOutputDir, "kube-apiserver")
|
||||||
|
if _, err := os.Stat(apiserver); err != nil {
|
||||||
|
glog.Fatalf("Failed to locate binary %s", apiserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("Building archive...")
|
||||||
|
tardir, err := ioutil.TempDir("", "node-e2e-archive")
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to create temporary directory %v.", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tardir)
|
||||||
|
|
||||||
|
// Copy binaries
|
||||||
|
out, err = exec.Command("cp", ginkgoTest, filepath.Join(tardir, "e2e_node.test")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to copy e2e_node.test %v.", err)
|
||||||
|
}
|
||||||
|
out, err = exec.Command("cp", kubelet, filepath.Join(tardir, "kubelet")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to copy kubelet %v.", err)
|
||||||
|
}
|
||||||
|
out, err = exec.Command("cp", apiserver, filepath.Join(tardir, "kube-apiserver")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to copy kube-apiserver %v.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the tar
|
||||||
|
out, err = exec.Command("tar", "-zcvf", archiveName, "-C", tardir, ".").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to build tar %v. Output:\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to get working directory %v.", err)
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, archiveName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunRemote copies the archive file to a /tmp file on host, unpacks it, and runs the e2e_node.test
|
||||||
|
func RunRemote(archive string, host string) (string, error) {
|
||||||
|
// Create the temp staging directory
|
||||||
|
tmp := fmt.Sprintf("/tmp/gcloud-e2e-%d", rand.Int31())
|
||||||
|
_, err := runSshCommand("ssh", host, "--", "mkdir", tmp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
output, err := runSshCommand("ssh", host, "--", "rm", "-rf", tmp)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to cleanup tmp directory %s on host %v. Output:\n%s", tmp, err, output)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Copy the archive to the staging directory
|
||||||
|
_, err = runSshCommand("scp", archive, fmt.Sprintf("%s:%s/", host, tmp))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill any running node processes
|
||||||
|
cmd := getSshCommand(" ; ",
|
||||||
|
"sudo pkill kubelet",
|
||||||
|
"sudo pkill kube-apiserver",
|
||||||
|
"sudo pkill etcd")
|
||||||
|
// No need to log an error if pkill fails since pkill will fail if the commands are not running.
|
||||||
|
// If we are unable to stop existing running k8s processes, we should see messages in the kubelet/apiserver/etcd
|
||||||
|
// logs about failing to bind the required ports.
|
||||||
|
runSshCommand("ssh", host, "--", "sh", "-c", cmd)
|
||||||
|
|
||||||
|
// Extract the archive and run the tests
|
||||||
|
cmd = getSshCommand(" && ",
|
||||||
|
fmt.Sprintf("cd %s", tmp),
|
||||||
|
fmt.Sprintf("tar -xzvf ./%s", archiveName),
|
||||||
|
"./e2e_node.test --logtostderr --v 2 --build-services=false --node-name `hostname`")
|
||||||
|
output, err := runSshCommand("ssh", host, "--", "sh", "-c", cmd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSshCommand handles proper quoting so that multiple commands are executed in the same shell over ssh
|
||||||
|
func getSshCommand(sep string, args ...string) string {
|
||||||
|
return fmt.Sprintf("'%s'", strings.Join(args, sep))
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSshCommand executes the ssh or scp command, adding the flag provided --ssh-options
|
||||||
|
func runSshCommand(cmd string, args ...string) (string, error) {
|
||||||
|
if env, found := sshOptionsMap[*sshEnv]; found {
|
||||||
|
args = append(strings.Split(env, " "), args...)
|
||||||
|
}
|
||||||
|
if *sshOptions != "" {
|
||||||
|
args = append(strings.Split(*sshOptions, " "), args...)
|
||||||
|
}
|
||||||
|
output, err := exec.Command(cmd, args...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("%s", output), fmt.Errorf("command %q %q failed with error: %v and output: %q", cmd, args, err, output)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s", output), nil
|
||||||
|
}
|
185
test/e2e_node/e2e_service.go
Normal file
185
test/e2e_node/e2e_service.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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_node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var serverStartTimeout = flag.Duration("server-start-timeout", time.Second*30, "Time to wait for each server to become healthy.")
|
||||||
|
|
||||||
|
type e2eService struct {
|
||||||
|
etcdCmd *exec.Cmd
|
||||||
|
etcdCombinedOut bytes.Buffer
|
||||||
|
etcdDataDir string
|
||||||
|
apiServerCmd *exec.Cmd
|
||||||
|
apiServerCombinedOut bytes.Buffer
|
||||||
|
kubeletCmd *exec.Cmd
|
||||||
|
kubeletCombinedOut bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newE2eService() *e2eService {
|
||||||
|
return &e2eService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) start() error {
|
||||||
|
if _, err := getK8sBin("kubelet"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := getK8sBin("kube-apiserver"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := es.startEtcd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.etcdCmd = cmd
|
||||||
|
|
||||||
|
cmd, err = es.startApiServer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.apiServerCmd = cmd
|
||||||
|
|
||||||
|
cmd, err = es.startKubeletServer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.kubeletCmd = cmd
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) stop() {
|
||||||
|
if es.kubeletCmd != nil {
|
||||||
|
err := es.kubeletCmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to stop kubelet.\n%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if es.apiServerCmd != nil {
|
||||||
|
err := es.apiServerCmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to stop be-apiserver.\n%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if es.etcdCmd != nil {
|
||||||
|
err := es.etcdCmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to stop etcd.\n%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if es.etcdDataDir != "" {
|
||||||
|
err := os.RemoveAll(es.etcdDataDir)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to delete etcd data directory %s.\n%v", es.etcdDataDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) startEtcd() (*exec.Cmd, error) {
|
||||||
|
dataDir, err := ioutil.TempDir("", "node-e2e")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.etcdDataDir = dataDir
|
||||||
|
return es.startServer(healthCheckCommand{
|
||||||
|
combinedOut: &es.etcdCombinedOut,
|
||||||
|
healthCheckUrl: "http://127.0.0.1:4001/v2/keys",
|
||||||
|
command: "etcd",
|
||||||
|
args: []string{"--data-dir", dataDir, "--name", "e2e-node"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) startApiServer() (*exec.Cmd, error) {
|
||||||
|
return es.startServer(
|
||||||
|
healthCheckCommand{
|
||||||
|
combinedOut: &es.apiServerCombinedOut,
|
||||||
|
healthCheckUrl: "http://127.0.0.1:8080/healthz",
|
||||||
|
command: "sudo",
|
||||||
|
args: []string{getApiServerBin(),
|
||||||
|
"--v", "2", "--logtostderr", "--log_dir", "./",
|
||||||
|
"--etcd-servers", "http://127.0.0.1:4001",
|
||||||
|
"--insecure-bind-address", "0.0.0.0",
|
||||||
|
"--service-cluster-ip-range", "10.0.0.1/24",
|
||||||
|
"--kubelet-port", "10250"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) startKubeletServer() (*exec.Cmd, error) {
|
||||||
|
return es.startServer(
|
||||||
|
healthCheckCommand{
|
||||||
|
combinedOut: &es.kubeletCombinedOut,
|
||||||
|
healthCheckUrl: "http://127.0.0.1:10255/healthz",
|
||||||
|
command: "sudo",
|
||||||
|
args: []string{getKubeletServerBin(),
|
||||||
|
"--v", "2", "--logtostderr", "--log_dir", "./",
|
||||||
|
"--api-servers", "http://127.0.0.1:8080",
|
||||||
|
"--address", "0.0.0.0",
|
||||||
|
"--port", "10250"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *e2eService) startServer(hcc healthCheckCommand) (*exec.Cmd, error) {
|
||||||
|
cmdErrorChan := make(chan error)
|
||||||
|
cmd := exec.Command(hcc.command, hcc.args...)
|
||||||
|
cmd.Stdout = hcc.combinedOut
|
||||||
|
cmd.Stderr = hcc.combinedOut
|
||||||
|
go func() {
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
cmdErrorChan <- fmt.Errorf("%v Exited with status %v. Output:\n%s", hcc, err, *hcc.combinedOut)
|
||||||
|
}
|
||||||
|
close(cmdErrorChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
endTime := time.Now().Add(*serverStartTimeout)
|
||||||
|
for endTime.After(time.Now()) {
|
||||||
|
select {
|
||||||
|
case err := <-cmdErrorChan:
|
||||||
|
return nil, err
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
resp, err := http.Get(hcc.healthCheckUrl)
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Timeout waiting for service %v", hcc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type healthCheckCommand struct {
|
||||||
|
healthCheckUrl string
|
||||||
|
command string
|
||||||
|
args []string
|
||||||
|
combinedOut *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hcc *healthCheckCommand) String() string {
|
||||||
|
return fmt.Sprintf("`%s %s` %s", hcc.command, strings.Join(hcc.args, " "), hcc.healthCheckUrl)
|
||||||
|
}
|
@ -28,8 +28,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
||||||
)
|
)
|
||||||
|
|
||||||
const success = "\033[0;32mSUCESS\033[0m"
|
const success = "\033[0;32mSUCESS\033[0m"
|
||||||
|
@ -1,213 +0,0 @@
|
|||||||
/*
|
|
||||||
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 gcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
)
|
|
||||||
|
|
||||||
var freePortRegexp = regexp.MustCompile(".+:([0-9]+)")
|
|
||||||
|
|
||||||
type TearDown func() *RunResult
|
|
||||||
|
|
||||||
type GCloudClient interface {
|
|
||||||
RunAndWaitTillHealthy(
|
|
||||||
sudo bool, copyBin bool, remotePort string,
|
|
||||||
timeout time.Duration, healthUrl string, bin string, args ...string) (*CmdHandle, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type gCloudClientImpl struct {
|
|
||||||
host string
|
|
||||||
zone string
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunResult struct {
|
|
||||||
out []byte
|
|
||||||
err error
|
|
||||||
cmd string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CmdHandle struct {
|
|
||||||
TearDown TearDown
|
|
||||||
CombinedOutput bytes.Buffer
|
|
||||||
Output chan RunResult
|
|
||||||
LPort string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGCloudClient(host string, zone string) GCloudClient {
|
|
||||||
return &gCloudClientImpl{host, zone}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *gCloudClientImpl) Command(cmd string, moreargs ...string) ([]byte, error) {
|
|
||||||
args := append([]string{"compute", "ssh"})
|
|
||||||
if gc.zone != "" {
|
|
||||||
args = append(args, "--zone", gc.zone)
|
|
||||||
}
|
|
||||||
args = append(args, gc.host, "--", cmd)
|
|
||||||
args = append(args, moreargs...)
|
|
||||||
glog.V(2).Infof("Command gcloud %s", strings.Join(args, " "))
|
|
||||||
return exec.Command("gcloud", args...).CombinedOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *gCloudClientImpl) TunnelCommand(sudo bool, lPort string, rPort string, dir string, cmd string, moreargs ...string) *exec.Cmd {
|
|
||||||
tunnelStr := fmt.Sprintf("-L %s:localhost:%s", lPort, rPort)
|
|
||||||
args := []string{"compute", "ssh"}
|
|
||||||
if gc.zone != "" {
|
|
||||||
args = append(args, "--zone", gc.zone)
|
|
||||||
}
|
|
||||||
args = append(args, "--ssh-flag", tunnelStr, gc.host, "--")
|
|
||||||
args = append(args, "cd", dir, ";")
|
|
||||||
if sudo {
|
|
||||||
args = append(args, "sudo")
|
|
||||||
}
|
|
||||||
args = append(args, cmd)
|
|
||||||
args = append(args, moreargs...)
|
|
||||||
glog.V(2).Infof("Command gcloud %s", strings.Join(args, " "))
|
|
||||||
return exec.Command("gcloud", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *gCloudClientImpl) CopyToHost(from string, to string) ([]byte, error) {
|
|
||||||
rto := fmt.Sprintf("%s:%s", gc.host, to)
|
|
||||||
args := []string{"compute", "copy-files"}
|
|
||||||
if gc.zone != "" {
|
|
||||||
args = append(args, "--zone", gc.zone)
|
|
||||||
}
|
|
||||||
args = append(args, from, rto)
|
|
||||||
glog.V(2).Infof("Command gcloud %s", strings.Join(args, " "))
|
|
||||||
return exec.Command("gcloud", args...).CombinedOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *gCloudClientImpl) Run(
|
|
||||||
sudo bool, copyBin bool, remotePort string, bin string, args ...string) *CmdHandle {
|
|
||||||
|
|
||||||
h := &CmdHandle{}
|
|
||||||
h.Output = make(chan RunResult)
|
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
// Define where we will copy the temp binary
|
|
||||||
tDir := fmt.Sprintf("/tmp/gcloud-e2e-%d", rand.Int31())
|
|
||||||
_, f := filepath.Split(bin)
|
|
||||||
cmd := f
|
|
||||||
if copyBin {
|
|
||||||
cmd = filepath.Join(tDir, f)
|
|
||||||
}
|
|
||||||
h.LPort = getLocalPort()
|
|
||||||
|
|
||||||
h.TearDown = func() *RunResult {
|
|
||||||
out, err := gc.Command("sudo", "pkill", f)
|
|
||||||
if err != nil {
|
|
||||||
return &RunResult{out, err, fmt.Sprintf("pkill %s", f)}
|
|
||||||
}
|
|
||||||
out, err = gc.Command("rm", "-rf", tDir)
|
|
||||||
if err != nil {
|
|
||||||
return &RunResult{out, err, fmt.Sprintf("rm -rf %s", tDir)}
|
|
||||||
}
|
|
||||||
return &RunResult{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the commands in a Go fn so that this method doesn't block when writing to a channel
|
|
||||||
// to report an error
|
|
||||||
go func() {
|
|
||||||
// Create the tmp directory
|
|
||||||
out, err := gc.Command("mkdir", "-p", tDir)
|
|
||||||
|
|
||||||
// Work around for gcloud flakiness - TODO: debug why gcloud sometimes cannot find credentials for some hosts
|
|
||||||
// If there was an error about credentials, retry making the directory 6 times to see if it can be resolved
|
|
||||||
// This is to help debug if the credential issues are persistent for a given host on a given run, or transient
|
|
||||||
// And if downstream gcloud commands are also impacted
|
|
||||||
for i := 0; i < 6 && err != nil && strings.Contains(string(out), "does not have any valid credentials"); i++ {
|
|
||||||
glog.Warningf("mkdir failed on host %s due to credential issues, retrying in 5 seconds %v %s", gc.host, err, out)
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
out, err = gc.Command("mkdir", "-p", tDir)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("mkdir failed %v %s", err, out)
|
|
||||||
h.Output <- RunResult{out, err, fmt.Sprintf("mkdir -p %s", tDir)}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the binary
|
|
||||||
if copyBin {
|
|
||||||
out, err = gc.CopyToHost(bin, tDir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("copy-files failed %v %s", err, out)
|
|
||||||
h.Output <- RunResult{out, err, fmt.Sprintf("copy-files %s %s", bin, tDir)}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := gc.TunnelCommand(sudo, h.LPort, remotePort, tDir, cmd, args...)
|
|
||||||
c.Stdout = &h.CombinedOutput
|
|
||||||
c.Stderr = &h.CombinedOutput
|
|
||||||
go func() {
|
|
||||||
// Start the process
|
|
||||||
err = c.Run()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("command failed %v %s", err, h.CombinedOutput.Bytes())
|
|
||||||
h.Output <- RunResult{h.CombinedOutput.Bytes(), err, fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}()
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *gCloudClientImpl) RunAndWaitTillHealthy(
|
|
||||||
sudo bool, copyBin bool,
|
|
||||||
remotePort string, timeout time.Duration, healthUrl string, bin string, args ...string) (*CmdHandle, error) {
|
|
||||||
h := gc.Run(sudo, copyBin, remotePort, bin, args...)
|
|
||||||
eTime := time.Now().Add(timeout)
|
|
||||||
done := false
|
|
||||||
for eTime.After(time.Now()) && !done {
|
|
||||||
select {
|
|
||||||
case r := <-h.Output:
|
|
||||||
glog.V(2).Infof("Error running %s Output:\n%s Error:\n%v", r.cmd, r.out, r.err)
|
|
||||||
return h, r.err
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%s/%s", h.LPort, healthUrl))
|
|
||||||
if err == nil && resp.StatusCode == http.StatusOK {
|
|
||||||
done = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !done {
|
|
||||||
return h, errors.New(fmt.Sprintf("Timeout waiting for service to be healthy at http://localhost:%s/%s", h.LPort, healthUrl))
|
|
||||||
}
|
|
||||||
glog.Info("Healthz Success")
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLocalPort returns a free local port that can be used for ssh tunneling
|
|
||||||
func getLocalPort() string {
|
|
||||||
l, _ := net.Listen("tcp", ":0")
|
|
||||||
defer l.Close()
|
|
||||||
return freePortRegexp.FindStringSubmatch(l.Addr().String())[1]
|
|
||||||
}
|
|
@ -249,7 +249,7 @@ var _ = Describe("Kubelet", func() {
|
|||||||
Expect(*container.Logs.UsedBytes).NotTo(BeZero(), spew.Sdump(container))
|
Expect(*container.Logs.UsedBytes).NotTo(BeZero(), spew.Sdump(container))
|
||||||
|
|
||||||
}
|
}
|
||||||
Expect(podsList).To(ConsistOf(podNames))
|
Expect(podsList).To(ConsistOf(podNames), spew.Sdump(summary))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -19,189 +19,72 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"runtime"
|
"k8s.io/kubernetes/test/e2e_node"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"k8s.io/kubernetes/test/e2e_node/gcloud"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RunFunc func(host string, port string) ([]byte, error)
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
host string
|
|
||||||
output []byte
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
const gray = "\033[1;30m"
|
|
||||||
const blue = "\033[0;34m"
|
|
||||||
const noColour = "\033[0m"
|
|
||||||
|
|
||||||
var u = sync.WaitGroup{}
|
|
||||||
var zone = flag.String("zone", "", "gce zone the hosts live in")
|
|
||||||
var hosts = flag.String("hosts", "", "hosts to test")
|
var hosts = flag.String("hosts", "", "hosts to test")
|
||||||
var wait = flag.Bool("wait", false, "if true, wait for input before running tests")
|
|
||||||
var kubeOutputRelPath = flag.String("k8s-build-output", "_output/local/bin/linux/amd64", "Where k8s binary files are written")
|
|
||||||
|
|
||||||
var kubeRoot = ""
|
|
||||||
|
|
||||||
const buildScriptRelPath = "hack/build-go.sh"
|
|
||||||
const ginkgoTestRelPath = "test/e2e_node"
|
|
||||||
const healthyTimeoutDuration = time.Minute * 3
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Setup coloring
|
||||||
|
stat, _ := os.Stdout.Stat()
|
||||||
|
useColor := (stat.Mode() & os.ModeCharDevice) != 0
|
||||||
|
blue := ""
|
||||||
|
noColour := ""
|
||||||
|
if useColor {
|
||||||
|
blue = "\033[0;34m"
|
||||||
|
noColour = "\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *hosts == "" {
|
if *hosts == "" {
|
||||||
glog.Fatalf("Must specific --hosts flag")
|
fmt.Printf("Must specific --hosts flag")
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out the kube root
|
|
||||||
_, path, _, _ := runtime.Caller(0)
|
|
||||||
kubeRoot, _ = filepath.Split(path)
|
|
||||||
kubeRoot = strings.Split(kubeRoot, "/test/e2e_node")[0]
|
|
||||||
|
|
||||||
// Build the go code
|
|
||||||
out, err := exec.Command(filepath.Join(kubeRoot, buildScriptRelPath)).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Failed to build go packages %s: %v", out, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy kubelet to each host and run test
|
|
||||||
if *wait {
|
|
||||||
u.Add(1)
|
|
||||||
}
|
}
|
||||||
|
archive := e2e_node.CreateTestArchive()
|
||||||
|
defer os.Remove(archive)
|
||||||
|
|
||||||
results := make(chan *TestResult)
|
results := make(chan *TestResult)
|
||||||
hs := strings.Split(*hosts, ",")
|
hs := strings.Split(*hosts, ",")
|
||||||
for _, h := range hs {
|
for _, h := range hs {
|
||||||
go func(host string) { results <- runTests(host) }(h)
|
fmt.Printf("Starting tests on host %s.", h)
|
||||||
}
|
go func(host string) {
|
||||||
|
output, err := e2e_node.RunRemote(archive, host)
|
||||||
// Maybe wait for user input before running tests
|
results <- &TestResult{
|
||||||
if *wait {
|
output: output,
|
||||||
WaitForUser()
|
err: err,
|
||||||
|
host: host,
|
||||||
|
}
|
||||||
|
}(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all tests to complete and emit the results
|
// Wait for all tests to complete and emit the results
|
||||||
errCount := 0
|
errCount := 0
|
||||||
for i := 0; i < len(hs); i++ {
|
for i := 0; i < len(hs); i++ {
|
||||||
tr := <-results
|
tr := <-results
|
||||||
host := tr.fullhost
|
host := tr.host
|
||||||
|
fmt.Printf("%s================================================================%s\n", blue, noColour)
|
||||||
if tr.err != nil {
|
if tr.err != nil {
|
||||||
errCount++
|
errCount++
|
||||||
glog.Infof("%s================================================================%s", blue, noColour)
|
fmt.Printf("Failure Finished Host %s Test Suite %s %v\n", host, tr.output, tr.err)
|
||||||
glog.Infof("Failure Finished Host %s Test Suite %s %v", host, tr.testCombinedOutput, tr.err)
|
|
||||||
glog.V(2).Infof("----------------------------------------------------------------")
|
|
||||||
glog.V(5).Infof("Host %s Etcd Logs\n%s%s%s", host, gray, tr.etcdCombinedOutput, noColour)
|
|
||||||
glog.V(5).Infof("----------------------------------------------------------------")
|
|
||||||
glog.V(5).Infof("Host %s Apiserver Logs\n%s%s%s", host, gray, tr.apiServerCombinedOutput, noColour)
|
|
||||||
glog.V(5).Infof("----------------------------------------------------------------")
|
|
||||||
glog.V(2).Infof("Host %s Kubelet Logs\n%s%s%s", host, gray, tr.kubeletCombinedOutput, noColour)
|
|
||||||
glog.Infof("%s================================================================%s", blue, noColour)
|
|
||||||
} else {
|
} else {
|
||||||
glog.Infof("================================================================")
|
fmt.Printf("Success Finished Host %s Test Suite %s\n", host, tr.output)
|
||||||
glog.Infof("Success Finished Host %s Test Suite %s", host, tr.testCombinedOutput)
|
|
||||||
glog.Infof("================================================================")
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf("%s================================================================%s\n", blue, noColour)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the exit code if there were failures
|
// Set the exit code if there were failures
|
||||||
if errCount > 0 {
|
if errCount > 0 {
|
||||||
glog.Errorf("Failure: %d errors encountered.", errCount)
|
fmt.Printf("Failure: %d errors encountered.", errCount)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WaitForUser() {
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Enter \"y\" to run tests\n")
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.ToUpper(scanner.Text()) != "Y\n" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fmt.Printf("Enter \"y\" to run tests\n")
|
|
||||||
}
|
|
||||||
u.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
type TestResult struct {
|
||||||
fullhost string
|
output string
|
||||||
err error
|
err error
|
||||||
testCombinedOutput string
|
host string
|
||||||
etcdCombinedOutput string
|
|
||||||
apiServerCombinedOutput string
|
|
||||||
kubeletCombinedOutput string
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests(fullhost string) *TestResult {
|
|
||||||
result := &TestResult{fullhost: fullhost}
|
|
||||||
|
|
||||||
host := strings.Split(fullhost, ".")[0]
|
|
||||||
c := gcloud.NewGCloudClient(host, *zone)
|
|
||||||
// TODO(pwittrock): Come up with something better for bootstrapping the environment.
|
|
||||||
eh, err := c.RunAndWaitTillHealthy(
|
|
||||||
false, false, "4001", healthyTimeoutDuration, "v2/keys/", "etcd", "--data-dir", "./", "--name", "e2e-node")
|
|
||||||
defer func() {
|
|
||||||
eh.TearDown()
|
|
||||||
result.etcdCombinedOutput = fmt.Sprintf("%s", eh.CombinedOutput.Bytes())
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
result.err = fmt.Errorf("Host %s failed to run command %v", host, err)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
apiBin := filepath.Join(kubeRoot, *kubeOutputRelPath, "kube-apiserver")
|
|
||||||
ah, err := c.RunAndWaitTillHealthy(
|
|
||||||
true, true, "8080", healthyTimeoutDuration, "healthz", apiBin, "--service-cluster-ip-range",
|
|
||||||
"10.0.0.1/24", "--insecure-bind-address", "0.0.0.0", "--etcd-servers", "http://127.0.0.1:4001",
|
|
||||||
"--v", "2", "--alsologtostderr", "--kubelet-port", "10250")
|
|
||||||
defer func() {
|
|
||||||
ah.TearDown()
|
|
||||||
result.apiServerCombinedOutput = fmt.Sprintf("%s", ah.CombinedOutput.Bytes())
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
result.err = fmt.Errorf("Host %s failed to run command %v", host, err)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeletBin := filepath.Join(kubeRoot, *kubeOutputRelPath, "kubelet")
|
|
||||||
// TODO: Used --v 4 or higher and upload to gcs instead of printing to the console
|
|
||||||
// TODO: Copy /var/log/messages and upload to GCS for failed tests
|
|
||||||
kh, err := c.RunAndWaitTillHealthy(
|
|
||||||
true, true, "10255", healthyTimeoutDuration, "healthz", kubeletBin, "--api-servers", "http://127.0.0.1:8080",
|
|
||||||
"--v", "2", "--alsologtostderr", "--address", "0.0.0.0", "--port", "10250")
|
|
||||||
defer func() {
|
|
||||||
kh.TearDown()
|
|
||||||
result.kubeletCombinedOutput = fmt.Sprintf("%s", kh.CombinedOutput.Bytes())
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
result.err = fmt.Errorf("Host %s failed to run command %v", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the tests
|
|
||||||
glog.Infof("Kubelet healthy on host %s", host)
|
|
||||||
glog.Infof("Kubelet host %s tunnel running on port %s", host, ah.LPort)
|
|
||||||
u.Wait()
|
|
||||||
glog.Infof("Running ginkgo tests against host %s", host)
|
|
||||||
ginkgoTests := filepath.Join(kubeRoot, ginkgoTestRelPath)
|
|
||||||
out, err := exec.Command(
|
|
||||||
"ginkgo", ginkgoTests, "--",
|
|
||||||
"--kubelet-address", fmt.Sprintf("http://127.0.0.1:%s", kh.LPort),
|
|
||||||
"--api-server-address", fmt.Sprintf("http://127.0.0.1:%s", ah.LPort),
|
|
||||||
"--node-name", fullhost,
|
|
||||||
"--v", "2", "--alsologtostderr").CombinedOutput()
|
|
||||||
|
|
||||||
result.err = err
|
|
||||||
result.testCombinedOutput = fmt.Sprintf("%s", out)
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user