123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- /*
- 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 (
- "flag"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
- "time"
- utilerrors "k8s.io/apimachinery/pkg/util/errors"
- "k8s.io/klog"
- )
- var testTimeout = flag.Duration("test-timeout", 45*time.Minute, "How long (in golang duration format) to wait for ginkgo tests to complete.")
- var resultsDir = flag.String("results-dir", "/tmp/", "Directory to scp test results to.")
- const archiveName = "e2e_node_test.tar.gz"
- // CreateTestArchive creates the archive package for the node e2e test.
- func CreateTestArchive(suite TestSuite, systemSpecName string) (string, error) {
- klog.V(2).Infof("Building archive...")
- tardir, err := ioutil.TempDir("", "node-e2e-archive")
- if err != nil {
- return "", fmt.Errorf("failed to create temporary directory %v", err)
- }
- defer os.RemoveAll(tardir)
- // Call the suite function to setup the test package.
- err = suite.SetupTestPackage(tardir, systemSpecName)
- if err != nil {
- return "", fmt.Errorf("failed to setup test package %q: %v", tardir, err)
- }
- // Build the tar
- out, err := exec.Command("tar", "-zcvf", archiveName, "-C", tardir, ".").CombinedOutput()
- if err != nil {
- return "", fmt.Errorf("failed to build tar %v. Output:\n%s", err, out)
- }
- dir, err := os.Getwd()
- if err != nil {
- return "", fmt.Errorf("failed to get working directory %v", err)
- }
- return filepath.Join(dir, archiveName), nil
- }
- // RunRemote returns the command output, whether the exit was ok, and any errors
- // TODO(random-liu): junitFilePrefix is not prefix actually, the file name is junit-junitFilePrefix.xml. Change the variable name.
- func RunRemote(suite TestSuite, archive string, host string, cleanup bool, imageDesc, junitFilePrefix string, testArgs string, ginkgoArgs string, systemSpecName string, extraEnvs string) (string, bool, error) {
- // Create the temp staging directory
- klog.V(2).Infof("Staging test binaries on %q", host)
- workspace := newWorkspaceDir()
- // Do not sudo here, so that we can use scp to copy test archive to the directory.
- if output, err := SSHNoSudo(host, "mkdir", workspace); err != nil {
- // Exit failure with the error
- return "", false, fmt.Errorf("failed to create workspace directory %q on host %q: %v output: %q", workspace, host, err, output)
- }
- if cleanup {
- defer func() {
- output, err := SSH(host, "rm", "-rf", workspace)
- if err != nil {
- klog.Errorf("failed to cleanup workspace %q on host %q: %v. Output:\n%s", workspace, host, err, output)
- }
- }()
- }
- // Copy the archive to the staging directory
- if output, err := runSSHCommand("scp", archive, fmt.Sprintf("%s:%s/", GetHostnameOrIP(host), workspace)); err != nil {
- // Exit failure with the error
- return "", false, fmt.Errorf("failed to copy test archive: %v, output: %q", err, output)
- }
- // Extract the archive
- cmd := getSSHCommand(" && ",
- fmt.Sprintf("cd %s", workspace),
- fmt.Sprintf("tar -xzvf ./%s", archiveName),
- )
- klog.V(2).Infof("Extracting tar on %q", host)
- // Do not use sudo here, because `sudo tar -x` will recover the file ownership inside the tar ball, but
- // we want the extracted files to be owned by the current user.
- if output, err := SSHNoSudo(host, "sh", "-c", cmd); err != nil {
- // Exit failure with the error
- return "", false, fmt.Errorf("failed to extract test archive: %v, output: %q", err, output)
- }
- // Create the test result directory.
- resultDir := filepath.Join(workspace, "results")
- if output, err := SSHNoSudo(host, "mkdir", resultDir); err != nil {
- // Exit failure with the error
- return "", false, fmt.Errorf("failed to create test result directory %q on host %q: %v output: %q", resultDir, host, err, output)
- }
- klog.V(2).Infof("Running test on %q", host)
- output, err := suite.RunTest(host, workspace, resultDir, imageDesc, junitFilePrefix, testArgs, ginkgoArgs, systemSpecName, extraEnvs, *testTimeout)
- aggErrs := []error{}
- // Do not log the output here, let the caller deal with the test output.
- if err != nil {
- aggErrs = append(aggErrs, err)
- collectSystemLog(host)
- }
- klog.V(2).Infof("Copying test artifacts from %q", host)
- scpErr := getTestArtifacts(host, workspace)
- if scpErr != nil {
- aggErrs = append(aggErrs, scpErr)
- }
- return output, len(aggErrs) == 0, utilerrors.NewAggregate(aggErrs)
- }
- const (
- // workspaceDirPrefix is the string prefix used in the workspace directory name.
- workspaceDirPrefix = "node-e2e-"
- // timestampFormat is the timestamp format used in the node e2e directory name.
- timestampFormat = "20060102T150405"
- )
- func getTimestamp() string {
- return fmt.Sprint(time.Now().Format(timestampFormat))
- }
- func newWorkspaceDir() string {
- return filepath.Join("/tmp", workspaceDirPrefix+getTimestamp())
- }
- // GetTimestampFromWorkspaceDir parses the workspace directory name and gets the timestamp part of it.
- // This can later be used to name other artifacts (such as the
- // kubelet-${instance}.service systemd transient service used to launch
- // Kubelet) so that they can be matched to each other.
- func GetTimestampFromWorkspaceDir(dir string) string {
- dirTimestamp := strings.TrimPrefix(filepath.Base(dir), workspaceDirPrefix)
- re := regexp.MustCompile("^\\d{8}T\\d{6}$")
- if re.MatchString(dirTimestamp) {
- return dirTimestamp
- }
- // Fallback: if we can't find that timestamp, default to using Now()
- return getTimestamp()
- }
- func getTestArtifacts(host, testDir string) error {
- logPath := filepath.Join(*resultsDir, host)
- if err := os.MkdirAll(logPath, 0755); err != nil {
- return fmt.Errorf("failed to create log directory %q: %v", logPath, err)
- }
- // Copy logs to artifacts/hostname
- if _, err := runSSHCommand("scp", "-r", fmt.Sprintf("%s:%s/results/*.log", GetHostnameOrIP(host), testDir), logPath); err != nil {
- return err
- }
- // Copy json files (if any) to artifacts.
- if _, err := SSH(host, "ls", fmt.Sprintf("%s/results/*.json", testDir)); err == nil {
- if _, err = runSSHCommand("scp", "-r", fmt.Sprintf("%s:%s/results/*.json", GetHostnameOrIP(host), testDir), *resultsDir); err != nil {
- return err
- }
- }
- if _, err := SSH(host, "ls", fmt.Sprintf("%s/results/junit*", testDir)); err == nil {
- // Copy junit (if any) to the top of artifacts
- if _, err = runSSHCommand("scp", fmt.Sprintf("%s:%s/results/junit*", GetHostnameOrIP(host), testDir), *resultsDir); err != nil {
- return err
- }
- }
- return nil
- }
- // collectSystemLog is a temporary hack to collect system log when encountered on
- // unexpected error.
- func collectSystemLog(host string) {
- // Encountered an unexpected error. The remote test harness may not
- // have finished retrieved and stored all the logs in this case. Try
- // to get some logs for debugging purposes.
- // TODO: This is a best-effort, temporary hack that only works for
- // journald nodes. We should have a more robust way to collect logs.
- var (
- logName = "system.log"
- logPath = fmt.Sprintf("/tmp/%s-%s", getTimestamp(), logName)
- destPath = fmt.Sprintf("%s/%s-%s", *resultsDir, host, logName)
- )
- klog.V(2).Infof("Test failed unexpectedly. Attempting to retrieving system logs (only works for nodes with journald)")
- // Try getting the system logs from journald and store it to a file.
- // Don't reuse the original test directory on the remote host because
- // it could've be been removed if the node was rebooted.
- if output, err := SSH(host, "sh", "-c", fmt.Sprintf("'journalctl --system --all > %s'", logPath)); err == nil {
- klog.V(2).Infof("Got the system logs from journald; copying it back...")
- if output, err := runSSHCommand("scp", fmt.Sprintf("%s:%s", GetHostnameOrIP(host), logPath), destPath); err != nil {
- klog.V(2).Infof("Failed to copy the log: err: %v, output: %q", err, output)
- }
- } else {
- klog.V(2).Infof("Failed to run journactl (normal if it doesn't exist on the node): %v, output: %q", err, output)
- }
- }
- // WriteLog is a temporary function to make it possible to write log
- // in the runner. This is used to collect serial console log.
- // TODO(random-liu): Use the log-dump script in cluster e2e.
- func WriteLog(host, filename, content string) error {
- logPath := filepath.Join(*resultsDir, host)
- if err := os.MkdirAll(logPath, 0755); err != nil {
- return fmt.Errorf("failed to create log directory %q: %v", logPath, err)
- }
- f, err := os.Create(filepath.Join(logPath, filename))
- if err != nil {
- return err
- }
- defer f.Close()
- _, err = f.WriteString(content)
- return err
- }
|