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