1cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Copyright 2013 The Chromium Authors. All rights reserved. 2cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Use of this source code is governed by a BSD-style license that can be 3cef7893435aa41160dd1255c43cb8498279738ccChris Craik# found in the LICENSE file. 4cef7893435aa41160dd1255c43cb8498279738ccChris Craik"""A wrapper around ssh for common operations on a CrOS-based device""" 5cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport logging 6cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport os 7cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport re 8cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport shutil 9cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport stat 10cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport subprocess 11cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport tempfile 12cef7893435aa41160dd1255c43cb8498279738ccChris Craik 13cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Some developers' workflow includes running the Chrome process from 14cef7893435aa41160dd1255c43cb8498279738ccChris Craik# /usr/local/... instead of the default location. We have to check for both 15cef7893435aa41160dd1255c43cb8498279738ccChris Craik# paths in order to support this workflow. 16cef7893435aa41160dd1255c43cb8498279738ccChris Craik_CHROME_PROCESS_REGEX = [re.compile(r'^/opt/google/chrome/chrome '), 17cef7893435aa41160dd1255c43cb8498279738ccChris Craik re.compile(r'^/usr/local/?.*/chrome/chrome ')] 18cef7893435aa41160dd1255c43cb8498279738ccChris Craik 19cef7893435aa41160dd1255c43cb8498279738ccChris Craik 20cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef RunCmd(args, cwd=None, quiet=False): 21cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Opens a subprocess to execute a program and returns its return value. 22cef7893435aa41160dd1255c43cb8498279738ccChris Craik 23cef7893435aa41160dd1255c43cb8498279738ccChris Craik Args: 24cef7893435aa41160dd1255c43cb8498279738ccChris Craik args: A string or a sequence of program arguments. The program to execute is 25cef7893435aa41160dd1255c43cb8498279738ccChris Craik the string or the first item in the args sequence. 26cef7893435aa41160dd1255c43cb8498279738ccChris Craik cwd: If not None, the subprocess's current directory will be changed to 27cef7893435aa41160dd1255c43cb8498279738ccChris Craik |cwd| before it's executed. 28cef7893435aa41160dd1255c43cb8498279738ccChris Craik 29cef7893435aa41160dd1255c43cb8498279738ccChris Craik Returns: 30cef7893435aa41160dd1255c43cb8498279738ccChris Craik Return code from the command execution. 31cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 32cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not quiet: 33cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug(' '.join(args) + ' ' + (cwd or '')) 34cef7893435aa41160dd1255c43cb8498279738ccChris Craik with open(os.devnull, 'w') as devnull: 35cef7893435aa41160dd1255c43cb8498279738ccChris Craik p = subprocess.Popen(args=args, 36cef7893435aa41160dd1255c43cb8498279738ccChris Craik cwd=cwd, 37cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout=devnull, 38cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr=devnull, 39cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdin=devnull, 40cef7893435aa41160dd1255c43cb8498279738ccChris Craik shell=False) 41cef7893435aa41160dd1255c43cb8498279738ccChris Craik return p.wait() 42cef7893435aa41160dd1255c43cb8498279738ccChris Craik 43cef7893435aa41160dd1255c43cb8498279738ccChris Craik 44cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef GetAllCmdOutput(args, cwd=None, quiet=False): 45cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Open a subprocess to execute a program and returns its output. 46cef7893435aa41160dd1255c43cb8498279738ccChris Craik 47cef7893435aa41160dd1255c43cb8498279738ccChris Craik Args: 48cef7893435aa41160dd1255c43cb8498279738ccChris Craik args: A string or a sequence of program arguments. The program to execute is 49cef7893435aa41160dd1255c43cb8498279738ccChris Craik the string or the first item in the args sequence. 50cef7893435aa41160dd1255c43cb8498279738ccChris Craik cwd: If not None, the subprocess's current directory will be changed to 51cef7893435aa41160dd1255c43cb8498279738ccChris Craik |cwd| before it's executed. 52cef7893435aa41160dd1255c43cb8498279738ccChris Craik 53cef7893435aa41160dd1255c43cb8498279738ccChris Craik Returns: 54cef7893435aa41160dd1255c43cb8498279738ccChris Craik Captures and returns the command's stdout. 55cef7893435aa41160dd1255c43cb8498279738ccChris Craik Prints the command's stderr to logger (which defaults to stdout). 56cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 57cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not quiet: 58cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug(' '.join(args) + ' ' + (cwd or '')) 59cef7893435aa41160dd1255c43cb8498279738ccChris Craik with open(os.devnull, 'w') as devnull: 60cef7893435aa41160dd1255c43cb8498279738ccChris Craik p = subprocess.Popen(args=args, 61cef7893435aa41160dd1255c43cb8498279738ccChris Craik cwd=cwd, 62cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout=subprocess.PIPE, 63cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr=subprocess.PIPE, 64cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdin=devnull) 65cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = p.communicate() 66cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not quiet: 67cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug(' > stdout=[%s], stderr=[%s]', stdout, stderr) 68cef7893435aa41160dd1255c43cb8498279738ccChris Craik return stdout, stderr 69cef7893435aa41160dd1255c43cb8498279738ccChris Craik 70cef7893435aa41160dd1255c43cb8498279738ccChris Craik 71cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef HasSSH(): 72cef7893435aa41160dd1255c43cb8498279738ccChris Craik try: 73cef7893435aa41160dd1255c43cb8498279738ccChris Craik RunCmd(['ssh'], quiet=True) 74cef7893435aa41160dd1255c43cb8498279738ccChris Craik RunCmd(['scp'], quiet=True) 75cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("HasSSH()->True") 76cef7893435aa41160dd1255c43cb8498279738ccChris Craik return True 77cef7893435aa41160dd1255c43cb8498279738ccChris Craik except OSError: 78cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("HasSSH()->False") 79cef7893435aa41160dd1255c43cb8498279738ccChris Craik return False 80cef7893435aa41160dd1255c43cb8498279738ccChris Craik 81cef7893435aa41160dd1255c43cb8498279738ccChris Craik 82cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass LoginException(Exception): 83cef7893435aa41160dd1255c43cb8498279738ccChris Craik pass 84cef7893435aa41160dd1255c43cb8498279738ccChris Craik 85cef7893435aa41160dd1255c43cb8498279738ccChris Craik 86cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass KeylessLoginRequiredException(LoginException): 87cef7893435aa41160dd1255c43cb8498279738ccChris Craik pass 88cef7893435aa41160dd1255c43cb8498279738ccChris Craik 89cef7893435aa41160dd1255c43cb8498279738ccChris Craik 90cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass DNSFailureException(LoginException): 91cef7893435aa41160dd1255c43cb8498279738ccChris Craik pass 92cef7893435aa41160dd1255c43cb8498279738ccChris Craik 93cef7893435aa41160dd1255c43cb8498279738ccChris Craik 94cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass CrOSInterface(object): 95cef7893435aa41160dd1255c43cb8498279738ccChris Craik 96cef7893435aa41160dd1255c43cb8498279738ccChris Craik def __init__(self, hostname=None, ssh_port=None, ssh_identity=None): 97cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._hostname = hostname 98cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_port = ssh_port 99cef7893435aa41160dd1255c43cb8498279738ccChris Craik 100cef7893435aa41160dd1255c43cb8498279738ccChris Craik # List of ports generated from GetRemotePort() that may not be in use yet. 101cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._reserved_ports = [] 102cef7893435aa41160dd1255c43cb8498279738ccChris Craik 103cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.local: 104cef7893435aa41160dd1255c43cb8498279738ccChris Craik return 105cef7893435aa41160dd1255c43cb8498279738ccChris Craik 106cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_identity = None 107cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_args = ['-o ConnectTimeout=5', '-o StrictHostKeyChecking=no', 108cef7893435aa41160dd1255c43cb8498279738ccChris Craik '-o KbdInteractiveAuthentication=no', 109cef7893435aa41160dd1255c43cb8498279738ccChris Craik '-o PreferredAuthentications=publickey', 110cef7893435aa41160dd1255c43cb8498279738ccChris Craik '-o UserKnownHostsFile=/dev/null', '-o ControlMaster=no'] 111cef7893435aa41160dd1255c43cb8498279738ccChris Craik 112cef7893435aa41160dd1255c43cb8498279738ccChris Craik if ssh_identity: 113cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_identity = os.path.abspath(os.path.expanduser(ssh_identity)) 114cef7893435aa41160dd1255c43cb8498279738ccChris Craik os.chmod(self._ssh_identity, stat.S_IREAD) 115cef7893435aa41160dd1255c43cb8498279738ccChris Craik 116cef7893435aa41160dd1255c43cb8498279738ccChris Craik # Establish master SSH connection using ControlPersist. 117cef7893435aa41160dd1255c43cb8498279738ccChris Craik # Since only one test will be run on a remote host at a time, 118cef7893435aa41160dd1255c43cb8498279738ccChris Craik # the control socket filename can be telemetry@hostname. 119cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_control_file = '/tmp/' + 'telemetry' + '@' + hostname 120cef7893435aa41160dd1255c43cb8498279738ccChris Craik with open(os.devnull, 'w') as devnull: 121cef7893435aa41160dd1255c43cb8498279738ccChris Craik subprocess.call( 122cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.FormSSHCommandLine(['-M', '-o ControlPersist=yes']), 123cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdin=devnull, 124cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout=devnull, 125cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr=devnull) 126cef7893435aa41160dd1255c43cb8498279738ccChris Craik 127cef7893435aa41160dd1255c43cb8498279738ccChris Craik def __enter__(self): 128cef7893435aa41160dd1255c43cb8498279738ccChris Craik return self 129cef7893435aa41160dd1255c43cb8498279738ccChris Craik 130cef7893435aa41160dd1255c43cb8498279738ccChris Craik def __exit__(self, *args): 131cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.CloseConnection() 132cef7893435aa41160dd1255c43cb8498279738ccChris Craik 133cef7893435aa41160dd1255c43cb8498279738ccChris Craik @property 134cef7893435aa41160dd1255c43cb8498279738ccChris Craik def local(self): 135cef7893435aa41160dd1255c43cb8498279738ccChris Craik return not self._hostname 136cef7893435aa41160dd1255c43cb8498279738ccChris Craik 137cef7893435aa41160dd1255c43cb8498279738ccChris Craik @property 138cef7893435aa41160dd1255c43cb8498279738ccChris Craik def hostname(self): 139cef7893435aa41160dd1255c43cb8498279738ccChris Craik return self._hostname 140cef7893435aa41160dd1255c43cb8498279738ccChris Craik 141cef7893435aa41160dd1255c43cb8498279738ccChris Craik @property 142cef7893435aa41160dd1255c43cb8498279738ccChris Craik def ssh_port(self): 143cef7893435aa41160dd1255c43cb8498279738ccChris Craik return self._ssh_port 144cef7893435aa41160dd1255c43cb8498279738ccChris Craik 145cef7893435aa41160dd1255c43cb8498279738ccChris Craik def FormSSHCommandLine(self, args, extra_ssh_args=None): 146cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Constructs a subprocess-suitable command line for `ssh'. 147cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 148cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.local: 149cef7893435aa41160dd1255c43cb8498279738ccChris Craik # We run the command through the shell locally for consistency with 150cef7893435aa41160dd1255c43cb8498279738ccChris Craik # how commands are run through SSH (crbug.com/239161). This work 151cef7893435aa41160dd1255c43cb8498279738ccChris Craik # around will be unnecessary once we implement a persistent SSH 152cef7893435aa41160dd1255c43cb8498279738ccChris Craik # connection to run remote commands (crbug.com/239607). 153cef7893435aa41160dd1255c43cb8498279738ccChris Craik return ['sh', '-c', " ".join(args)] 154cef7893435aa41160dd1255c43cb8498279738ccChris Craik 155cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args = ['ssh', '-o ForwardX11=no', '-o ForwardX11Trusted=no', '-n', 156cef7893435aa41160dd1255c43cb8498279738ccChris Craik '-S', self._ssh_control_file] + self._ssh_args 157cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self._ssh_identity is not None: 158cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args.extend(['-i', self._ssh_identity]) 159cef7893435aa41160dd1255c43cb8498279738ccChris Craik if extra_ssh_args: 160cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args.extend(extra_ssh_args) 161cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args.append('root@%s' % self._hostname) 162cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args.append('-p%d' % self._ssh_port) 163cef7893435aa41160dd1255c43cb8498279738ccChris Craik full_args.extend(args) 164cef7893435aa41160dd1255c43cb8498279738ccChris Craik return full_args 165cef7893435aa41160dd1255c43cb8498279738ccChris Craik 166cef7893435aa41160dd1255c43cb8498279738ccChris Craik def _FormSCPCommandLine(self, src, dst, extra_scp_args=None): 167cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Constructs a subprocess-suitable command line for `scp'. 168cef7893435aa41160dd1255c43cb8498279738ccChris Craik 169cef7893435aa41160dd1255c43cb8498279738ccChris Craik Note: this function is not designed to work with IPv6 addresses, which need 170cef7893435aa41160dd1255c43cb8498279738ccChris Craik to have their addresses enclosed in brackets and a '-6' flag supplied 171cef7893435aa41160dd1255c43cb8498279738ccChris Craik in order to be properly parsed by `scp'. 172cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 173cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert not self.local, "Cannot use SCP on local target." 174cef7893435aa41160dd1255c43cb8498279738ccChris Craik 175cef7893435aa41160dd1255c43cb8498279738ccChris Craik args = ['scp', '-P', str(self._ssh_port)] + self._ssh_args 176cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self._ssh_identity: 177cef7893435aa41160dd1255c43cb8498279738ccChris Craik args.extend(['-i', self._ssh_identity]) 178cef7893435aa41160dd1255c43cb8498279738ccChris Craik if extra_scp_args: 179cef7893435aa41160dd1255c43cb8498279738ccChris Craik args.extend(extra_scp_args) 180cef7893435aa41160dd1255c43cb8498279738ccChris Craik args += [src, dst] 181cef7893435aa41160dd1255c43cb8498279738ccChris Craik return args 182cef7893435aa41160dd1255c43cb8498279738ccChris Craik 183cef7893435aa41160dd1255c43cb8498279738ccChris Craik def _FormSCPToRemote(self, 184cef7893435aa41160dd1255c43cb8498279738ccChris Craik source, 185cef7893435aa41160dd1255c43cb8498279738ccChris Craik remote_dest, 186cef7893435aa41160dd1255c43cb8498279738ccChris Craik extra_scp_args=None, 187cef7893435aa41160dd1255c43cb8498279738ccChris Craik user='root'): 188cef7893435aa41160dd1255c43cb8498279738ccChris Craik return self._FormSCPCommandLine(source, 189cef7893435aa41160dd1255c43cb8498279738ccChris Craik '%s@%s:%s' % (user, self._hostname, 190cef7893435aa41160dd1255c43cb8498279738ccChris Craik remote_dest), 191cef7893435aa41160dd1255c43cb8498279738ccChris Craik extra_scp_args=extra_scp_args) 192cef7893435aa41160dd1255c43cb8498279738ccChris Craik 193cef7893435aa41160dd1255c43cb8498279738ccChris Craik def _FormSCPFromRemote(self, 194cef7893435aa41160dd1255c43cb8498279738ccChris Craik remote_source, 195cef7893435aa41160dd1255c43cb8498279738ccChris Craik dest, 196cef7893435aa41160dd1255c43cb8498279738ccChris Craik extra_scp_args=None, 197cef7893435aa41160dd1255c43cb8498279738ccChris Craik user='root'): 198cef7893435aa41160dd1255c43cb8498279738ccChris Craik return self._FormSCPCommandLine('%s@%s:%s' % (user, self._hostname, 199cef7893435aa41160dd1255c43cb8498279738ccChris Craik remote_source), 200cef7893435aa41160dd1255c43cb8498279738ccChris Craik dest, 201cef7893435aa41160dd1255c43cb8498279738ccChris Craik extra_scp_args=extra_scp_args) 202cef7893435aa41160dd1255c43cb8498279738ccChris Craik 203cef7893435aa41160dd1255c43cb8498279738ccChris Craik def _RemoveSSHWarnings(self, toClean): 204cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Removes specific ssh warning lines from a string. 205cef7893435aa41160dd1255c43cb8498279738ccChris Craik 206cef7893435aa41160dd1255c43cb8498279738ccChris Craik Args: 207cef7893435aa41160dd1255c43cb8498279738ccChris Craik toClean: A string that may be containing multiple lines. 208cef7893435aa41160dd1255c43cb8498279738ccChris Craik 209cef7893435aa41160dd1255c43cb8498279738ccChris Craik Returns: 210cef7893435aa41160dd1255c43cb8498279738ccChris Craik A copy of toClean with all the Warning lines removed. 211cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 212cef7893435aa41160dd1255c43cb8498279738ccChris Craik # Remove the Warning about connecting to a new host for the first time. 213cef7893435aa41160dd1255c43cb8498279738ccChris Craik return re.sub( 214cef7893435aa41160dd1255c43cb8498279738ccChris Craik r'Warning: Permanently added [^\n]* to the list of known hosts.\s\n', 215cef7893435aa41160dd1255c43cb8498279738ccChris Craik '', toClean) 216cef7893435aa41160dd1255c43cb8498279738ccChris Craik 217cef7893435aa41160dd1255c43cb8498279738ccChris Craik def RunCmdOnDevice(self, args, cwd=None, quiet=False): 218cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = GetAllCmdOutput( 219cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.FormSSHCommandLine(args), 220cef7893435aa41160dd1255c43cb8498279738ccChris Craik cwd, 221cef7893435aa41160dd1255c43cb8498279738ccChris Craik quiet=quiet) 222cef7893435aa41160dd1255c43cb8498279738ccChris Craik # The initial login will add the host to the hosts file but will also print 223cef7893435aa41160dd1255c43cb8498279738ccChris Craik # a warning to stderr that we need to remove. 224cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr = self._RemoveSSHWarnings(stderr) 225cef7893435aa41160dd1255c43cb8498279738ccChris Craik return stdout, stderr 226cef7893435aa41160dd1255c43cb8498279738ccChris Craik 227cef7893435aa41160dd1255c43cb8498279738ccChris Craik def TryLogin(self): 228cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug('TryLogin()') 229cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert not self.local 230cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = self.RunCmdOnDevice(['echo', '$USER'], quiet=True) 231cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 232cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'Host key verification failed' in stderr: 233cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise LoginException(('%s host key verification failed. ' + 234cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'SSH to it manually to fix connectivity.') % 235cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._hostname) 236cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'Operation timed out' in stderr: 237cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise LoginException('Timed out while logging into %s' % self._hostname) 238cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'UNPROTECTED PRIVATE KEY FILE!' in stderr: 239cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise LoginException('Permissions for %s are too open. To fix this,\n' 240cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'chmod 600 %s' % (self._ssh_identity, 241cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._ssh_identity)) 242cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'Permission denied (publickey,keyboard-interactive)' in stderr: 243cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise KeylessLoginRequiredException('Need to set up ssh auth for %s' % 244cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._hostname) 245cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'Could not resolve hostname' in stderr: 246cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise DNSFailureException('Unable to resolve the hostname for: %s' % 247cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._hostname) 248cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise LoginException('While logging into %s, got %s' % (self._hostname, 249cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr)) 250cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stdout != 'root\n': 251cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise LoginException('Logged into %s, expected $USER=root, but got %s.' % 252cef7893435aa41160dd1255c43cb8498279738ccChris Craik (self._hostname, stdout)) 253cef7893435aa41160dd1255c43cb8498279738ccChris Craik 254cef7893435aa41160dd1255c43cb8498279738ccChris Craik def FileExistsOnDevice(self, file_name): 255cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.local: 256cef7893435aa41160dd1255c43cb8498279738ccChris Craik return os.path.exists(file_name) 257cef7893435aa41160dd1255c43cb8498279738ccChris Craik 258cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = self.RunCmdOnDevice( 259cef7893435aa41160dd1255c43cb8498279738ccChris Craik [ 260cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'if', 'test', '-e', file_name, ';', 'then', 'echo', '1', ';', 'fi' 261cef7893435aa41160dd1255c43cb8498279738ccChris Craik ], 262cef7893435aa41160dd1255c43cb8498279738ccChris Craik quiet=True) 263cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 264cef7893435aa41160dd1255c43cb8498279738ccChris Craik if "Connection timed out" in stderr: 265cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('Machine wasn\'t responding to ssh: %s' % stderr) 266cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('Unexpected error: %s' % stderr) 267cef7893435aa41160dd1255c43cb8498279738ccChris Craik exists = stdout == '1\n' 268cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("FileExistsOnDevice(<text>, %s)->%s" % (file_name, exists)) 269cef7893435aa41160dd1255c43cb8498279738ccChris Craik return exists 270cef7893435aa41160dd1255c43cb8498279738ccChris Craik 271cef7893435aa41160dd1255c43cb8498279738ccChris Craik def PushFile(self, filename, remote_filename): 272cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.local: 273cef7893435aa41160dd1255c43cb8498279738ccChris Craik args = ['cp', '-r', filename, remote_filename] 274cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = GetAllCmdOutput(args, quiet=True) 275cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 276cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('No such file or directory %s' % stderr) 277cef7893435aa41160dd1255c43cb8498279738ccChris Craik return 278cef7893435aa41160dd1255c43cb8498279738ccChris Craik 279cef7893435aa41160dd1255c43cb8498279738ccChris Craik args = self._FormSCPToRemote( 280cef7893435aa41160dd1255c43cb8498279738ccChris Craik os.path.abspath(filename), 281cef7893435aa41160dd1255c43cb8498279738ccChris Craik remote_filename, 282cef7893435aa41160dd1255c43cb8498279738ccChris Craik extra_scp_args=['-r']) 283cef7893435aa41160dd1255c43cb8498279738ccChris Craik 284cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = GetAllCmdOutput(args, quiet=True) 285cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr = self._RemoveSSHWarnings(stderr) 286cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 287cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('No such file or directory %s' % stderr) 288cef7893435aa41160dd1255c43cb8498279738ccChris Craik 289cef7893435aa41160dd1255c43cb8498279738ccChris Craik def PushContents(self, text, remote_filename): 290cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("PushContents(<text>, %s)" % remote_filename) 291cef7893435aa41160dd1255c43cb8498279738ccChris Craik with tempfile.NamedTemporaryFile() as f: 292cef7893435aa41160dd1255c43cb8498279738ccChris Craik f.write(text) 293cef7893435aa41160dd1255c43cb8498279738ccChris Craik f.flush() 294cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.PushFile(f.name, remote_filename) 295cef7893435aa41160dd1255c43cb8498279738ccChris Craik 296cef7893435aa41160dd1255c43cb8498279738ccChris Craik def GetFile(self, filename, destfile=None): 297cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Copies a local file |filename| to |destfile| on the device. 298cef7893435aa41160dd1255c43cb8498279738ccChris Craik 299cef7893435aa41160dd1255c43cb8498279738ccChris Craik Args: 300cef7893435aa41160dd1255c43cb8498279738ccChris Craik filename: The name of the local source file. 301cef7893435aa41160dd1255c43cb8498279738ccChris Craik destfile: The name of the file to copy to, and if it is not specified 302cef7893435aa41160dd1255c43cb8498279738ccChris Craik then it is the basename of the source file. 303cef7893435aa41160dd1255c43cb8498279738ccChris Craik 304cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 305cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("GetFile(%s, %s)" % (filename, destfile)) 306cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.local: 307cef7893435aa41160dd1255c43cb8498279738ccChris Craik if destfile is not None and destfile != filename: 308cef7893435aa41160dd1255c43cb8498279738ccChris Craik shutil.copyfile(filename, destfile) 309cef7893435aa41160dd1255c43cb8498279738ccChris Craik return 310cef7893435aa41160dd1255c43cb8498279738ccChris Craik 311cef7893435aa41160dd1255c43cb8498279738ccChris Craik if destfile is None: 312cef7893435aa41160dd1255c43cb8498279738ccChris Craik destfile = os.path.basename(filename) 313cef7893435aa41160dd1255c43cb8498279738ccChris Craik args = self._FormSCPFromRemote(filename, os.path.abspath(destfile)) 314cef7893435aa41160dd1255c43cb8498279738ccChris Craik 315cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = GetAllCmdOutput(args, quiet=True) 316cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr = self._RemoveSSHWarnings(stderr) 317cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 318cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('No such file or directory %s' % stderr) 319cef7893435aa41160dd1255c43cb8498279738ccChris Craik 320cef7893435aa41160dd1255c43cb8498279738ccChris Craik def GetFileContents(self, filename): 321cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Get the contents of a file on the device. 322cef7893435aa41160dd1255c43cb8498279738ccChris Craik 323cef7893435aa41160dd1255c43cb8498279738ccChris Craik Args: 324cef7893435aa41160dd1255c43cb8498279738ccChris Craik filename: The name of the file on the device. 325cef7893435aa41160dd1255c43cb8498279738ccChris Craik 326cef7893435aa41160dd1255c43cb8498279738ccChris Craik Returns: 327cef7893435aa41160dd1255c43cb8498279738ccChris Craik A string containing the contents of the file. 328cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 329cef7893435aa41160dd1255c43cb8498279738ccChris Craik # TODO: handle the self.local case 330cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert not self.local 331cef7893435aa41160dd1255c43cb8498279738ccChris Craik t = tempfile.NamedTemporaryFile() 332cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.GetFile(filename, t.name) 333cef7893435aa41160dd1255c43cb8498279738ccChris Craik with open(t.name, 'r') as f2: 334cef7893435aa41160dd1255c43cb8498279738ccChris Craik res = f2.read() 335cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("GetFileContents(%s)->%s" % (filename, res)) 336cef7893435aa41160dd1255c43cb8498279738ccChris Craik f2.close() 337cef7893435aa41160dd1255c43cb8498279738ccChris Craik return res 338cef7893435aa41160dd1255c43cb8498279738ccChris Craik 339cef7893435aa41160dd1255c43cb8498279738ccChris Craik def ListProcesses(self): 340cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns (pid, cmd, ppid, state) of all processes on the device.""" 341cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = self.RunCmdOnDevice( 342cef7893435aa41160dd1255c43cb8498279738ccChris Craik [ 343cef7893435aa41160dd1255c43cb8498279738ccChris Craik '/bin/ps', '--no-headers', '-A', '-o', 'pid,ppid,args:4096,state' 344cef7893435aa41160dd1255c43cb8498279738ccChris Craik ], 345cef7893435aa41160dd1255c43cb8498279738ccChris Craik quiet=True) 346cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert stderr == '', stderr 347cef7893435aa41160dd1255c43cb8498279738ccChris Craik procs = [] 348cef7893435aa41160dd1255c43cb8498279738ccChris Craik for l in stdout.split('\n'): 349cef7893435aa41160dd1255c43cb8498279738ccChris Craik if l == '': 350cef7893435aa41160dd1255c43cb8498279738ccChris Craik continue 351cef7893435aa41160dd1255c43cb8498279738ccChris Craik m = re.match(r'^\s*(\d+)\s+(\d+)\s+(.+)\s+(.+)', l, re.DOTALL) 352cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert m 353cef7893435aa41160dd1255c43cb8498279738ccChris Craik procs.append((int(m.group(1)), m.group(3).rstrip(), int(m.group(2)), 354cef7893435aa41160dd1255c43cb8498279738ccChris Craik m.group(4))) 355cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("ListProcesses(<predicate>)->[%i processes]" % len(procs)) 356cef7893435aa41160dd1255c43cb8498279738ccChris Craik return procs 357cef7893435aa41160dd1255c43cb8498279738ccChris Craik 358cef7893435aa41160dd1255c43cb8498279738ccChris Craik def _GetSessionManagerPid(self, procs): 359cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns the pid of the session_manager process, given the list of 360cef7893435aa41160dd1255c43cb8498279738ccChris Craik processes.""" 361cef7893435aa41160dd1255c43cb8498279738ccChris Craik for pid, process, _, _ in procs: 362cef7893435aa41160dd1255c43cb8498279738ccChris Craik argv = process.split() 363cef7893435aa41160dd1255c43cb8498279738ccChris Craik if argv and os.path.basename(argv[0]) == 'session_manager': 364cef7893435aa41160dd1255c43cb8498279738ccChris Craik return pid 365cef7893435aa41160dd1255c43cb8498279738ccChris Craik return None 366cef7893435aa41160dd1255c43cb8498279738ccChris Craik 367cef7893435aa41160dd1255c43cb8498279738ccChris Craik def GetChromeProcess(self): 368cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Locates the the main chrome browser process. 369cef7893435aa41160dd1255c43cb8498279738ccChris Craik 370cef7893435aa41160dd1255c43cb8498279738ccChris Craik Chrome on cros is usually in /opt/google/chrome, but could be in 371cef7893435aa41160dd1255c43cb8498279738ccChris Craik /usr/local/ for developer workflows - debug chrome is too large to fit on 372cef7893435aa41160dd1255c43cb8498279738ccChris Craik rootfs. 373cef7893435aa41160dd1255c43cb8498279738ccChris Craik 374cef7893435aa41160dd1255c43cb8498279738ccChris Craik Chrome spawns multiple processes for renderers. pids wrap around after they 375cef7893435aa41160dd1255c43cb8498279738ccChris Craik are exhausted so looking for the smallest pid is not always correct. We 376cef7893435aa41160dd1255c43cb8498279738ccChris Craik locate the session_manager's pid, and look for the chrome process that's an 377cef7893435aa41160dd1255c43cb8498279738ccChris Craik immediate child. This is the main browser process. 378cef7893435aa41160dd1255c43cb8498279738ccChris Craik """ 379cef7893435aa41160dd1255c43cb8498279738ccChris Craik procs = self.ListProcesses() 380cef7893435aa41160dd1255c43cb8498279738ccChris Craik session_manager_pid = self._GetSessionManagerPid(procs) 381cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not session_manager_pid: 382cef7893435aa41160dd1255c43cb8498279738ccChris Craik return None 383cef7893435aa41160dd1255c43cb8498279738ccChris Craik 384cef7893435aa41160dd1255c43cb8498279738ccChris Craik # Find the chrome process that is the child of the session_manager. 385cef7893435aa41160dd1255c43cb8498279738ccChris Craik for pid, process, ppid, _ in procs: 386cef7893435aa41160dd1255c43cb8498279738ccChris Craik if ppid != session_manager_pid: 387cef7893435aa41160dd1255c43cb8498279738ccChris Craik continue 388cef7893435aa41160dd1255c43cb8498279738ccChris Craik for regex in _CHROME_PROCESS_REGEX: 389cef7893435aa41160dd1255c43cb8498279738ccChris Craik path_match = re.match(regex, process) 390cef7893435aa41160dd1255c43cb8498279738ccChris Craik if path_match is not None: 391cef7893435aa41160dd1255c43cb8498279738ccChris Craik return {'pid': pid, 'path': path_match.group(), 'args': process} 392cef7893435aa41160dd1255c43cb8498279738ccChris Craik return None 393cef7893435aa41160dd1255c43cb8498279738ccChris Craik 394cef7893435aa41160dd1255c43cb8498279738ccChris Craik def GetChromePid(self): 395cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns pid of main chrome browser process.""" 396cef7893435aa41160dd1255c43cb8498279738ccChris Craik result = self.GetChromeProcess() 397cef7893435aa41160dd1255c43cb8498279738ccChris Craik if result and 'pid' in result: 398cef7893435aa41160dd1255c43cb8498279738ccChris Craik return result['pid'] 399cef7893435aa41160dd1255c43cb8498279738ccChris Craik return None 400cef7893435aa41160dd1255c43cb8498279738ccChris Craik 401cef7893435aa41160dd1255c43cb8498279738ccChris Craik def RmRF(self, filename): 402cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("rm -rf %s" % filename) 403cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['rm', '-rf', filename], quiet=True) 404cef7893435aa41160dd1255c43cb8498279738ccChris Craik 405cef7893435aa41160dd1255c43cb8498279738ccChris Craik def Chown(self, filename): 406cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['chown', '-R', 'chronos:chronos', filename]) 407cef7893435aa41160dd1255c43cb8498279738ccChris Craik 408cef7893435aa41160dd1255c43cb8498279738ccChris Craik def KillAllMatching(self, predicate): 409cef7893435aa41160dd1255c43cb8498279738ccChris Craik kills = ['kill', '-KILL'] 410cef7893435aa41160dd1255c43cb8498279738ccChris Craik for pid, cmd, _, _ in self.ListProcesses(): 411cef7893435aa41160dd1255c43cb8498279738ccChris Craik if predicate(cmd): 412cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.info('Killing %s, pid %d' % cmd, pid) 413cef7893435aa41160dd1255c43cb8498279738ccChris Craik kills.append(pid) 414cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("KillAllMatching(<predicate>)->%i" % (len(kills) - 2)) 415cef7893435aa41160dd1255c43cb8498279738ccChris Craik if len(kills) > 2: 416cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(kills, quiet=True) 417cef7893435aa41160dd1255c43cb8498279738ccChris Craik return len(kills) - 2 418cef7893435aa41160dd1255c43cb8498279738ccChris Craik 419cef7893435aa41160dd1255c43cb8498279738ccChris Craik def IsServiceRunning(self, service_name): 420cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = self.RunCmdOnDevice(['status', service_name], quiet=True) 421cef7893435aa41160dd1255c43cb8498279738ccChris Craik assert stderr == '', stderr 422cef7893435aa41160dd1255c43cb8498279738ccChris Craik running = 'running, process' in stdout 423cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.debug("IsServiceRunning(%s)->%s" % (service_name, running)) 424cef7893435aa41160dd1255c43cb8498279738ccChris Craik return running 425cef7893435aa41160dd1255c43cb8498279738ccChris Craik 426cef7893435aa41160dd1255c43cb8498279738ccChris Craik def GetRemotePort(self): 427cef7893435aa41160dd1255c43cb8498279738ccChris Craik netstat = self.RunCmdOnDevice(['netstat', '-ant']) 428cef7893435aa41160dd1255c43cb8498279738ccChris Craik netstat = netstat[0].split('\n') 429cef7893435aa41160dd1255c43cb8498279738ccChris Craik ports_in_use = [] 430cef7893435aa41160dd1255c43cb8498279738ccChris Craik 431cef7893435aa41160dd1255c43cb8498279738ccChris Craik for line in netstat[2:]: 432cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not line: 433cef7893435aa41160dd1255c43cb8498279738ccChris Craik continue 434cef7893435aa41160dd1255c43cb8498279738ccChris Craik address_in_use = line.split()[3] 435cef7893435aa41160dd1255c43cb8498279738ccChris Craik port_in_use = address_in_use.split(':')[-1] 436cef7893435aa41160dd1255c43cb8498279738ccChris Craik ports_in_use.append(int(port_in_use)) 437cef7893435aa41160dd1255c43cb8498279738ccChris Craik 438cef7893435aa41160dd1255c43cb8498279738ccChris Craik ports_in_use.extend(self._reserved_ports) 439cef7893435aa41160dd1255c43cb8498279738ccChris Craik 440cef7893435aa41160dd1255c43cb8498279738ccChris Craik new_port = sorted(ports_in_use)[-1] + 1 441cef7893435aa41160dd1255c43cb8498279738ccChris Craik self._reserved_ports.append(new_port) 442cef7893435aa41160dd1255c43cb8498279738ccChris Craik 443cef7893435aa41160dd1255c43cb8498279738ccChris Craik return new_port 444cef7893435aa41160dd1255c43cb8498279738ccChris Craik 445cef7893435aa41160dd1255c43cb8498279738ccChris Craik def IsHTTPServerRunningOnPort(self, port): 446cef7893435aa41160dd1255c43cb8498279738ccChris Craik wget_output = self.RunCmdOnDevice(['wget', 'localhost:%i' % (port), '-T1', 447cef7893435aa41160dd1255c43cb8498279738ccChris Craik '-t1']) 448cef7893435aa41160dd1255c43cb8498279738ccChris Craik 449cef7893435aa41160dd1255c43cb8498279738ccChris Craik if 'Connection refused' in wget_output[1]: 450cef7893435aa41160dd1255c43cb8498279738ccChris Craik return False 451cef7893435aa41160dd1255c43cb8498279738ccChris Craik 452cef7893435aa41160dd1255c43cb8498279738ccChris Craik return True 453cef7893435aa41160dd1255c43cb8498279738ccChris Craik 454cef7893435aa41160dd1255c43cb8498279738ccChris Craik def FilesystemMountedAt(self, path): 455cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns the filesystem mounted at |path|""" 456cef7893435aa41160dd1255c43cb8498279738ccChris Craik df_out, _ = self.RunCmdOnDevice(['/bin/df', path]) 457cef7893435aa41160dd1255c43cb8498279738ccChris Craik df_ary = df_out.split('\n') 458cef7893435aa41160dd1255c43cb8498279738ccChris Craik # 3 lines for title, mount info, and empty line. 459cef7893435aa41160dd1255c43cb8498279738ccChris Craik if len(df_ary) == 3: 460cef7893435aa41160dd1255c43cb8498279738ccChris Craik line_ary = df_ary[1].split() 461cef7893435aa41160dd1255c43cb8498279738ccChris Craik if line_ary: 462cef7893435aa41160dd1255c43cb8498279738ccChris Craik return line_ary[0] 463cef7893435aa41160dd1255c43cb8498279738ccChris Craik return None 464cef7893435aa41160dd1255c43cb8498279738ccChris Craik 465cef7893435aa41160dd1255c43cb8498279738ccChris Craik def CryptohomePath(self, user): 466cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns the cryptohome mount point for |user|.""" 467cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout, stderr = self.RunCmdOnDevice(['cryptohome-path', 'user', "'%s'" % 468cef7893435aa41160dd1255c43cb8498279738ccChris Craik user]) 469cef7893435aa41160dd1255c43cb8498279738ccChris Craik if stderr != '': 470cef7893435aa41160dd1255c43cb8498279738ccChris Craik raise OSError('cryptohome-path failed: %s' % stderr) 471cef7893435aa41160dd1255c43cb8498279738ccChris Craik return stdout.rstrip() 472cef7893435aa41160dd1255c43cb8498279738ccChris Craik 473cef7893435aa41160dd1255c43cb8498279738ccChris Craik def IsCryptohomeMounted(self, username, is_guest): 474cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns True iff |user|'s cryptohome is mounted.""" 475cef7893435aa41160dd1255c43cb8498279738ccChris Craik profile_path = self.CryptohomePath(username) 476cef7893435aa41160dd1255c43cb8498279738ccChris Craik mount = self.FilesystemMountedAt(profile_path) 477cef7893435aa41160dd1255c43cb8498279738ccChris Craik mount_prefix = 'guestfs' if is_guest else '/home/.shadow/' 478cef7893435aa41160dd1255c43cb8498279738ccChris Craik return mount and mount.startswith(mount_prefix) 479cef7893435aa41160dd1255c43cb8498279738ccChris Craik 480cef7893435aa41160dd1255c43cb8498279738ccChris Craik def TakeScreenShot(self, screenshot_prefix): 481cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Takes a screenshot, useful for debugging failures.""" 482cef7893435aa41160dd1255c43cb8498279738ccChris Craik # TODO(achuith): Find a better location for screenshots. Cros autotests 483cef7893435aa41160dd1255c43cb8498279738ccChris Craik # upload everything in /var/log so use /var/log/screenshots for now. 484cef7893435aa41160dd1255c43cb8498279738ccChris Craik SCREENSHOT_DIR = '/var/log/screenshots/' 485cef7893435aa41160dd1255c43cb8498279738ccChris Craik SCREENSHOT_EXT = '.png' 486cef7893435aa41160dd1255c43cb8498279738ccChris Craik 487cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['mkdir', '-p', SCREENSHOT_DIR]) 488cef7893435aa41160dd1255c43cb8498279738ccChris Craik # Large number of screenshots can increase hardware lab bandwidth 489cef7893435aa41160dd1255c43cb8498279738ccChris Craik # dramatically, so keep this number low. crbug.com/524814. 490cef7893435aa41160dd1255c43cb8498279738ccChris Craik for i in xrange(2): 491cef7893435aa41160dd1255c43cb8498279738ccChris Craik screenshot_file = ('%s%s-%d%s' % 492cef7893435aa41160dd1255c43cb8498279738ccChris Craik (SCREENSHOT_DIR, screenshot_prefix, i, SCREENSHOT_EXT)) 493cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not self.FileExistsOnDevice(screenshot_file): 494cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice([ 495cef7893435aa41160dd1255c43cb8498279738ccChris Craik '/usr/local/autotest/bin/screenshot.py', screenshot_file 496cef7893435aa41160dd1255c43cb8498279738ccChris Craik ]) 497cef7893435aa41160dd1255c43cb8498279738ccChris Craik return 498cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.warning('screenshot directory full.') 499cef7893435aa41160dd1255c43cb8498279738ccChris Craik 500cef7893435aa41160dd1255c43cb8498279738ccChris Craik def RestartUI(self, clear_enterprise_policy): 501cef7893435aa41160dd1255c43cb8498279738ccChris Craik logging.info('(Re)starting the ui (logs the user out)') 502cef7893435aa41160dd1255c43cb8498279738ccChris Craik if clear_enterprise_policy: 503cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['stop', 'ui']) 504cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RmRF('/var/lib/whitelist/*') 505cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RmRF(r'/home/chronos/Local\ State') 506cef7893435aa41160dd1255c43cb8498279738ccChris Craik 507cef7893435aa41160dd1255c43cb8498279738ccChris Craik if self.IsServiceRunning('ui'): 508cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['restart', 'ui']) 509cef7893435aa41160dd1255c43cb8498279738ccChris Craik else: 510cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RunCmdOnDevice(['start', 'ui']) 511cef7893435aa41160dd1255c43cb8498279738ccChris Craik 512cef7893435aa41160dd1255c43cb8498279738ccChris Craik def CloseConnection(self): 513cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not self.local: 514cef7893435aa41160dd1255c43cb8498279738ccChris Craik with open(os.devnull, 'w') as devnull: 515cef7893435aa41160dd1255c43cb8498279738ccChris Craik subprocess.call( 516cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.FormSSHCommandLine(['-O', 'exit', self._hostname]), 517cef7893435aa41160dd1255c43cb8498279738ccChris Craik stdout=devnull, 518cef7893435aa41160dd1255c43cb8498279738ccChris Craik stderr=devnull) 519