diff --git a/test/e2e/storage/persistent_volumes-local.go b/test/e2e/storage/persistent_volumes-local.go index 8e6db3b25e1..b5469217078 100644 --- a/test/e2e/storage/persistent_volumes-local.go +++ b/test/e2e/storage/persistent_volumes-local.go @@ -1182,9 +1182,10 @@ func validateStatefulSet(config *localTestConfig, ss *appsv1.StatefulSet, anti b // and skips if a disk of that type does not exist on the node func SkipUnlessLocalSSDExists(config *localTestConfig, ssdInterface, filesystemType string, node *v1.Node) { ssdCmd := fmt.Sprintf("ls -1 /mnt/disks/by-uuid/google-local-ssds-%s-%s/ | wc -l", ssdInterface, filesystemType) - res, err := config.hostExec.IssueCommandWithResult(ssdCmd, node) + res, err := config.hostExec.Execute(ssdCmd, node) + utils.LogResult(res) framework.ExpectNoError(err) - num, err := strconv.Atoi(strings.TrimSpace(res)) + num, err := strconv.Atoi(strings.TrimSpace(res.Stdout)) framework.ExpectNoError(err) if num < 1 { framework.Skipf("Requires at least 1 %s %s localSSD ", ssdInterface, filesystemType) diff --git a/test/e2e/storage/utils/BUILD b/test/e2e/storage/utils/BUILD index f06c92ddc41..aaeab2731f0 100644 --- a/test/e2e/storage/utils/BUILD +++ b/test/e2e/storage/utils/BUILD @@ -26,7 +26,9 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/util/exec:go_default_library", "//test/e2e/framework:go_default_library", + "//test/e2e/framework/log:go_default_library", "//test/e2e/framework/node:go_default_library", "//test/e2e/framework/pod:go_default_library", "//test/e2e/framework/ssh:go_default_library", diff --git a/test/e2e/storage/utils/host_exec.go b/test/e2e/storage/utils/host_exec.go index 4e6f4046db4..ac1146fdf10 100644 --- a/test/e2e/storage/utils/host_exec.go +++ b/test/e2e/storage/utils/host_exec.go @@ -20,12 +20,33 @@ import ( "fmt" v1 "k8s.io/api/core/v1" + "k8s.io/client-go/util/exec" "k8s.io/kubernetes/test/e2e/framework" + e2elog "k8s.io/kubernetes/test/e2e/framework/log" e2epod "k8s.io/kubernetes/test/e2e/framework/pod" ) +// Result holds the execution result of remote execution command. +type Result struct { + Host string + Cmd string + Stdout string + Stderr string + Code int +} + +// LogResult records result log +func LogResult(result Result) { + remote := result.Host + e2elog.Logf("exec %s: command: %s", remote, result.Cmd) + e2elog.Logf("exec %s: stdout: %q", remote, result.Stdout) + e2elog.Logf("exec %s: stderr: %q", remote, result.Stderr) + e2elog.Logf("exec %s: exit code: %d", remote, result.Code) +} + // HostExec represents interface we require to execute commands on remote host. type HostExec interface { + Execute(cmd string, node *v1.Node) (Result, error) IssueCommandWithResult(cmd string, node *v1.Node) (string, error) IssueCommand(cmd string, node *v1.Node) error Cleanup() @@ -84,21 +105,35 @@ func (h *hostExecutor) launchNodeExecPod(node string) *v1.Pod { return pod } -// IssueCommandWithResult issues command on given node and returns stdout. -func (h *hostExecutor) IssueCommandWithResult(cmd string, node *v1.Node) (string, error) { +// Execute executes the command on the given node. If there is no error +// performing the remote command execution, the stdout, stderr and exit code +// are returned. +// This works like ssh.SSH(...) utility. +func (h *hostExecutor) Execute(cmd string, node *v1.Node) (Result, error) { + result, err := h.exec(cmd, node) + if codeExitErr, ok := err.(exec.CodeExitError); ok { + // extract the exit code of remote command and silence the command + // non-zero exit code error + result.Code = codeExitErr.ExitStatus() + err = nil + } + return result, err +} + +func (h *hostExecutor) exec(cmd string, node *v1.Node) (Result, error) { + result := Result{ + Host: node.Name, + Cmd: cmd, + } pod, ok := h.nodeExecPods[node.Name] if !ok { pod = h.launchNodeExecPod(node.Name) if pod == nil { - return "", fmt.Errorf("failed to create hostexec pod for node %q", node) + return result, fmt.Errorf("failed to create hostexec pod for node %q", node) } h.nodeExecPods[node.Name] = pod } args := []string{ - "exec", - fmt.Sprintf("--namespace=%v", pod.Namespace), - pod.Name, - "--", "nsenter", "--mount=/rootfs/proc/1/ns/mnt", "--", @@ -106,7 +141,27 @@ func (h *hostExecutor) IssueCommandWithResult(cmd string, node *v1.Node) (string "-c", cmd, } - return framework.RunKubectl(args...) + containerName := pod.Spec.Containers[0].Name + var err error + result.Stdout, result.Stderr, err = h.Framework.ExecWithOptions(framework.ExecOptions{ + Command: args, + Namespace: pod.Namespace, + PodName: pod.Name, + ContainerName: containerName, + Stdin: nil, + CaptureStdout: true, + CaptureStderr: true, + PreserveWhitespace: true, + }) + return result, err +} + +// IssueCommandWithResult issues command on the given node and returns stdout as +// result. It returns error if there are some issues executing the command or +// the command exits non-zero. +func (h *hostExecutor) IssueCommandWithResult(cmd string, node *v1.Node) (string, error) { + result, err := h.exec(cmd, node) + return result.Stdout, err } // IssueCommand works like IssueCommandWithResult, but discards result.