def copy_from(self, src, dst=".", recursive=False, nonzero_e=tc.error_e): """ Copy a file or tree with *SCP* from the target to the client :param str src: remote file or directory to copy :param str dst: (optional) destination file or directory (defaults to current working directory) :param bool recursive: (optional) copy recursively (needed for directories) :param tcfl.tc.exception nonzero_e: exception to raise in case of non zero exit code. Must be a subclass of :class:`tcfl.tc.exception` (i.e.: :class:`tcfl.tc.failed_e`, :class:`tcfl.tc.error_e`, :class:`tcfl.tc.skip_e`, :class:`tcfl.tc.blocked_e`) or *None* (default) to not raise anything and just return the exit code. """ self._tunnel() self.target.report_info("running SCP target:%s -> local:%s" % (src, dst), dlevel=1) options = "-v" if recursive: options += "r" try: env = dict(os.environ) if self.password: cmdline = ['sshpass', "-e"] env['SSHPASS'] = self.password else: cmdline = [] options += "B" cmdline += \ [ "scp", options, "-P", str(self._ssh_port) ] \ + self._ssh_cmdline_options \ + [ self.login + "@" + self._ssh_host + ":" + src, dst ] self.target.report_info("running SCP command: %s" % " ".join(cmdline), dlevel=2) self._known_hosts_wipe() s = subprocess.check_output(cmdline, stderr=subprocess.STDOUT, shell=False, env=env, encoding='utf-8') except subprocess.CalledProcessError as e: self._returncode_eval(e.returncode) commonl.raise_from( nonzero_e( "failed SCP local:%s -> target:%s" % (src, dst), dict(returncode=e.returncode, output=e.output, src=src, dst=dst, recursive=recursive, ssh_cmd=" ".join(e.cmd), target=self.target)), e) self.target.report_info("ran SCP target:%s -> local:%s" % (src, dst), attachments=dict(output=s))
def copy_to(self, src, dst="", recursive=False, result_on_failure="error"): """ Copy a file or tree with *SCP* to the target from the client :param str src: local file or directory to copy :param str dst: (optional) destination file or directoy (defaults to root's home directory) :param bool recursive: (optional) copy recursively (needed for directories) :param str result_on_failure: (optional) how shall a failure considered to be (errr|fail|blck|skip). """ exc = self._result_e(result_on_failure) self._tunnel() self.target.report_info("running SCP %s %s" % (src, dst), dlevel=2) options = "-vB" if recursive: options += "r" try: s = subprocess.check_output( ["/usr/bin/scp", options, "-P", "%s" % self._ssh_port] + self._ssh_cmdline_options + [src, self.login + "@" + self._ssh_host + ":" + dst], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: commonl.raise_from( exc( "scp to '%s' -> '%s' failed" % (src, dst), dict(output=e.output, src=src, dst=dst, recursive=recursive, ssh_cmd=" ".join(e.cmd))), e) self.target.report_info("ran SCP %s %s" % (src, dst), dlevel=1) return s
def check_output(self, cmd, result_on_failure="error"): """ Run a shell command over SSH, substituting any %(KEYWORD)[ds] field from the target's keywords in :attr:`tcfl.tc.target_c.kws` Similar to :func:`subprocess.check_output` :param str cmd: shell command to execute via SSH :param str result_on_failure: (optional) how shall a failure considered to be (errr|fail|blck|skip). :returns: exitcode """ exc = self._result_e(result_on_failure) self._tunnel() _cmd = cmd % self.target.kws self.target.report_info("running SSH command '%s'" % _cmd, dlevel=2) try: s = subprocess.check_output( ["/usr/bin/ssh", "-p", "%s" % self._ssh_port] + self._ssh_cmdline_options + [self.login + "@" + self._ssh_host, "-t", _cmd], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: commonl.raise_from( exc("ssh command failed: %s" % cmd, dict(output=e.output, ssh_cmd=" ".join(e.cmd), cmd=cmd)), e) self.target.report_info("ran SSH command '%s': %s" % (_cmd, s), dlevel=1) return s
def copy_to(self, src, dst="", recursive=False, nonzero_e=tc.error_e): """Copy a file or tree with *SCP* to the target from the client :param str src: local file or directory to copy Note a relative path will be made relative to the location of the testscript, see :func:`testcase.relpath_to_abs <tcfl.tc.tc_c.relpath_to_abs>`. :param str dst: (optional) destination file or directoy (defaults to root's home directory) :param bool recursive: (optional) copy recursively (needed for directories) :param tcfl.tc.exception nonzero_e: exception to raise in case of non zero exit code. Must be a subclass of :class:`tcfl.tc.exception` (i.e.: :class:`tcfl.tc.failed_e`, :class:`tcfl.tc.error_e`, :class:`tcfl.tc.skip_e`, :class:`tcfl.tc.blocked_e`) or *None* (default) to not raise anything and just return the exit code. """ self._tunnel() self.target.report_info("running SCP local:%s -> target:%s" % (src, dst), dlevel=1) src = self.target.testcase.relpath_to_abs(src) options = "-vB" if recursive: options += "r" try: cmdline = \ [ "/usr/bin/scp", options, "-P", "%s" % self._ssh_port] \ + self._ssh_cmdline_options \ + [ src, self.login + "@" + self._ssh_host + ":" + dst ] self.target.report_info("running SCP command: %s" % " ".join(cmdline), dlevel=2) s = subprocess.check_output(cmdline, stderr=subprocess.STDOUT, shell=False) except subprocess.CalledProcessError as e: self._returncode_eval(e.returncode) commonl.raise_from( nonzero_e( "failed SCP local:%s -> target:%s" % (src, dst), dict(returncode=e.returncode, output=e.output, src=src, dst=dst, recursive=recursive, ssh_cmd=" ".join(e.cmd), target=self.target)), e) self.target.report_info("ran SCP local:%s -> target:%s" % (src, dst), attachments=dict(output=s))
def copy_from(self, src, dst = ".", recursive = False, nonzero_e = tc.error_e): """ Copy a file or tree with *SCP* from the target to the client :param str src: remote file or directory to copy :param str dst: (optional) destination file or directory (defaults to current working directory) :param bool recursive: (optional) copy recursively (needed for directories) :param tcfl.tc.exception nonzero_e: exception to raise in case of non zero exit code. Must be a subclass of :class:`tcfl.tc.exception` (i.e.: :class:`tcfl.tc.failed_e`, :class:`tcfl.tc.error_e`, :class:`tcfl.tc.skip_e`, :class:`tcfl.tc.blocked_e`) or *None* (default) to not raise anything and just return the exit code. """ self._tunnel() self.target.report_info("running SCP target:%s -> local:%s" % (src, dst), dlevel = 1) options = "-vB" if recursive: options += "r" try: cmdline = \ [ "/usr/bin/scp", options, "-P", "%s" % self._ssh_port] \ + self._ssh_cmdline_options \ + [self.login + "@" + self._ssh_host + ":" + src, dst ] self.target.report_info("running SCP command: %s" % " ".join(cmdline), dlevel = 2) s = subprocess.check_output(cmdline, stderr = subprocess.STDOUT) except subprocess.CalledProcessError as e: self._returncode_eval(e.returncode) commonl.raise_from(nonzero_e( "failed SCP local:%s -> target:%s" % (src, dst), dict(returncode = e.returncode, output = e.output, src = src, dst = dst, recursive = recursive, ssh_cmd = " ".join(e.cmd), target = self.target )), e) self.target.report_info("ran SCP target:%s -> local:%s" % (src, dst), attachments = dict(output = s))
def _qemu_launch(self, bsp, kws): gdb_tcp_port = commonl.tcp_port_assigner( 1, port_range=ttbl.config.tcp_port_range) self.fsdb.set("debug-%s-gdb-tcp-port" % bsp, "%s" % gdb_tcp_port) console_out_fname = os.path.join(self.state_dir, "console-%s.log" % bsp) errfname = os.path.join(self.state_dir, "%s-stderr.log" % bsp) try: # Make sure we wipe the PID file -- sometimes a pidfile is # left over and it seems to override it, so the reading # becomes corrupt commonl.rm_f(self.pidfile[bsp]) qemu_cmdline = \ (self.qemu_cmdlines[bsp] % kws).split() \ + [ # Don't add -daemonize! This way this is part of # the process tree and killed when we kill the # parent process # We use QMP to find the PTY assigned "-qmp", "unix:%s.qmp,server,nowait" % self.pidfile[bsp], # Always start in debug mode -- this way the # whole thing is stopped until we unleash it # with QMP; this allows us to first start # daemons that we might need to start "-S", "-pidfile", self.pidfile[bsp], "-gdb", "tcp:0.0.0.0:%d" % gdb_tcp_port, ] self.fsdb.set("qemu-cmdline-%s" % bsp, qemu_cmdline[0]) except KeyError as e: msg = "bad QEMU command line specification: " \ "uninitialized key %s" % e self.log.error(msg) commonl.raise_from(RuntimeError(msg), e) self.log.debug("QEMU cmdline %s" % " ".join(qemu_cmdline)) self.tags['bsps'][bsp]['cmdline'] = " ".join(qemu_cmdline) try: _preexec_fn = getattr(self, "qemu_preexec_fn", None) def _local_preexec_fn(): if _preexec_fn: _preexec_fn() # Open file descriptors for stdout/stderr to a # logfile, because -D is not really working. We # need it to check startup errors for things that # need a retry. commonl.rm_f(errfname) logfd = os.open( errfname, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL | os.O_CREAT, 0o0644) os.dup2(logfd, 1) os.dup2(logfd, 2) os.close(logfd) p = subprocess.Popen(qemu_cmdline, shell=False, cwd=self.state_dir, close_fds=True, preexec_fn=_local_preexec_fn) self.log.debug("QEMU %s: console @ %s" % (bsp, console_out_fname)) # Give it a few secs to start, the pidfile has been # deleted before starting -- note 4 was found by # ad-hoc experimentation, sometimes depending on system load it # takes more or less. timeout = 10 ts0 = time.time() while True: if time.time() - ts0 > timeout: lines = [] with open(errfname) as f: count = 0 for line in f: lines.append("log: " + line) if count > 5: lines.append("log: ...") break raise RuntimeError("QEMU %s: did not start after %.0fs\n" "%s" % (bsp, timeout, "\n".join(lines))) try: if self._qmp_running(bsp): # FIXME: race condition ttbl.daemon_pid_add(p.pid) return True except RuntimeError as e: self.log.warning("QEMU %s: can't read QMP: %s" % (bsp, str(e))) # fall through, let it retry # Check errors during startup with open(errfname, "r") as logf: causes_for_retry = [ # bah, race condition: since we chose the # port we wanted to use and until we # started using it someone took it. Retry 'Failed to bind socket: Address already in use', ] for line in logf: for cause in causes_for_retry: if cause in line: self.log.info( "QEMU %s: retrying because found in " "logfile: %s", bsp, cause) return False time.sleep(0.25) # nothing runs after this, either it returns or raises except (OSError, ValueError) as e: self.log.debug("QEMU %s: launch failure: %s", bsp, e) raise
def _qemu_launch(self, bsp, kws): gdb_tcp_port = commonl.tcp_port_assigner( 1, port_range = ttbl.config.tcp_port_range) self.fsdb.set("debug-%s-gdb-tcp-port" % bsp, "%s" % gdb_tcp_port) console_out_fname = os.path.join( self.state_dir, "console-%s.log" % bsp) errfname = os.path.join( self.state_dir, "%s-stderr.log" % bsp) try: # Make sure we wipe the PID file -- sometimes a pidfile is # left over and it seems to override it, so the reading # becomes corrupt commonl.rm_f(self.pidfile[bsp]) qemu_cmdline = \ (self.qemu_cmdlines[bsp] % kws).split() \ + [ # Don't add -daemonize! This way this is part of # the process tree and killed when we kill the # parent process # We use QMP to find the PTY assigned "-qmp", "unix:%s.qmp,server,nowait" % self.pidfile[bsp], # Always start in debug mode -- this way the # whole thing is stopped until we unleash it # with QMP; this allows us to first start # daemons that we might need to start "-S", "-pidfile", self.pidfile[bsp], "-gdb", "tcp:0.0.0.0:%d" % gdb_tcp_port, ] self.fsdb.set("qemu-cmdline-%s" % bsp, qemu_cmdline[0]) except KeyError as e: msg = "bad QEMU command line specification: " \ "uninitialized key %s" % e self.log.error(msg) commonl.raise_from(RuntimeError(msg), e) self.log.debug("QEMU cmdline %s" % " ".join(qemu_cmdline)) self.tags['bsps'][bsp]['cmdline'] = " ".join(qemu_cmdline) try: _preexec_fn = getattr(self, "qemu_preexec_fn", None) def _local_preexec_fn(): if _preexec_fn: _preexec_fn() # Open file descriptors for stdout/stderr to a # logfile, because -D is not really working. We # need it to check startup errors for things that # need a retry. commonl.rm_f(errfname) logfd = os.open(errfname, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644) os.dup2(logfd, 1) os.dup2(logfd, 2) os.close(logfd) p = subprocess.Popen(qemu_cmdline, shell = False, cwd = self.state_dir, close_fds = True, preexec_fn = _local_preexec_fn) self.log.debug("QEMU %s: console @ %s" % (bsp, console_out_fname)) # Give it a few secs to start, the pidfile has been # deleted before starting -- note 4 was found by # ad-hoc experimentation, sometimes depending on system load it # takes more or less. timeout = 10 ts0 = time.time() while True: if time.time() - ts0 > timeout: lines = [] with open(errfname) as f: count = 0 for line in f: lines.append("log: " + line) if count > 5: lines.append("log: ...") break raise RuntimeError("QEMU %s: did not start after %.0fs\n" "%s" % (bsp, timeout, "\n".join(lines))) try: if self._qmp_running(bsp): # FIXME: race condition ttbl.daemon_pid_add(p.pid) return True except RuntimeError as e: self.log.warning("QEMU %s: can't read QMP: %s" % (bsp, str(e))) # fall through, let it retry # Check errors during startup with open(errfname, "r") as logf: causes_for_retry = [ # bah, race condition: since we chose the # port we wanted to use and until we # started using it someone took it. Retry 'Failed to bind socket: Address already in use', ] for line in logf: for cause in causes_for_retry: if cause in line: self.log.info( "QEMU %s: retrying because found in " "logfile: %s", bsp, cause) return False time.sleep(0.25) # nothing runs after this, either it returns or raises except (OSError, ValueError) as e: self.log.debug("QEMU %s: launch failure: %s", bsp, e) raise