2016-12-05 22:20:50 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
|
|
|
|
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 remote
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2018-11-09 18:49:10 +00:00
|
|
|
"k8s.io/klog"
|
2016-12-05 22:20:50 +00:00
|
|
|
|
|
|
|
"k8s.io/kubernetes/test/e2e_node/builder"
|
2018-04-27 15:12:10 +00:00
|
|
|
"k8s.io/kubernetes/test/utils"
|
2016-12-05 22:20:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ConformanceRemote contains the specific functions in the node conformance test suite.
|
|
|
|
type ConformanceRemote struct{}
|
|
|
|
|
|
|
|
func InitConformanceRemote() TestSuite {
|
|
|
|
return &ConformanceRemote{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// getConformanceDirectory gets node conformance test build directory.
|
|
|
|
func getConformanceDirectory() (string, error) {
|
2018-04-27 15:12:10 +00:00
|
|
|
k8sRoot, err := utils.GetK8sRootDir()
|
2016-12-05 22:20:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return filepath.Join(k8sRoot, "test", "e2e_node", "conformance", "build"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// commandToString is a helper function which formats command to string.
|
|
|
|
func commandToString(c *exec.Cmd) string {
|
|
|
|
return strings.Join(append([]string{c.Path}, c.Args[1:]...), " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Image path constants.
|
|
|
|
const (
|
Switch to k8s.gcr.io vanity domain
This is the 2nd attempt. The previous was reverted while we figured out
the regional mirrors (oops).
New plan: k8s.gcr.io is a read-only facade that auto-detects your source
region (us, eu, or asia for now) and pulls from the closest. To publish
an image, push k8s-staging.gcr.io and it will be synced to the regionals
automatically (similar to today). For now the staging is an alias to
gcr.io/google_containers (the legacy URL).
When we move off of google-owned projects (working on it), then we just
do a one-time sync, and change the google-internal config, and nobody
outside should notice.
We can, in parallel, change the auto-sync into a manual sync - send a PR
to "promote" something from staging, and a bot activates it. Nice and
visible, easy to keep track of.
2018-01-17 19:36:53 +00:00
|
|
|
conformanceRegistry = "k8s.gcr.io"
|
2016-12-05 22:20:50 +00:00
|
|
|
conformanceArch = runtime.GOARCH
|
|
|
|
conformanceTarfile = "node_conformance.tar"
|
|
|
|
conformanceTestBinary = "e2e_node.test"
|
|
|
|
conformanceImageLoadTimeout = time.Duration(30) * time.Second
|
|
|
|
)
|
|
|
|
|
|
|
|
// timestamp is used as an unique id of current test.
|
|
|
|
var timestamp = getTimestamp()
|
|
|
|
|
2017-06-03 00:00:46 +00:00
|
|
|
// getConformanceTestImageName returns name of the conformance test image given the system spec name.
|
|
|
|
func getConformanceTestImageName(systemSpecName string) string {
|
|
|
|
if systemSpecName == "" {
|
|
|
|
return fmt.Sprintf("%s/node-test-%s:%s", conformanceRegistry, conformanceArch, timestamp)
|
|
|
|
} else {
|
|
|
|
return fmt.Sprintf("%s/node-test-%s-%s:%s", conformanceRegistry, systemSpecName, conformanceArch, timestamp)
|
|
|
|
}
|
2016-12-05 22:20:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// buildConformanceTest builds node conformance test image tarball into binDir.
|
2017-06-03 00:00:46 +00:00
|
|
|
func buildConformanceTest(binDir, systemSpecName string) error {
|
2016-12-05 22:20:50 +00:00
|
|
|
// Get node conformance directory.
|
|
|
|
conformancePath, err := getConformanceDirectory()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get node conformance directory: %v", err)
|
|
|
|
}
|
|
|
|
// Build docker image.
|
|
|
|
cmd := exec.Command("make", "-C", conformancePath, "BIN_DIR="+binDir,
|
|
|
|
"REGISTRY="+conformanceRegistry,
|
|
|
|
"ARCH="+conformanceArch,
|
2017-06-03 00:00:46 +00:00
|
|
|
"VERSION="+timestamp,
|
|
|
|
"SYSTEM_SPEC_NAME="+systemSpecName)
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
|
|
return fmt.Errorf("failed to build node conformance docker image: command - %q, error - %v, output - %q",
|
|
|
|
commandToString(cmd), err, output)
|
|
|
|
}
|
|
|
|
// Save docker image into tar file.
|
2017-06-03 00:00:46 +00:00
|
|
|
cmd = exec.Command("docker", "save", "-o", filepath.Join(binDir, conformanceTarfile), getConformanceTestImageName(systemSpecName))
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
|
|
return fmt.Errorf("failed to save node conformance docker image into tar file: command - %q, error - %v, output - %q",
|
|
|
|
commandToString(cmd), err, output)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetupTestPackage sets up the test package with binaries k8s required for node conformance test
|
2017-06-03 00:00:46 +00:00
|
|
|
func (c *ConformanceRemote) SetupTestPackage(tardir, systemSpecName string) error {
|
2016-12-05 22:20:50 +00:00
|
|
|
// Build the executables
|
|
|
|
if err := builder.BuildGo(); err != nil {
|
2018-02-09 06:53:53 +00:00
|
|
|
return fmt.Errorf("failed to build the dependencies: %v", err)
|
2016-12-05 22:20:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we can find the newly built binaries
|
2018-04-27 15:12:10 +00:00
|
|
|
buildOutputDir, err := utils.GetK8sBuildOutputDir()
|
2016-12-05 22:20:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to locate kubernetes build output directory %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build node conformance tarball.
|
2017-06-03 00:00:46 +00:00
|
|
|
if err := buildConformanceTest(buildOutputDir, systemSpecName); err != nil {
|
|
|
|
return fmt.Errorf("failed to build node conformance test: %v", err)
|
2016-12-05 22:20:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy files
|
|
|
|
requiredFiles := []string{"kubelet", conformanceTestBinary, conformanceTarfile}
|
|
|
|
for _, file := range requiredFiles {
|
|
|
|
source := filepath.Join(buildOutputDir, file)
|
|
|
|
if _, err := os.Stat(source); err != nil {
|
|
|
|
return fmt.Errorf("failed to locate test file %s: %v", file, err)
|
|
|
|
}
|
|
|
|
output, err := exec.Command("cp", source, filepath.Join(tardir, file)).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to copy %q: error - %v output - %q", file, err, output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadConformanceImage loads node conformance image from tar file.
|
|
|
|
func loadConformanceImage(host, workspace string) error {
|
|
|
|
tarfile := filepath.Join(workspace, conformanceTarfile)
|
|
|
|
if output, err := SSH(host, "timeout", conformanceImageLoadTimeout.String(),
|
|
|
|
"docker", "load", "-i", tarfile); err != nil {
|
|
|
|
return fmt.Errorf("failed to load node conformance image from tar file %q: error - %v output - %q",
|
|
|
|
tarfile, err, output)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// kubeletLauncherLog is the log of kubelet launcher.
|
|
|
|
const kubeletLauncherLog = "kubelet-launcher.log"
|
|
|
|
|
2018-02-23 00:43:16 +00:00
|
|
|
// kubeletPodPath is a fixed known pod specification path. We can not use the random pod
|
2016-12-05 22:20:50 +00:00
|
|
|
// manifest directory generated in e2e_node.test because we need to mount the directory into
|
|
|
|
// the conformance test container, it's easier if it's a known directory.
|
|
|
|
// TODO(random-liu): Get rid of this once we switch to cluster e2e node bootstrap script.
|
2018-02-23 00:43:16 +00:00
|
|
|
var kubeletPodPath = "conformance-pod-manifest-" + timestamp
|
2016-12-05 22:20:50 +00:00
|
|
|
|
2018-02-23 00:43:16 +00:00
|
|
|
// getPodPath returns pod manifest full path.
|
|
|
|
func getPodPath(workspace string) string {
|
|
|
|
return filepath.Join(workspace, kubeletPodPath)
|
2016-12-05 22:20:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// isSystemd returns whether the node is a systemd node.
|
|
|
|
func isSystemd(host string) (bool, error) {
|
|
|
|
// Returns "systemd" if /run/systemd/system is found, empty string otherwise.
|
|
|
|
output, err := SSH(host, "test", "-e", "/run/systemd/system", "&&", "echo", "systemd", "||", "true")
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("failed to check systemd: error - %v output - %q", err, output)
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(output) != "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// launchKubelet launches kubelet by running e2e_node.test binary in run-kubelet-mode.
|
|
|
|
// This is a temporary solution, we should change node e2e to use the same node bootstrap
|
|
|
|
// with cluster e2e and launch kubelet outside of the test for both regular node e2e and
|
|
|
|
// node conformance test.
|
|
|
|
// TODO(random-liu): Switch to use standard node bootstrap script.
|
|
|
|
func launchKubelet(host, workspace, results, testArgs string) error {
|
2018-02-23 00:43:16 +00:00
|
|
|
podManifestPath := getPodPath(workspace)
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := SSH(host, "mkdir", podManifestPath); err != nil {
|
|
|
|
return fmt.Errorf("failed to create kubelet pod manifest path %q: error - %v output - %q",
|
|
|
|
podManifestPath, err, output)
|
|
|
|
}
|
|
|
|
startKubeletCmd := fmt.Sprintf("./%s --run-kubelet-mode --logtostderr --node-name=%s"+
|
|
|
|
" --report-dir=%s %s --kubelet-flags=--pod-manifest-path=%s > %s 2>&1",
|
|
|
|
conformanceTestBinary, host, results, testArgs, podManifestPath, filepath.Join(results, kubeletLauncherLog))
|
|
|
|
var cmd []string
|
|
|
|
systemd, err := isSystemd(host)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to check systemd: %v", err)
|
|
|
|
}
|
|
|
|
if systemd {
|
|
|
|
cmd = []string{
|
|
|
|
"systemd-run", "sh", "-c", getSSHCommand(" && ",
|
|
|
|
// Switch to workspace.
|
|
|
|
fmt.Sprintf("cd %s", workspace),
|
|
|
|
// Launch kubelet by running e2e_node.test in run-kubelet-mode.
|
|
|
|
startKubeletCmd,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cmd = []string{
|
|
|
|
"sh", "-c", getSSHCommand(" && ",
|
|
|
|
// Switch to workspace.
|
|
|
|
fmt.Sprintf("cd %s", workspace),
|
|
|
|
// Launch kubelet by running e2e_node.test in run-kubelet-mode with nohup.
|
|
|
|
fmt.Sprintf("(nohup %s &)", startKubeletCmd),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.V(2).Infof("Launch kubelet with command: %v", cmd)
|
2016-12-05 22:20:50 +00:00
|
|
|
output, err := SSH(host, cmd...)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to launch kubelet with command %v: error - %v output - %q",
|
|
|
|
cmd, err, output)
|
|
|
|
}
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Info("Successfully launch kubelet")
|
2016-12-05 22:20:50 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// kubeletStopGracePeriod is the grace period to wait before forcibly killing kubelet.
|
|
|
|
const kubeletStopGracePeriod = 10 * time.Second
|
|
|
|
|
|
|
|
// stopKubelet stops kubelet launcher and kubelet gracefully.
|
|
|
|
func stopKubelet(host, workspace string) error {
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Info("Gracefully stop kubelet launcher")
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := SSH(host, "pkill", conformanceTestBinary); err != nil {
|
|
|
|
return fmt.Errorf("failed to gracefully stop kubelet launcher: error - %v output - %q",
|
|
|
|
err, output)
|
|
|
|
}
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Info("Wait for kubelet launcher to stop")
|
2016-12-05 22:20:50 +00:00
|
|
|
stopped := false
|
|
|
|
for start := time.Now(); time.Since(start) < kubeletStopGracePeriod; time.Sleep(time.Second) {
|
2017-05-15 04:07:36 +00:00
|
|
|
// Check whether the process is still running.
|
2016-12-05 22:20:50 +00:00
|
|
|
output, err := SSH(host, "pidof", conformanceTestBinary, "||", "true")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to check kubelet stopping: error - %v output -%q",
|
|
|
|
err, output)
|
|
|
|
}
|
|
|
|
// Kubelet is stopped
|
|
|
|
if strings.TrimSpace(output) == "" {
|
|
|
|
stopped = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !stopped {
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Info("Forcibly stop kubelet")
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := SSH(host, "pkill", "-SIGKILL", conformanceTestBinary); err != nil {
|
|
|
|
return fmt.Errorf("failed to forcibly stop kubelet: error - %v output - %q",
|
|
|
|
err, output)
|
|
|
|
}
|
|
|
|
}
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Info("Successfully stop kubelet")
|
2016-12-05 22:20:50 +00:00
|
|
|
// Clean up the pod manifest path
|
2018-02-23 00:43:16 +00:00
|
|
|
podManifestPath := getPodPath(workspace)
|
2016-12-05 22:20:50 +00:00
|
|
|
if output, err := SSH(host, "rm", "-f", filepath.Join(workspace, podManifestPath)); err != nil {
|
|
|
|
return fmt.Errorf("failed to cleanup pod manifest directory %q: error - %v, output - %q",
|
|
|
|
podManifestPath, err, output)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RunTest runs test on the node.
|
2019-02-07 23:41:16 +00:00
|
|
|
func (c *ConformanceRemote) RunTest(host, workspace, results, imageDesc, junitFilePrefix, testArgs, _, systemSpecName, extraEnvs string, timeout time.Duration) (string, error) {
|
2017-04-11 23:23:54 +00:00
|
|
|
// Install the cni plugins and add a basic CNI configuration.
|
|
|
|
if err := setupCNI(host, workspace); err != nil {
|
2016-12-05 22:20:50 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure iptables firewall rules.
|
|
|
|
if err := configureFirewall(host); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kill any running node processes.
|
|
|
|
cleanupNodeProcesses(host)
|
|
|
|
|
|
|
|
// Load node conformance image.
|
|
|
|
if err := loadConformanceImage(host, workspace); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Launch kubelet.
|
|
|
|
if err := launchKubelet(host, workspace, results, testArgs); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// Stop kubelet.
|
|
|
|
defer func() {
|
|
|
|
if err := stopKubelet(host, workspace); err != nil {
|
|
|
|
// Only log an error if failed to stop kubelet because it is not critical.
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.Errorf("failed to stop kubelet: %v", err)
|
2016-12-05 22:20:50 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Run the tests
|
2018-11-09 18:49:10 +00:00
|
|
|
klog.V(2).Infof("Starting tests on %q", host)
|
2018-02-23 00:43:16 +00:00
|
|
|
podManifestPath := getPodPath(workspace)
|
2019-02-07 23:41:16 +00:00
|
|
|
cmd := fmt.Sprintf("'timeout -k 30s %fs docker run --rm --privileged=true --net=host -v /:/rootfs -v %s:%s -v %s:/var/result -e TEST_ARGS=--report-prefix=%s -e EXTRA_ENVS=%s %s'",
|
|
|
|
timeout.Seconds(), podManifestPath, podManifestPath, results, junitFilePrefix, extraEnvs, getConformanceTestImageName(systemSpecName))
|
2016-12-05 22:20:50 +00:00
|
|
|
testOutput, err := SSH(host, "sh", "-c", cmd)
|
|
|
|
if err != nil {
|
|
|
|
return testOutput, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return testOutput, nil
|
|
|
|
}
|