def _ssh(self, command, stdin=None): """Run a command via SSH. Args: command: string or list of the command to run stdin: optional, values to be passed in Returns: tuple of stdout, stderr and the return code """ client = self._ssh_connect() cmd = shell_pack(command) fp_in, fp_out, fp_err = client.exec_command(cmd) channel = fp_in.channel if stdin is not None: fp_in.write(stdin) fp_in.close() channel.shutdown_write() out = fp_out.read() err = fp_err.read() return_code = channel.recv_exit_status() out = '' if not out else out.rstrip().decode("utf-8") err = '' if not err else err.rstrip().decode("utf-8") return Result(out, err, return_code)
def test_happy_path(self, m_subp): """Command succeeds and returns valid YAML.""" image_id = "my:image_id" content = {"my": "data"} m_subp.return_value = Result(yaml.dump(content), "", 0) ret = LXDVirtualMachine(tag="test")._lxc_image_info(image_id) assert content == ret expected_call = mock.call(["lxc", "image", "info", image_id], rcs=()) assert [expected_call] == m_subp.call_args_list
def _ssh(self, command, stdin=None): """Run a command via SSH. Args: command: string or list of the command to run stdin: optional, values to be passed in Returns: tuple of stdout, stderr and the return code """ cmd = shell_pack(command) for _ in range(10): try: client = self._ssh_connect() fp_in, fp_out, fp_err = client.exec_command(cmd) break except (ConnectionResetError, NoValidConnectionsError) as e: last_error = e # On OCI instances, attempting to re-connect without a longer # sleep leaves you locked out of ssh completely time.sleep(5) else: raise last_error # noqa channel = fp_in.channel if stdin is not None: fp_in.write(stdin) fp_in.close() channel.shutdown_write() out = fp_out.read() err = fp_err.read() return_code = channel.recv_exit_status() out = '' if not out else out.rstrip().decode("utf-8") err = '' if not err else err.rstrip().decode("utf-8") return Result(out, err, return_code)
def test_invalid_yaml_returns_empty_dict(self, m_subp): """Invalid YAML even with command success returns empty dict.""" m_subp.return_value = Result("{:a}", "", 0) assert {} == LXDVirtualMachine(tag="test")._lxc_image_info("image_id")
def test_command_failure_returns_empty_dict(self, m_subp): """Command failure even with valid YAML returns empty dict.""" content = {"my": "data"} m_subp.return_value = Result(yaml.dump(content), "", 1) assert {} == LXDVirtualMachine(tag="test")._lxc_image_info("image_id")
def subp(args, data=None, env=None, shell=False, rcs=(0, ), shortcircuit_stdin=True): """Subprocess wrapper. Args: args: command to run data: data to pass env: optional env to use shell: optional shell to use rcs: tuple of successful exit codes, default: (0) shortcircuit_stdin: bind stdin to /dev/null if no data is given Returns: Tuple of out, err, return_code """ devnull_fp = None if data is not None: stdin = subprocess.PIPE if not isinstance(data, bytes): data = data.encode() elif shortcircuit_stdin: # using devnull assures any reads get null, rather # than possibly waiting on input. devnull_fp = open(os.devnull) stdin = devnull_fp else: stdin = None bytes_args = _convert_args(args) try: process = subprocess.Popen(bytes_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=stdin, env=env, shell=shell) (out, err) = process.communicate(data) finally: if devnull_fp: devnull_fp.close() rc = process.returncode out = '' if not out else out.rstrip().decode("utf-8") err = '' if not err else err.rstrip().decode("utf-8") if rcs and rc not in rcs: if err: errmsg = err elif out: errmsg = out else: errmsg = "command failed silently" errmsg = "Failure (rc=%s): %s" % (rc, errmsg) raise RuntimeError(errmsg) return Result(out, err, rc)