| import os, sys, subprocess, logging |
| |
| from autotest_lib.client.common_lib import utils, error |
| from autotest_lib.server import utils as server_utils |
| from autotest_lib.server.hosts import remote |
| |
| |
| SiteHost = utils.import_site_class( |
| __file__, "autotest_lib.server.hosts.site_host", "SiteHost", |
| remote.RemoteHost) |
| |
| |
| class SerialHost(SiteHost): |
| DEFAULT_REBOOT_TIMEOUT = SiteHost.DEFAULT_REBOOT_TIMEOUT |
| |
| def _initialize(self, conmux_server=None, conmux_attach=None, |
| console_log="console.log", *args, **dargs): |
| super(SerialHost, self)._initialize(*args, **dargs) |
| |
| self.__logger = None |
| self.__console_log = console_log |
| |
| self.conmux_server = conmux_server |
| self.conmux_attach = self._get_conmux_attach(conmux_attach) |
| |
| |
| @classmethod |
| def _get_conmux_attach(cls, conmux_attach=None): |
| if conmux_attach: |
| return conmux_attach |
| |
| # assume we're using the conmux-attach provided with autotest |
| server_dir = server_utils.get_server_dir() |
| path = os.path.join(server_dir, "..", "conmux", "conmux-attach") |
| path = os.path.abspath(path) |
| return path |
| |
| |
| @staticmethod |
| def _get_conmux_hostname(hostname, conmux_server): |
| if conmux_server: |
| return "%s/%s" % (conmux_server, hostname) |
| else: |
| return hostname |
| |
| |
| def get_conmux_hostname(self): |
| return self._get_conmux_hostname(self.hostname, self.conmux_server) |
| |
| |
| @classmethod |
| def host_is_supported(cls, hostname, conmux_server=None, |
| conmux_attach=None): |
| """ Returns a boolean indicating if the remote host with "hostname" |
| supports use as a SerialHost """ |
| conmux_attach = cls._get_conmux_attach(conmux_attach) |
| conmux_hostname = cls._get_conmux_hostname(hostname, conmux_server) |
| cmd = "%s %s echo 2> /dev/null" % (conmux_attach, conmux_hostname) |
| try: |
| result = utils.run(cmd, ignore_status=True, timeout=10) |
| return result.exit_status == 0 |
| except error.CmdError: |
| logging.warning("Timed out while trying to attach to conmux") |
| |
| return False |
| |
| |
| def start_loggers(self): |
| super(SerialHost, self).start_loggers() |
| |
| if self.__console_log is None: |
| return |
| |
| if not self.conmux_attach or not os.path.exists(self.conmux_attach): |
| return |
| |
| r, w = os.pipe() |
| script_path = os.path.join(self.monitordir, 'console.py') |
| cmd = [self.conmux_attach, self.get_conmux_hostname(), |
| '%s %s %s %d' % (sys.executable, script_path, |
| self.__console_log, w)] |
| |
| self.__warning_stream = os.fdopen(r, 'r', 0) |
| if self.job: |
| self.job.warning_loggers.add(self.__warning_stream) |
| |
| stdout = stderr = open(os.devnull, 'w') |
| self.__logger = subprocess.Popen(cmd, stdout=stdout, stderr=stderr) |
| os.close(w) |
| |
| |
| def stop_loggers(self): |
| super(SerialHost, self).stop_loggers() |
| |
| if self.__logger: |
| utils.nuke_subprocess(self.__logger) |
| self.__logger = None |
| if self.job: |
| self.job.warning_loggers.discard(self.__warning_stream) |
| self.__warning_stream.close() |
| |
| |
| def run_conmux(self, cmd): |
| """ |
| Send a command to the conmux session |
| """ |
| if not self.conmux_attach or not os.path.exists(self.conmux_attach): |
| return False |
| cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach, |
| self.get_conmux_hostname(), |
| cmd) |
| result = utils.system(cmd, ignore_status=True) |
| return result == 0 |
| |
| |
| def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True, |
| conmux_command='hardreset', num_attempts=1, |
| **wait_for_restart_kwargs): |
| """ |
| Reach out and slap the box in the power switch. |
| @params conmux_command: The command to run via the conmux interface |
| @params timeout: timelimit in seconds before the machine is |
| considered unreachable |
| @params wait: Whether or not to wait for the machine to reboot |
| @params num_attempts: Number of times to attempt hard reset erroring |
| on the last attempt. |
| @params **wait_for_restart_kwargs: keyword arguments passed to |
| wait_for_restart() |
| """ |
| conmux_command = "'~$%s'" % conmux_command |
| |
| # if the machine is up, grab the old boot id, otherwise use a dummy |
| # string and NOT None to ensure that wait_down always returns True, |
| # even if the machine comes back up before it's called |
| try: |
| old_boot_id = self.get_boot_id() |
| except error.AutoservSSHTimeout: |
| old_boot_id = 'unknown boot_id prior to SerialHost.hardreset' |
| |
| def reboot(): |
| if not self.run_conmux(conmux_command): |
| self.record("ABORT", None, "reboot.start", |
| "hard reset unavailable") |
| raise error.AutoservUnsupportedError( |
| 'Hard reset unavailable') |
| self.record("GOOD", None, "reboot.start", "hard reset") |
| if wait: |
| warning_msg = ('Serial console failed to respond to hard reset ' |
| 'attempt (%s/%s)') |
| for attempt in xrange(num_attempts-1): |
| try: |
| self.wait_for_restart(timeout, log_failure=False, |
| old_boot_id=old_boot_id, |
| **wait_for_restart_kwargs) |
| except error.AutoservShutdownError: |
| logging.warning(warning_msg, attempt+1, num_attempts) |
| # re-send the hard reset command |
| self.run_conmux(conmux_command) |
| else: |
| break |
| else: |
| # Run on num_attempts=1 or last retry |
| try: |
| self.wait_for_restart(timeout, |
| **wait_for_restart_kwargs) |
| except error.AutoservShutdownError: |
| logging.warning(warning_msg, num_attempts, num_attempts) |
| msg = "Host did not shutdown" |
| raise error.AutoservShutdownError(msg) |
| |
| if self.job: |
| self.job.disable_warnings("POWER_FAILURE") |
| try: |
| if wait: |
| self.log_reboot(reboot) |
| else: |
| reboot() |
| finally: |
| if self.job: |
| self.job.enable_warnings("POWER_FAILURE") |