Change:
<!-- Please only use this template for submitting enhancement requests --What would you like to be : separate stdout and stderr command exit code: remove kubectl dependency: Why is this needed: to replace
/assign
/sig storage HostExec was originally written for local PV tests. If we want to replace SSH in e2e, some features must be supported and I’d like to help on this. cc

Discussion:
// 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) <del> res, err := config.hostExec.IssueCommandWithResult(ssdCmd, node) </del> <ins> res, err := config.hostExec.Execute(ssdCmd, node) utils.LogResult(res) </ins> framework.ExpectNoError(err) <del> num, err := strconv.Atoi(strings.TrimSpace(res)) </del> <ins> num, err := strconv.Atoi(strings.TrimSpace(res.Stdout)) </ins> framework.ExpectNoError(err) if num < 1 { framework.Skipf("Requires at least 1 %s %s localSSD ", ssdInterface, filesystemType)

"//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", <ins> "//staging/src/k8s.io/client-go/util/exec:go_default_library", </ins> "//test/e2e/framework:go_default_library", <ins> "//test/e2e/framework/log:go_default_library", </ins> "//test/e2e/framework/node:go_default_library", "//test/e2e/framework/pod:go_default_library", "//test/e2e/framework/ssh:go_default_library",

"fmt" v1 "k8s.io/api/core/v1" <ins> "k8s.io/client-go/util/exec" </ins> "k8s.io/kubernetes/test/e2e/framework" <ins> e2elog "k8s.io/kubernetes/test/e2e/framework/log" </ins> e2epod "k8s.io/kubernetes/test/e2e/framework/pod" ) <ins> // 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) } </ins> // HostExec represents interface we require to execute commands on remote host. type HostExec interface { <ins> Execute(cmd string, node *v1.Node) (Result, error) </ins> IssueCommandWithResult(cmd string, node *v1.Node) (string, error) IssueCommand(cmd string, node *v1.Node) error Cleanup()

return pod } <del> // IssueCommandWithResult issues command on given node and returns stdout. func (h *hostExecutor) IssueCommandWithResult(cmd string, node *v1.Node) (string, error) { </del> <ins> // 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, } </ins> pod, ok := h.nodeExecPods[node.Name] if !ok { pod = h.launchNodeExecPod(node.Name) if pod == nil { <del> return "", fmt.Errorf("failed to create hostexec pod for node %q", node) </del> <ins> return result, fmt.Errorf("failed to create hostexec pod for node %q", node) </ins> } h.nodeExecPods[node.Name] = pod } args := []string{ <del> "exec", fmt.Sprintf("--namespace=%v", pod.Namespace), pod.Name, "--", </del> "nsenter", "--mount=/rootfs/proc/1/ns/mnt", "--",

"-c", cmd, } <del> return framework.RunKubectl(args...) </del> <ins> 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 </ins> } // IssueCommand works like IssueCommandWithResult, but discards result.