| Index: tools/telemetry/telemetry/cros_interface.py
|
| diff --git a/tools/telemetry/telemetry/cros_interface.py b/tools/telemetry/telemetry/cros_interface.py
|
| deleted file mode 100644
|
| index 7eb862d7d6a93c416213915210207299edbaae8f..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/telemetry/cros_interface.py
|
| +++ /dev/null
|
| @@ -1,410 +0,0 @@
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -"""A wrapper around ssh for common operations on a CrOS-based device"""
|
| -import logging
|
| -import os
|
| -import re
|
| -import subprocess
|
| -import sys
|
| -import time
|
| -import tempfile
|
| -
|
| -from telemetry import util
|
| -
|
| -# TODO(nduca): This whole file is built up around making individual ssh calls
|
| -# for each operation. It really could get away with a single ssh session built
|
| -# around pexpect, I suspect, if we wanted it to be faster. But, this was
|
| -# convenient.
|
| -
|
| -def RunCmd(args, cwd=None, quiet=False):
|
| - """Opens a subprocess to execute a program and returns its return value.
|
| -
|
| - Args:
|
| - args: A string or a sequence of program arguments. The program to execute is
|
| - the string or the first item in the args sequence.
|
| - cwd: If not None, the subprocess's current directory will be changed to
|
| - |cwd| before it's executed.
|
| -
|
| - Returns:
|
| - Return code from the command execution.
|
| - """
|
| - if not quiet:
|
| - logging.debug(' '.join(args) + ' ' + (cwd or ''))
|
| - with open(os.devnull, 'w') as devnull:
|
| - p = subprocess.Popen(args=args, cwd=cwd, stdout=devnull,
|
| - stderr=devnull, stdin=devnull, shell=False)
|
| - return p.wait()
|
| -
|
| -def GetAllCmdOutput(args, cwd=None, quiet=False):
|
| - """Open a subprocess to execute a program and returns its output.
|
| -
|
| - Args:
|
| - args: A string or a sequence of program arguments. The program to execute is
|
| - the string or the first item in the args sequence.
|
| - cwd: If not None, the subprocess's current directory will be changed to
|
| - |cwd| before it's executed.
|
| -
|
| - Returns:
|
| - Captures and returns the command's stdout.
|
| - Prints the command's stderr to logger (which defaults to stdout).
|
| - """
|
| - if not quiet:
|
| - logging.debug(' '.join(args) + ' ' + (cwd or ''))
|
| - with open(os.devnull, 'w') as devnull:
|
| - p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE,
|
| - stderr=subprocess.PIPE, stdin=devnull, shell=False)
|
| - stdout, stderr = p.communicate()
|
| - if not quiet:
|
| - logging.debug(' > stdout=[%s], stderr=[%s]', stdout, stderr)
|
| - return stdout, stderr
|
| -
|
| -class DeviceSideProcess(object):
|
| - def __init__(self,
|
| - cri,
|
| - device_side_args,
|
| - prevent_output=True,
|
| - extra_ssh_args=None,
|
| - leave_ssh_alive=False,
|
| - env=None,
|
| - login_shell=False):
|
| -
|
| - # Init members first so that Close will always succeed.
|
| - self._cri = cri
|
| - self._proc = None
|
| - self._devnull = open(os.devnull, 'w')
|
| -
|
| - if prevent_output:
|
| - out = self._devnull
|
| - else:
|
| - out = sys.stderr
|
| -
|
| - cri.RmRF('/tmp/cros_interface_remote_device_pid')
|
| - cmd_str = ' '.join(device_side_args)
|
| - if env:
|
| - env_str = ' '.join(['%s=%s' % (k, v) for k, v in env.items()])
|
| - cmd = env_str + ' ' + cmd_str
|
| - else:
|
| - cmd = cmd_str
|
| - contents = """%s&\n""" % cmd
|
| - contents += 'echo $! > /tmp/cros_interface_remote_device_pid\n'
|
| - cri.PushContents(contents, '/tmp/cros_interface_remote_device_bootstrap.sh')
|
| -
|
| - cmdline = ['/bin/bash']
|
| - if login_shell:
|
| - cmdline.append('-l')
|
| - cmdline.append('/tmp/cros_interface_remote_device_bootstrap.sh')
|
| - proc = subprocess.Popen(
|
| - cri.FormSSHCommandLine(cmdline,
|
| - extra_ssh_args=extra_ssh_args),
|
| - stdout=out,
|
| - stderr=out,
|
| - stdin=self._devnull,
|
| - shell=False)
|
| -
|
| - time.sleep(0.1)
|
| - def TryGetResult():
|
| - try:
|
| - self._pid = cri.GetFileContents(
|
| - '/tmp/cros_interface_remote_device_pid').strip()
|
| - return True
|
| - except OSError:
|
| - return False
|
| - try:
|
| - util.WaitFor(TryGetResult, 5)
|
| - except util.TimeoutException:
|
| - raise Exception('Something horrible has happened!')
|
| -
|
| - # Killing the ssh session leaves the process running. We dont
|
| - # need it anymore, unless we have port-forwards.
|
| - if not leave_ssh_alive:
|
| - proc.kill()
|
| - else:
|
| - self._proc = proc
|
| -
|
| - self._pid = int(self._pid)
|
| - if not self.IsAlive():
|
| - raise OSError('Process did not come up or did not stay alive very long!')
|
| - self._cri = cri
|
| -
|
| - def Close(self, try_sigint_first=False):
|
| - if self.IsAlive():
|
| - # Try to politely shutdown, first.
|
| - if try_sigint_first:
|
| - logging.debug("kill -INT %i" % self._pid)
|
| - self._cri.GetAllCmdOutput(
|
| - ['kill', '-INT', str(self._pid)], quiet=True)
|
| - try:
|
| - self.Wait(timeout=0.5)
|
| - except util.TimeoutException:
|
| - pass
|
| -
|
| - if self.IsAlive():
|
| - logging.debug("kill -KILL %i" % self._pid)
|
| - self._cri.GetAllCmdOutput(
|
| - ['kill', '-KILL', str(self._pid)], quiet=True)
|
| - try:
|
| - self.Wait(timeout=5)
|
| - except util.TimeoutException:
|
| - pass
|
| -
|
| - if self.IsAlive():
|
| - raise Exception('Could not shutdown the process.')
|
| -
|
| - self._cri = None
|
| - if self._proc:
|
| - self._proc.kill()
|
| - self._proc = None
|
| -
|
| - if self._devnull:
|
| - self._devnull.close()
|
| - self._devnull = None
|
| -
|
| - def __enter__(self):
|
| - return self
|
| -
|
| - def __exit__(self, *args):
|
| - self.Close()
|
| - return
|
| -
|
| - def Wait(self, timeout=1):
|
| - if not self._pid:
|
| - raise Exception('Closed')
|
| - def IsDone():
|
| - return not self.IsAlive()
|
| - util.WaitFor(IsDone, timeout)
|
| - self._pid = None
|
| -
|
| - def IsAlive(self, quiet=True):
|
| - if not self._pid:
|
| - return False
|
| - exists = self._cri.FileExistsOnDevice('/proc/%i/cmdline' % self._pid,
|
| - quiet=quiet)
|
| - return exists
|
| -
|
| -def HasSSH():
|
| - try:
|
| - RunCmd(['ssh'], quiet=True)
|
| - RunCmd(['scp'], quiet=True)
|
| - logging.debug("HasSSH()->True")
|
| - return True
|
| - except OSError:
|
| - logging.debug("HasSSH()->False")
|
| - return False
|
| -
|
| -class LoginException(Exception):
|
| - pass
|
| -
|
| -class KeylessLoginRequiredException(LoginException):
|
| - pass
|
| -
|
| -class CrOSInterface(object):
|
| - # pylint: disable=R0923
|
| - def __init__(self, hostname, ssh_identity = None):
|
| - self._hostname = hostname
|
| - self._ssh_identity = None
|
| - self._hostfile = tempfile.NamedTemporaryFile()
|
| - self._hostfile.flush()
|
| - self._ssh_args = ['-o ConnectTimeout=5',
|
| - '-o StrictHostKeyChecking=no',
|
| - '-o KbdInteractiveAuthentication=no',
|
| - '-o PreferredAuthentications=publickey',
|
| - '-o UserKnownHostsFile=%s' % self._hostfile.name]
|
| -
|
| - # List of ports generated from GetRemotePort() that may not be in use yet.
|
| - self._reserved_ports = []
|
| -
|
| - if ssh_identity:
|
| - self._ssh_identity = os.path.abspath(os.path.expanduser(ssh_identity))
|
| -
|
| - @property
|
| - def hostname(self):
|
| - return self._hostname
|
| -
|
| - def FormSSHCommandLine(self, args, extra_ssh_args=None):
|
| - full_args = ['ssh',
|
| - '-o ForwardX11=no',
|
| - '-o ForwardX11Trusted=no',
|
| - '-n'] + self._ssh_args
|
| - if self._ssh_identity is not None:
|
| - full_args.extend(['-i', self._ssh_identity])
|
| - if extra_ssh_args:
|
| - full_args.extend(extra_ssh_args)
|
| - full_args.append('root@%s' % self._hostname)
|
| - full_args.extend(args)
|
| - return full_args
|
| -
|
| - def GetAllCmdOutput(self, args, cwd=None, quiet=False):
|
| - return GetAllCmdOutput(self.FormSSHCommandLine(args), cwd, quiet=quiet)
|
| -
|
| - def _RemoveSSHWarnings(self, toClean):
|
| - """Removes specific ssh warning lines from a string.
|
| -
|
| - Args:
|
| - toClean: A string that may be containing multiple lines.
|
| -
|
| - Returns:
|
| - A copy of toClean with all the Warning lines removed.
|
| - """
|
| - # Remove the Warning about connecting to a new host for the first time.
|
| - return re.sub('Warning: Permanently added [^\n]* to the list of known '
|
| - 'hosts.\s\n', '', toClean)
|
| -
|
| - def TryLogin(self):
|
| - logging.debug('TryLogin()')
|
| - stdout, stderr = self.GetAllCmdOutput(['echo', '$USER'], quiet=True)
|
| -
|
| - # The initial login will add the host to the hosts file but will also print
|
| - # a warning to stderr that we need to remove.
|
| - stderr = self._RemoveSSHWarnings(stderr)
|
| - if stderr != '':
|
| - if 'Host key verification failed' in stderr:
|
| - raise LoginException(('%s host key verification failed. ' +
|
| - 'SSH to it manually to fix connectivity.') %
|
| - self._hostname)
|
| - if 'Operation timed out' in stderr:
|
| - raise LoginException('Timed out while logging into %s' % self._hostname)
|
| - if 'UNPROTECTED PRIVATE KEY FILE!' in stderr:
|
| - raise LoginException('Permissions for %s are too open. To fix this,\n'
|
| - 'chmod 600 %s' % (self._ssh_identity,
|
| - self._ssh_identity))
|
| - if 'Permission denied (publickey,keyboard-interactive)' in stderr:
|
| - raise KeylessLoginRequiredException(
|
| - 'Need to set up ssh auth for %s' % self._hostname)
|
| - raise LoginException('While logging into %s, got %s' % (
|
| - self._hostname, stderr))
|
| - if stdout != 'root\n':
|
| - raise LoginException(
|
| - 'Logged into %s, expected $USER=root, but got %s.' % (
|
| - self._hostname, stdout))
|
| -
|
| - def FileExistsOnDevice(self, file_name, quiet=False):
|
| - stdout, stderr = self.GetAllCmdOutput([
|
| - 'if', 'test', '-a', file_name, ';',
|
| - 'then', 'echo', '1', ';',
|
| - 'fi'
|
| - ], quiet=True)
|
| - if stderr != '':
|
| - if "Connection timed out" in stderr:
|
| - raise OSError('Machine wasn\'t responding to ssh: %s' %
|
| - stderr)
|
| - raise OSError('Unepected error: %s' % stderr)
|
| - exists = stdout == '1\n'
|
| - if not quiet:
|
| - logging.debug("FileExistsOnDevice(<text>, %s)->%s" % (
|
| - file_name, exists))
|
| - return exists
|
| -
|
| - def PushFile(self, filename, remote_filename):
|
| - args = ['scp', '-r' ] + self._ssh_args
|
| - if self._ssh_identity:
|
| - args.extend(['-i', self._ssh_identity])
|
| -
|
| - args.extend([os.path.abspath(filename),
|
| - 'root@%s:%s' % (self._hostname, remote_filename)])
|
| -
|
| - stdout, stderr = GetAllCmdOutput(args, quiet=True)
|
| - if stderr != '':
|
| - assert 'No such file or directory' in stderr
|
| - raise OSError
|
| -
|
| - def PushContents(self, text, remote_filename):
|
| - logging.debug("PushContents(<text>, %s)" % remote_filename)
|
| - with tempfile.NamedTemporaryFile() as f:
|
| - f.write(text)
|
| - f.flush()
|
| - self.PushFile(f.name, remote_filename)
|
| -
|
| - def GetFileContents(self, filename):
|
| - with tempfile.NamedTemporaryFile() as f:
|
| - args = ['scp'] + self._ssh_args
|
| - if self._ssh_identity:
|
| - args.extend(['-i', self._ssh_identity])
|
| -
|
| - args.extend(['root@%s:%s' % (self._hostname, filename),
|
| - os.path.abspath(f.name)])
|
| -
|
| - stdout, stderr = GetAllCmdOutput(args, quiet=True)
|
| -
|
| - if stderr != '':
|
| - assert 'No such file or directory' in stderr
|
| - raise OSError
|
| -
|
| - with open(f.name, 'r') as f2:
|
| - res = f2.read()
|
| - logging.debug("GetFileContents(%s)->%s" % (filename, res))
|
| - return res
|
| -
|
| - def ListProcesses(self):
|
| - stdout, stderr = self.GetAllCmdOutput([
|
| - '/bin/ps', '--no-headers',
|
| - '-A',
|
| - '-o', 'pid,args'], quiet=True)
|
| - assert stderr == ''
|
| - procs = []
|
| - for l in stdout.split('\n'): # pylint: disable=E1103
|
| - if l == '':
|
| - continue
|
| - m = re.match('^\s*(\d+)\s+(.+)', l, re.DOTALL)
|
| - assert m
|
| - procs.append(m.groups())
|
| - logging.debug("ListProcesses(<predicate>)->[%i processes]" % len(procs))
|
| - return procs
|
| -
|
| - def RmRF(self, filename):
|
| - logging.debug("rm -rf %s" % filename)
|
| - self.GetCmdOutput(['rm', '-rf', filename], quiet=True)
|
| -
|
| - def KillAllMatching(self, predicate):
|
| - kills = ['kill', '-KILL']
|
| - for p in self.ListProcesses():
|
| - if predicate(p[1]):
|
| - logging.info('Killing %s', repr(p))
|
| - kills.append(p[0])
|
| - logging.debug("KillAllMatching(<predicate>)->%i" % (len(kills) - 2))
|
| - if len(kills) > 2:
|
| - self.GetCmdOutput(kills, quiet=True)
|
| - return len(kills) - 2
|
| -
|
| - def IsServiceRunning(self, service_name):
|
| - stdout, stderr = self.GetAllCmdOutput([
|
| - 'status', service_name], quiet=True)
|
| - assert stderr == ''
|
| - running = 'running, process' in stdout
|
| - logging.debug("IsServiceRunning(%s)->%s" % (service_name, running))
|
| - return running
|
| -
|
| - def GetCmdOutput(self, args, quiet=False):
|
| - stdout, stderr = self.GetAllCmdOutput(args, quiet=True)
|
| - assert stderr == ''
|
| - if not quiet:
|
| - logging.debug("GetCmdOutput(%s)->%s" % (repr(args), stdout))
|
| - return stdout
|
| -
|
| - def GetRemotePort(self):
|
| - netstat = self.GetAllCmdOutput(['netstat', '-ant'])
|
| - netstat = netstat[0].split('\n')
|
| - ports_in_use = []
|
| -
|
| - for line in netstat[2:]:
|
| - if not line:
|
| - continue
|
| - address_in_use = line.split()[3]
|
| - port_in_use = address_in_use.split(':')[-1]
|
| - ports_in_use.append(int(port_in_use))
|
| -
|
| - ports_in_use.extend(self._reserved_ports)
|
| -
|
| - new_port = sorted(ports_in_use)[-1] + 1
|
| - self._reserved_ports.append(new_port)
|
| -
|
| - return new_port
|
| -
|
| - def IsHTTPServerRunningOnPort(self, port):
|
| - wget_output = self.GetAllCmdOutput(
|
| - ['wget', 'localhost:%i' % (port), '-T1', '-t1'])
|
| -
|
| - if 'Connection refused' in wget_output[1]:
|
| - return False
|
| -
|
| - return True
|
|
|