blob: 007e0ca6fa2a349fc0e84d750e83727b29ab5a74 [file] [log] [blame]
import os, sys, subprocess, logging
from autotest_lib.client.common_lib import global_config, utils, error
from autotest_lib.server import utils as server_utils
from autotest_lib.server.hosts import cros_host
from autotest_lib.server.hosts import remote
RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
'rpm_frontend_uri', type=str, default='')
class SerialHost(remote.RemoteHost):
DEFAULT_REBOOT_TIMEOUT = remote.RemoteHost.DEFAULT_REBOOT_TIMEOUT
HARD_RESET_CMD = 'hardreset'
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 """
return_value = False
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_value = result.exit_status == 0
except error.CmdError:
logging.warning("Timed out while trying to attach to conmux")
return_value = False
return (return_value or
cros_host.CrosHost.check_for_rpm_support(hostname))
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=HARD_RESET_CMD, num_attempts=1, halt=False,
**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 halt: Halts the machine before hardresetting.
@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 halt:
self.halt()
if self.HARD_RESET_CMD in conmux_command and RPM_FRONTEND_URI:
self.power_cycle()
elif 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
if (self.HARD_RESET_CMD in conmux_command and
RPM_FRONTEND_URI):
self.power_cycle()
else:
self.run_conmux(conmux_command)
else:
break
else:
# Run on num_attempts=1 or last retry
try:
self.wait_for_restart(timeout,
old_boot_id=old_boot_id,
**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")