1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# 3*9c5db199SXin Li# Copyright 2007 Google Inc. Released under the GPL v2 4*9c5db199SXin Li 5*9c5db199SXin Li""" 6*9c5db199SXin LiThis module defines the SSHHost class. 7*9c5db199SXin Li 8*9c5db199SXin LiImplementation details: 9*9c5db199SXin LiYou should import the "hosts" package instead of importing each type of host. 10*9c5db199SXin Li 11*9c5db199SXin Li SSHHost: a remote machine with a ssh access 12*9c5db199SXin Li""" 13*9c5db199SXin Li 14*9c5db199SXin Lifrom __future__ import absolute_import 15*9c5db199SXin Lifrom __future__ import division 16*9c5db199SXin Lifrom __future__ import print_function 17*9c5db199SXin Li 18*9c5db199SXin Liimport inspect 19*9c5db199SXin Liimport logging 20*9c5db199SXin Liimport re 21*9c5db199SXin Liimport time 22*9c5db199SXin Li 23*9c5db199SXin Liimport common 24*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 25*9c5db199SXin Lifrom autotest_lib.client.common_lib import pxssh 26*9c5db199SXin Lifrom autotest_lib.server import utils 27*9c5db199SXin Lifrom autotest_lib.server.hosts import abstract_ssh 28*9c5db199SXin Liimport six 29*9c5db199SXin Li 30*9c5db199SXin Li# In case cros_host is being ran via SSP on an older Moblab version with an 31*9c5db199SXin Li# older chromite version. 32*9c5db199SXin Litry: 33*9c5db199SXin Li from autotest_lib.utils.frozen_chromite.lib import metrics 34*9c5db199SXin Liexcept ImportError: 35*9c5db199SXin Li metrics = utils.metrics_mock 36*9c5db199SXin Li 37*9c5db199SXin Li 38*9c5db199SXin Lidef THIS_IS_SLOW(func): 39*9c5db199SXin Li """Mark the given function as slow, when looking at calls to it""" 40*9c5db199SXin Li func.__name__ = '%s__SLOW__' % func.__name__ 41*9c5db199SXin Li return func 42*9c5db199SXin Li 43*9c5db199SXin Li 44*9c5db199SXin Liclass SSHHost(abstract_ssh.AbstractSSHHost): 45*9c5db199SXin Li """ 46*9c5db199SXin Li This class represents a remote machine controlled through an ssh 47*9c5db199SXin Li session on which you can run programs. 48*9c5db199SXin Li 49*9c5db199SXin Li It is not the machine autoserv is running on. The machine must be 50*9c5db199SXin Li configured for password-less login, for example through public key 51*9c5db199SXin Li authentication. 52*9c5db199SXin Li 53*9c5db199SXin Li It includes support for controlling the machine through a serial 54*9c5db199SXin Li console on which you can run programs. If such a serial console is 55*9c5db199SXin Li set up on the machine then capabilities such as hard reset and 56*9c5db199SXin Li boot strap monitoring are available. If the machine does not have a 57*9c5db199SXin Li serial console available then ordinary SSH-based commands will 58*9c5db199SXin Li still be available, but attempts to use extensions such as 59*9c5db199SXin Li console logging or hard reset will fail silently. 60*9c5db199SXin Li 61*9c5db199SXin Li Implementation details: 62*9c5db199SXin Li This is a leaf class in an abstract class hierarchy, it must 63*9c5db199SXin Li implement the unimplemented methods in parent classes. 64*9c5db199SXin Li """ 65*9c5db199SXin Li RUN_TIMEOUT = 3600 66*9c5db199SXin Li 67*9c5db199SXin Li def _initialize(self, hostname, *args, **dargs): 68*9c5db199SXin Li """ 69*9c5db199SXin Li Construct a SSHHost object 70*9c5db199SXin Li 71*9c5db199SXin Li Args: 72*9c5db199SXin Li hostname: network hostname or address of remote machine 73*9c5db199SXin Li """ 74*9c5db199SXin Li super(SSHHost, self)._initialize(hostname=hostname, *args, **dargs) 75*9c5db199SXin Li self._default_run_timeout = self.RUN_TIMEOUT 76*9c5db199SXin Li self.setup_ssh() 77*9c5db199SXin Li 78*9c5db199SXin Li 79*9c5db199SXin Li def ssh_command(self, connect_timeout=30, options='', alive_interval=300, 80*9c5db199SXin Li alive_count_max=3, connection_attempts=1): 81*9c5db199SXin Li """ 82*9c5db199SXin Li Construct an ssh command with proper args for this host. 83*9c5db199SXin Li 84*9c5db199SXin Li @param connect_timeout: connection timeout (in seconds) 85*9c5db199SXin Li @param options: SSH options 86*9c5db199SXin Li @param alive_interval: SSH Alive interval. 87*9c5db199SXin Li @param alive_count_max: SSH AliveCountMax. 88*9c5db199SXin Li @param connection_attempts: SSH ConnectionAttempts 89*9c5db199SXin Li """ 90*9c5db199SXin Li options = " ".join([options, self._main_ssh.ssh_option]) 91*9c5db199SXin Li base_cmd = self.make_ssh_command(user=self.user, port=self.port, 92*9c5db199SXin Li opts=options, 93*9c5db199SXin Li hosts_file=self.known_hosts_file, 94*9c5db199SXin Li connect_timeout=connect_timeout, 95*9c5db199SXin Li alive_interval=alive_interval, 96*9c5db199SXin Li alive_count_max=alive_count_max, 97*9c5db199SXin Li connection_attempts=connection_attempts) 98*9c5db199SXin Li return "%s %s" % (base_cmd, self.hostname) 99*9c5db199SXin Li 100*9c5db199SXin Li def _get_server_stack_state(self, lowest_frames=0, highest_frames=None): 101*9c5db199SXin Li """ Get the server stack frame status. 102*9c5db199SXin Li @param lowest_frames: the lowest frames to start printing. 103*9c5db199SXin Li @param highest_frames: the highest frames to print. 104*9c5db199SXin Li (None means no restriction) 105*9c5db199SXin Li """ 106*9c5db199SXin Li stack_frames = inspect.stack() 107*9c5db199SXin Li stack = '' 108*9c5db199SXin Li for frame in stack_frames[lowest_frames:highest_frames]: 109*9c5db199SXin Li function_name = inspect.getframeinfo(frame[0]).function 110*9c5db199SXin Li stack = '%s|%s' % (function_name, stack) 111*9c5db199SXin Li del stack_frames 112*9c5db199SXin Li return stack[:-1] # Delete the last '|' character 113*9c5db199SXin Li 114*9c5db199SXin Li def _verbose_logger_command(self, command): 115*9c5db199SXin Li """ 116*9c5db199SXin Li Prepend the command for the client with information about the ssh 117*9c5db199SXin Li command to be executed and the server stack state. 118*9c5db199SXin Li 119*9c5db199SXin Li @param command: the ssh command to be executed. 120*9c5db199SXin Li """ 121*9c5db199SXin Li # The last few frames on the stack are not useful, so skip them. 122*9c5db199SXin Li stack = self._get_server_stack_state(lowest_frames=3, highest_frames=6) 123*9c5db199SXin Li # If logger executable exists on the DUT, use it to report the command. 124*9c5db199SXin Li # Then regardless of logger, run the command as usual. 125*9c5db199SXin Li command = ('test -x /usr/bin/logger && /usr/bin/logger' 126*9c5db199SXin Li ' -t autotest "from [%s] ssh_run: %s"; %s' 127*9c5db199SXin Li % (stack, utils.sh_escape(command), command)) 128*9c5db199SXin Li return command 129*9c5db199SXin Li 130*9c5db199SXin Li def _tls_run(self, original_cmd, timeout, ignore_status, stdout, stderr, 131*9c5db199SXin Li args, ignore_timeout): 132*9c5db199SXin Li """Helper function for run(), uses the tls client.""" 133*9c5db199SXin Li if not self.tls_connection.alive: 134*9c5db199SXin Li raise error.TLSConnectionError("TLS not connected.") 135*9c5db199SXin Li original_cmd = ' '.join([original_cmd] + 136*9c5db199SXin Li [utils.sh_quote_word(arg) for arg in args]) 137*9c5db199SXin Li 138*9c5db199SXin Li try: 139*9c5db199SXin Li result = self.tls_exec_dut_command_client.run_cmd(original_cmd, timeout, 140*9c5db199SXin Li stdout, stderr, 141*9c5db199SXin Li ignore_timeout) 142*9c5db199SXin Li except Exception as e: 143*9c5db199SXin Li logging.warning("TLS Client run err %s", e) 144*9c5db199SXin Li raise e 145*9c5db199SXin Li 146*9c5db199SXin Li if not ignore_status and result.exit_status > 0: 147*9c5db199SXin Li msg = result.stderr.strip() 148*9c5db199SXin Li if not msg: 149*9c5db199SXin Li msg = result.stdout.strip() 150*9c5db199SXin Li if msg: 151*9c5db199SXin Li msg = msg.splitlines()[-1] 152*9c5db199SXin Li raise error.AutoservRunError( 153*9c5db199SXin Li "command execution error using TLS (%d): %s" % 154*9c5db199SXin Li (result.exit_status, msg), result) 155*9c5db199SXin Li 156*9c5db199SXin Li return result 157*9c5db199SXin Li 158*9c5db199SXin Li def _run(self, command, timeout, ignore_status, stdout, stderr, 159*9c5db199SXin Li connect_timeout, env, options, stdin, args, ignore_timeout, 160*9c5db199SXin Li ssh_failure_retry_ok, verbose): 161*9c5db199SXin Li """Helper function for run().""" 162*9c5db199SXin Li if connect_timeout > timeout: 163*9c5db199SXin Li # timeout passed from run() may be smaller than 1, because we 164*9c5db199SXin Li # subtract the elapsed time from the original timeout supplied. 165*9c5db199SXin Li connect_timeout = max(int(timeout), 1) 166*9c5db199SXin Li original_cmd = command 167*9c5db199SXin Li 168*9c5db199SXin Li # If TLS client has been built, and not marked as unstable, use it. 169*9c5db199SXin Li # NOTE: if the tls_enabled setting in the config is not True, the 170*9c5db199SXin Li # client will not have been built. 171*9c5db199SXin Li use_tls = self.tls_exec_dut_command_client and not self.tls_unstable 172*9c5db199SXin Li 173*9c5db199SXin Li if verbose: 174*9c5db199SXin Li stack = self._get_server_stack_state(lowest_frames=2, 175*9c5db199SXin Li highest_frames=8) 176*9c5db199SXin Li 177*9c5db199SXin Li logging.debug("Running (via %s) '%s' from '%s'", 178*9c5db199SXin Li 'TLS' if use_tls else 'SSH', command, stack) 179*9c5db199SXin Li command = self._verbose_logger_command(command) 180*9c5db199SXin Li 181*9c5db199SXin Li if use_tls: 182*9c5db199SXin Li try: 183*9c5db199SXin Li return self._tls_run(command, timeout, ignore_status, stdout, 184*9c5db199SXin Li stderr, args, ignore_timeout) 185*9c5db199SXin Li except (error.AutoservRunError, error.CmdTimeoutError) as e: 186*9c5db199SXin Li raise e 187*9c5db199SXin Li except Exception as e: 188*9c5db199SXin Li # If TLS fails for unknown reason, we will revert to normal ssh. 189*9c5db199SXin Li logging.warning( 190*9c5db199SXin Li "Unexpected TLS cmd failed. Reverting to SSH.\n %s", e) 191*9c5db199SXin Li 192*9c5db199SXin Li # Note the TLS as unstable so we do not attempt to re-start it. 193*9c5db199SXin Li self.tls_unstable = True 194*9c5db199SXin Li 195*9c5db199SXin Li ssh_cmd = self.ssh_command(connect_timeout, options) 196*9c5db199SXin Li if not env.strip(): 197*9c5db199SXin Li env = "" 198*9c5db199SXin Li else: 199*9c5db199SXin Li env = "export %s;" % env 200*9c5db199SXin Li for arg in args: 201*9c5db199SXin Li command += ' "%s"' % utils.sh_escape(arg) 202*9c5db199SXin Li full_cmd = '%s "%s %s"' % (ssh_cmd, env, utils.sh_escape(command)) 203*9c5db199SXin Li 204*9c5db199SXin Li def counters_inc(counter_name, failure_name): 205*9c5db199SXin Li """Helper function to increment metrics counters. 206*9c5db199SXin Li @param counter_name: string indicating which counter to use 207*9c5db199SXin Li @param failure_name: string indentifying an error, or 'success' 208*9c5db199SXin Li """ 209*9c5db199SXin Li if counter_name == 'call': 210*9c5db199SXin Li # ssh_counter records the outcome of each ssh invocation 211*9c5db199SXin Li # inside _run(), including exceptions. 212*9c5db199SXin Li ssh_counter = metrics.Counter('chromeos/autotest/ssh/calls') 213*9c5db199SXin Li fields = {'error' : failure_name or 'success', 214*9c5db199SXin Li 'attempt' : ssh_call_count} 215*9c5db199SXin Li ssh_counter.increment(fields=fields) 216*9c5db199SXin Li 217*9c5db199SXin Li if counter_name == 'run': 218*9c5db199SXin Li # run_counter records each call to _run() with its result 219*9c5db199SXin Li # and how many tries were made. Calls are recorded when 220*9c5db199SXin Li # _run() exits (including exiting with an exception) 221*9c5db199SXin Li run_counter = metrics.Counter('chromeos/autotest/ssh/runs') 222*9c5db199SXin Li fields = {'error' : failure_name or 'success', 223*9c5db199SXin Li 'attempt' : ssh_call_count} 224*9c5db199SXin Li run_counter.increment(fields=fields) 225*9c5db199SXin Li 226*9c5db199SXin Li # If ssh_failure_retry_ok is True, retry twice on timeouts and generic 227*9c5db199SXin Li # error 255: if a simple retry doesn't work, kill the ssh main 228*9c5db199SXin Li # connection and try again. (Note that either error could come from 229*9c5db199SXin Li # the command running in the DUT, in which case the retry may be 230*9c5db199SXin Li # useless but, in theory, also harmless.) 231*9c5db199SXin Li if ssh_failure_retry_ok: 232*9c5db199SXin Li # Ignore ssh command timeout, even though it could be a timeout due 233*9c5db199SXin Li # to the command executing in the remote host. Note that passing 234*9c5db199SXin Li # ignore_timeout = True makes utils.run() return None on timeouts 235*9c5db199SXin Li # (and only on timeouts). 236*9c5db199SXin Li original_ignore_timeout = ignore_timeout 237*9c5db199SXin Li ignore_timeout = True 238*9c5db199SXin Li ssh_failure_retry_count = 2 239*9c5db199SXin Li else: 240*9c5db199SXin Li ssh_failure_retry_count = 0 241*9c5db199SXin Li 242*9c5db199SXin Li ssh_call_count = 0 243*9c5db199SXin Li 244*9c5db199SXin Li while True: 245*9c5db199SXin Li try: 246*9c5db199SXin Li # Increment call count first, in case utils.run() throws an 247*9c5db199SXin Li # exception. 248*9c5db199SXin Li ssh_call_count += 1 249*9c5db199SXin Li result = utils.run(full_cmd, timeout, True, stdout, stderr, 250*9c5db199SXin Li verbose=False, stdin=stdin, 251*9c5db199SXin Li stderr_is_expected=ignore_status, 252*9c5db199SXin Li ignore_timeout=ignore_timeout) 253*9c5db199SXin Li except Exception as e: 254*9c5db199SXin Li # No retries on exception. 255*9c5db199SXin Li counters_inc('call', 'exception') 256*9c5db199SXin Li counters_inc('run', 'exception') 257*9c5db199SXin Li raise e 258*9c5db199SXin Li 259*9c5db199SXin Li failure_name = None 260*9c5db199SXin Li 261*9c5db199SXin Li if result: 262*9c5db199SXin Li if result.exit_status == 255: 263*9c5db199SXin Li if re.search(r'^ssh: .*: Name or service not known', 264*9c5db199SXin Li result.stderr): 265*9c5db199SXin Li failure_name = 'dns_failure' 266*9c5db199SXin Li else: 267*9c5db199SXin Li failure_name = 'error_255' 268*9c5db199SXin Li elif result.exit_status > 0: 269*9c5db199SXin Li failure_name = 'nonzero_status' 270*9c5db199SXin Li else: 271*9c5db199SXin Li # result == None 272*9c5db199SXin Li failure_name = 'timeout' 273*9c5db199SXin Li 274*9c5db199SXin Li # Record the outcome of the ssh invocation. 275*9c5db199SXin Li counters_inc('call', failure_name) 276*9c5db199SXin Li 277*9c5db199SXin Li if failure_name: 278*9c5db199SXin Li # There was a failure: decide whether to retry. 279*9c5db199SXin Li if failure_name == 'dns_failure': 280*9c5db199SXin Li raise error.AutoservSshDnsError("DNS Failure: ", result) 281*9c5db199SXin Li else: 282*9c5db199SXin Li if ssh_failure_retry_count == 2: 283*9c5db199SXin Li logging.debug('retrying ssh command after %s', 284*9c5db199SXin Li failure_name) 285*9c5db199SXin Li ssh_failure_retry_count -= 1 286*9c5db199SXin Li continue 287*9c5db199SXin Li elif ssh_failure_retry_count == 1: 288*9c5db199SXin Li # After two failures, restart the main connection 289*9c5db199SXin Li # before the final try. 290*9c5db199SXin Li stack = self._get_server_stack_state(lowest_frames=1, 291*9c5db199SXin Li highest_frames=7) 292*9c5db199SXin Li logging.debug( 293*9c5db199SXin Li 'retry 2: restarting main connection from \'%s\'', 294*9c5db199SXin Li stack) 295*9c5db199SXin Li self.restart_main_ssh() 296*9c5db199SXin Li # Last retry: reinstate timeout behavior. 297*9c5db199SXin Li ignore_timeout = original_ignore_timeout 298*9c5db199SXin Li ssh_failure_retry_count -= 1 299*9c5db199SXin Li continue 300*9c5db199SXin Li 301*9c5db199SXin Li # No retry conditions occurred. Exit the loop. 302*9c5db199SXin Li break 303*9c5db199SXin Li 304*9c5db199SXin Li # The outcomes of ssh invocations have been recorded. Now record 305*9c5db199SXin Li # the outcome of this function. 306*9c5db199SXin Li 307*9c5db199SXin Li if ignore_timeout and not result: 308*9c5db199SXin Li counters_inc('run', 'ignored_timeout') 309*9c5db199SXin Li return None 310*9c5db199SXin Li 311*9c5db199SXin Li # The error messages will show up in band (indistinguishable 312*9c5db199SXin Li # from stuff sent through the SSH connection), so we have the 313*9c5db199SXin Li # remote computer echo the message "Connected." before running 314*9c5db199SXin Li # any command. Since the following 2 errors have to do with 315*9c5db199SXin Li # connecting, it's safe to do these checks. 316*9c5db199SXin Li if result.exit_status == 255: 317*9c5db199SXin Li if re.search(r'^ssh: connect to host .* port .*: ' 318*9c5db199SXin Li r'Connection timed out\r$', result.stderr): 319*9c5db199SXin Li counters_inc('run', 'final_timeout') 320*9c5db199SXin Li raise error.AutoservSSHTimeout( 321*9c5db199SXin Li "ssh timed out: %r" % original_cmd.strip(), result) 322*9c5db199SXin Li if "Permission denied." in result.stderr: 323*9c5db199SXin Li msg = "ssh permission denied" 324*9c5db199SXin Li counters_inc('run', 'final_eperm') 325*9c5db199SXin Li raise error.AutoservSshPermissionDeniedError(msg, result) 326*9c5db199SXin Li 327*9c5db199SXin Li if not ignore_status and result.exit_status > 0: 328*9c5db199SXin Li counters_inc('run', 'final_run_error') 329*9c5db199SXin Li msg = result.stderr.strip() 330*9c5db199SXin Li if not msg: 331*9c5db199SXin Li msg = result.stdout.strip() 332*9c5db199SXin Li if msg: 333*9c5db199SXin Li msg = msg.splitlines()[-1] 334*9c5db199SXin Li raise error.AutoservRunError("command execution error (%d): %r" % 335*9c5db199SXin Li (result.exit_status, msg), result) 336*9c5db199SXin Li 337*9c5db199SXin Li counters_inc('run', failure_name) 338*9c5db199SXin Li return result 339*9c5db199SXin Li 340*9c5db199SXin Li def set_default_run_timeout(self, timeout): 341*9c5db199SXin Li """Set the default timeout for run.""" 342*9c5db199SXin Li if timeout < 0: 343*9c5db199SXin Li raise error.TestError('Invalid timeout %d', timeout) 344*9c5db199SXin Li self._default_run_timeout = timeout 345*9c5db199SXin Li 346*9c5db199SXin Li @THIS_IS_SLOW 347*9c5db199SXin Li def run(self, command, timeout=None, ignore_status=False, 348*9c5db199SXin Li stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS, 349*9c5db199SXin Li connect_timeout=30, options='', stdin=None, verbose=True, args=(), 350*9c5db199SXin Li ignore_timeout=False, ssh_failure_retry_ok=False): 351*9c5db199SXin Li """ 352*9c5db199SXin Li Run a command on the remote host. 353*9c5db199SXin Li @note: This RPC call has an overhead of minimum 40ms and up to 400ms on 354*9c5db199SXin Li servers (crbug.com/734887). Each time a call is added for 355*9c5db199SXin Li every job, a server core dies in the lab. 356*9c5db199SXin Li @see: common_lib.hosts.host.run() 357*9c5db199SXin Li 358*9c5db199SXin Li @param timeout: command execution timeout in seconds. Default is 359*9c5db199SXin Li _default_run_timeout (1 hour). 360*9c5db199SXin Li @param connect_timeout: ssh connection timeout (in seconds) 361*9c5db199SXin Li @param options: string with additional ssh command options 362*9c5db199SXin Li @param verbose: log the commands 363*9c5db199SXin Li @param ignore_timeout: bool True if SSH command timeouts should be 364*9c5db199SXin Li ignored. Will return None on command timeout. 365*9c5db199SXin Li @param ssh_failure_retry_ok: True if the command may be retried on 366*9c5db199SXin Li probable ssh failure (error 255 or timeout). When true, 367*9c5db199SXin Li the command may be executed up to three times, the second 368*9c5db199SXin Li time after restarting the ssh main connection. Use only for 369*9c5db199SXin Li commands that are idempotent, because when a "probable 370*9c5db199SXin Li ssh failure" occurs, we cannot tell if the command executed 371*9c5db199SXin Li or not. 372*9c5db199SXin Li 373*9c5db199SXin Li @raises AutoservRunError: if the command failed 374*9c5db199SXin Li @raises AutoservSSHTimeout: ssh connection has timed out 375*9c5db199SXin Li """ 376*9c5db199SXin Li # For example if the command is a list, we need to convert it to a 377*9c5db199SXin Li # string first. 378*9c5db199SXin Li if not isinstance(command, six.string_types): 379*9c5db199SXin Li command = ' '.join(command) 380*9c5db199SXin Li 381*9c5db199SXin Li if timeout is None: 382*9c5db199SXin Li timeout = self._default_run_timeout 383*9c5db199SXin Li start_time = time.time() 384*9c5db199SXin Li with metrics.SecondsTimer('chromeos/autotest/ssh/main_ssh_time', 385*9c5db199SXin Li scale=0.001): 386*9c5db199SXin Li 387*9c5db199SXin Li self.start_main_ssh(min( 388*9c5db199SXin Li timeout, 389*9c5db199SXin Li self.DEFAULT_START_MAIN_SSH_TIMEOUT_S, 390*9c5db199SXin Li )) 391*9c5db199SXin Li 392*9c5db199SXin Li env = " ".join("=".join(pair) for pair in six.iteritems(self.env)) 393*9c5db199SXin Li elapsed = time.time() - start_time 394*9c5db199SXin Li try: 395*9c5db199SXin Li return self._run(command, timeout - elapsed, ignore_status, 396*9c5db199SXin Li stdout_tee, stderr_tee, connect_timeout, env, 397*9c5db199SXin Li options, stdin, args, ignore_timeout, 398*9c5db199SXin Li ssh_failure_retry_ok, verbose) 399*9c5db199SXin Li except error.CmdError as cmderr: 400*9c5db199SXin Li # We get a CmdError here only if there is timeout of that 401*9c5db199SXin Li # command. Catch that and stuff it into AutoservRunError and 402*9c5db199SXin Li # raise it. 403*9c5db199SXin Li timeout_message = str('Timeout encountered: %s' % 404*9c5db199SXin Li cmderr.args[0]) 405*9c5db199SXin Li raise error.AutoservRunError(timeout_message, cmderr.args[1]) 406*9c5db199SXin Li 407*9c5db199SXin Li 408*9c5db199SXin Li def run_background(self, command, verbose=True): 409*9c5db199SXin Li """Start a command on the host in the background. 410*9c5db199SXin Li 411*9c5db199SXin Li The command is started on the host in the background, and 412*9c5db199SXin Li this method call returns immediately without waiting for the 413*9c5db199SXin Li command's completion. The PID of the process on the host is 414*9c5db199SXin Li returned as a string. 415*9c5db199SXin Li 416*9c5db199SXin Li The command may redirect its stdin, stdout, or stderr as 417*9c5db199SXin Li necessary. Without redirection, all input and output will 418*9c5db199SXin Li use /dev/null. 419*9c5db199SXin Li 420*9c5db199SXin Li @param command The command to run in the background 421*9c5db199SXin Li @param verbose As for `self.run()` 422*9c5db199SXin Li 423*9c5db199SXin Li @return Returns the PID of the remote background process 424*9c5db199SXin Li as a string. 425*9c5db199SXin Li """ 426*9c5db199SXin Li # Redirection here isn't merely hygienic; it's a functional 427*9c5db199SXin Li # requirement. sshd won't terminate until stdin, stdout, 428*9c5db199SXin Li # and stderr are all closed. 429*9c5db199SXin Li # 430*9c5db199SXin Li # The subshell is needed to do the right thing in case the 431*9c5db199SXin Li # passed in command has its own I/O redirections. 432*9c5db199SXin Li cmd_fmt = '( %s ) </dev/null >/dev/null 2>&1 & echo -n $!' 433*9c5db199SXin Li return self.run(cmd_fmt % command, verbose=verbose).stdout 434*9c5db199SXin Li 435*9c5db199SXin Li 436*9c5db199SXin Li def run_short(self, command, **kwargs): 437*9c5db199SXin Li """ 438*9c5db199SXin Li Calls the run() command with a short default timeout. 439*9c5db199SXin Li 440*9c5db199SXin Li Takes the same arguments as does run(), 441*9c5db199SXin Li with the exception of the timeout argument which 442*9c5db199SXin Li here is fixed at 60 seconds. 443*9c5db199SXin Li It returns the result of run. 444*9c5db199SXin Li 445*9c5db199SXin Li @param command: the command line string 446*9c5db199SXin Li 447*9c5db199SXin Li """ 448*9c5db199SXin Li return self.run(command, timeout=60, **kwargs) 449*9c5db199SXin Li 450*9c5db199SXin Li 451*9c5db199SXin Li def run_grep(self, command, timeout=30, ignore_status=False, 452*9c5db199SXin Li stdout_ok_regexp=None, stdout_err_regexp=None, 453*9c5db199SXin Li stderr_ok_regexp=None, stderr_err_regexp=None, 454*9c5db199SXin Li connect_timeout=30): 455*9c5db199SXin Li """ 456*9c5db199SXin Li Run a command on the remote host and look for regexp 457*9c5db199SXin Li in stdout or stderr to determine if the command was 458*9c5db199SXin Li successul or not. 459*9c5db199SXin Li 460*9c5db199SXin Li 461*9c5db199SXin Li @param command: the command line string 462*9c5db199SXin Li @param timeout: time limit in seconds before attempting to 463*9c5db199SXin Li kill the running process. The run() function 464*9c5db199SXin Li will take a few seconds longer than 'timeout' 465*9c5db199SXin Li to complete if it has to kill the process. 466*9c5db199SXin Li @param ignore_status: do not raise an exception, no matter 467*9c5db199SXin Li what the exit code of the command is. 468*9c5db199SXin Li @param stdout_ok_regexp: regexp that should be in stdout 469*9c5db199SXin Li if the command was successul. 470*9c5db199SXin Li @param stdout_err_regexp: regexp that should be in stdout 471*9c5db199SXin Li if the command failed. 472*9c5db199SXin Li @param stderr_ok_regexp: regexp that should be in stderr 473*9c5db199SXin Li if the command was successul. 474*9c5db199SXin Li @param stderr_err_regexp: regexp that should be in stderr 475*9c5db199SXin Li if the command failed. 476*9c5db199SXin Li @param connect_timeout: connection timeout (in seconds) 477*9c5db199SXin Li 478*9c5db199SXin Li Returns: 479*9c5db199SXin Li if the command was successul, raises an exception 480*9c5db199SXin Li otherwise. 481*9c5db199SXin Li 482*9c5db199SXin Li Raises: 483*9c5db199SXin Li AutoservRunError: 484*9c5db199SXin Li - the exit code of the command execution was not 0. 485*9c5db199SXin Li - If stderr_err_regexp is found in stderr, 486*9c5db199SXin Li - If stdout_err_regexp is found in stdout, 487*9c5db199SXin Li - If stderr_ok_regexp is not found in stderr. 488*9c5db199SXin Li - If stdout_ok_regexp is not found in stdout, 489*9c5db199SXin Li """ 490*9c5db199SXin Li 491*9c5db199SXin Li # We ignore the status, because we will handle it at the end. 492*9c5db199SXin Li result = self.run(command, timeout, ignore_status=True, 493*9c5db199SXin Li connect_timeout=connect_timeout) 494*9c5db199SXin Li 495*9c5db199SXin Li # Look for the patterns, in order 496*9c5db199SXin Li for (regexp, stream) in ((stderr_err_regexp, result.stderr), 497*9c5db199SXin Li (stdout_err_regexp, result.stdout)): 498*9c5db199SXin Li if regexp and stream: 499*9c5db199SXin Li err_re = re.compile (regexp) 500*9c5db199SXin Li if err_re.search(stream): 501*9c5db199SXin Li raise error.AutoservRunError( 502*9c5db199SXin Li '%r failed, found error pattern: %r' % (command, 503*9c5db199SXin Li regexp), result) 504*9c5db199SXin Li 505*9c5db199SXin Li for (regexp, stream) in ((stderr_ok_regexp, result.stderr), 506*9c5db199SXin Li (stdout_ok_regexp, result.stdout)): 507*9c5db199SXin Li if regexp and stream: 508*9c5db199SXin Li ok_re = re.compile (regexp) 509*9c5db199SXin Li if ok_re.search(stream): 510*9c5db199SXin Li if ok_re.search(stream): 511*9c5db199SXin Li return 512*9c5db199SXin Li 513*9c5db199SXin Li if not ignore_status and result.exit_status > 0: 514*9c5db199SXin Li msg = result.stderr.strip() 515*9c5db199SXin Li if not msg: 516*9c5db199SXin Li msg = result.stdout.strip() 517*9c5db199SXin Li if msg: 518*9c5db199SXin Li msg = msg.splitlines()[-1] 519*9c5db199SXin Li raise error.AutoservRunError("command execution error (%d): %r" % 520*9c5db199SXin Li (result.exit_status, msg), result) 521*9c5db199SXin Li 522*9c5db199SXin Li 523*9c5db199SXin Li def setup_ssh_key(self): 524*9c5db199SXin Li """Setup SSH Key""" 525*9c5db199SXin Li logging.debug('Performing SSH key setup on %s as %s.', 526*9c5db199SXin Li self.host_port, self.user) 527*9c5db199SXin Li 528*9c5db199SXin Li try: 529*9c5db199SXin Li host = pxssh.pxssh() 530*9c5db199SXin Li host.login(self.hostname, self.user, self.password, 531*9c5db199SXin Li port=self.port) 532*9c5db199SXin Li public_key = utils.get_public_key() 533*9c5db199SXin Li 534*9c5db199SXin Li host.sendline('mkdir -p ~/.ssh') 535*9c5db199SXin Li host.prompt() 536*9c5db199SXin Li host.sendline('chmod 700 ~/.ssh') 537*9c5db199SXin Li host.prompt() 538*9c5db199SXin Li host.sendline("echo '%s' >> ~/.ssh/authorized_keys; " % 539*9c5db199SXin Li public_key) 540*9c5db199SXin Li host.prompt() 541*9c5db199SXin Li host.sendline('chmod 600 ~/.ssh/authorized_keys') 542*9c5db199SXin Li host.prompt() 543*9c5db199SXin Li host.logout() 544*9c5db199SXin Li 545*9c5db199SXin Li logging.debug('SSH key setup complete.') 546*9c5db199SXin Li 547*9c5db199SXin Li except: 548*9c5db199SXin Li logging.debug('SSH key setup has failed.') 549*9c5db199SXin Li try: 550*9c5db199SXin Li host.logout() 551*9c5db199SXin Li except: 552*9c5db199SXin Li pass 553*9c5db199SXin Li 554*9c5db199SXin Li 555*9c5db199SXin Li def setup_ssh(self): 556*9c5db199SXin Li """Setup SSH""" 557*9c5db199SXin Li if self.password: 558*9c5db199SXin Li try: 559*9c5db199SXin Li self.ssh_ping() 560*9c5db199SXin Li except error.AutoservSshPingHostError: 561*9c5db199SXin Li self.setup_ssh_key() 562