media/webrtc/trunk/build/android/pylib/chrome_test_server_spawner.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/media/webrtc/trunk/build/android/pylib/chrome_test_server_spawner.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,402 @@
     1.4 +# Copyright (c) 2012 The Chromium Authors. All rights reserved.
     1.5 +# Use of this source code is governed by a BSD-style license that can be
     1.6 +# found in the LICENSE file.
     1.7 +
     1.8 +"""A "Test Server Spawner" that handles killing/stopping per-test test servers.
     1.9 +
    1.10 +It's used to accept requests from the device to spawn and kill instances of the
    1.11 +chrome test server on the host.
    1.12 +"""
    1.13 +
    1.14 +import BaseHTTPServer
    1.15 +import json
    1.16 +import logging
    1.17 +import os
    1.18 +import select
    1.19 +import struct
    1.20 +import subprocess
    1.21 +import threading
    1.22 +import time
    1.23 +import urlparse
    1.24 +
    1.25 +import constants
    1.26 +from forwarder import Forwarder
    1.27 +import ports
    1.28 +
    1.29 +
    1.30 +# Path that are needed to import necessary modules when running testserver.py.
    1.31 +os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + ':%s:%s:%s:%s' % (
    1.32 +    os.path.join(constants.CHROME_DIR, 'third_party'),
    1.33 +    os.path.join(constants.CHROME_DIR, 'third_party', 'tlslite'),
    1.34 +    os.path.join(constants.CHROME_DIR, 'third_party', 'pyftpdlib', 'src'),
    1.35 +    os.path.join(constants.CHROME_DIR, 'net', 'tools', 'testserver'))
    1.36 +
    1.37 +
    1.38 +SERVER_TYPES = {
    1.39 +    'http': '',
    1.40 +    'ftp': '-f',
    1.41 +    'sync': '--sync',
    1.42 +    'tcpecho': '--tcp-echo',
    1.43 +    'udpecho': '--udp-echo',
    1.44 +}
    1.45 +
    1.46 +
    1.47 +# The timeout (in seconds) of starting up the Python test server.
    1.48 +TEST_SERVER_STARTUP_TIMEOUT = 10
    1.49 +
    1.50 +
    1.51 +def _CheckPortStatus(port, expected_status):
    1.52 +  """Returns True if port has expected_status.
    1.53 +
    1.54 +  Args:
    1.55 +    port: the port number.
    1.56 +    expected_status: boolean of expected status.
    1.57 +
    1.58 +  Returns:
    1.59 +    Returns True if the status is expected. Otherwise returns False.
    1.60 +  """
    1.61 +  for timeout in range(1, 5):
    1.62 +    if ports.IsHostPortUsed(port) == expected_status:
    1.63 +      return True
    1.64 +    time.sleep(timeout)
    1.65 +  return False
    1.66 +
    1.67 +
    1.68 +def _GetServerTypeCommandLine(server_type):
    1.69 +  """Returns the command-line by the given server type.
    1.70 +
    1.71 +  Args:
    1.72 +    server_type: the server type to be used (e.g. 'http').
    1.73 +
    1.74 +  Returns:
    1.75 +    A string containing the command-line argument.
    1.76 +  """
    1.77 +  if server_type not in SERVER_TYPES:
    1.78 +    raise NotImplementedError('Unknown server type: %s' % server_type)
    1.79 +  if server_type == 'udpecho':
    1.80 +    raise Exception('Please do not run UDP echo tests because we do not have '
    1.81 +                    'a UDP forwarder tool.')
    1.82 +  return SERVER_TYPES[server_type]
    1.83 +
    1.84 +
    1.85 +class TestServerThread(threading.Thread):
    1.86 +  """A thread to run the test server in a separate process."""
    1.87 +
    1.88 +  def __init__(self, ready_event, arguments, adb, tool, build_type):
    1.89 +    """Initialize TestServerThread with the following argument.
    1.90 +
    1.91 +    Args:
    1.92 +      ready_event: event which will be set when the test server is ready.
    1.93 +      arguments: dictionary of arguments to run the test server.
    1.94 +      adb: instance of AndroidCommands.
    1.95 +      tool: instance of runtime error detection tool.
    1.96 +      build_type: 'Release' or 'Debug'.
    1.97 +    """
    1.98 +    threading.Thread.__init__(self)
    1.99 +    self.wait_event = threading.Event()
   1.100 +    self.stop_flag = False
   1.101 +    self.ready_event = ready_event
   1.102 +    self.ready_event.clear()
   1.103 +    self.arguments = arguments
   1.104 +    self.adb = adb
   1.105 +    self.tool = tool
   1.106 +    self.test_server_process = None
   1.107 +    self.is_ready = False
   1.108 +    self.host_port = self.arguments['port']
   1.109 +    assert isinstance(self.host_port, int)
   1.110 +    self._test_server_forwarder = None
   1.111 +    # The forwarder device port now is dynamically allocated.
   1.112 +    self.forwarder_device_port = 0
   1.113 +    # Anonymous pipe in order to get port info from test server.
   1.114 +    self.pipe_in = None
   1.115 +    self.pipe_out = None
   1.116 +    self.command_line = []
   1.117 +    self.build_type = build_type
   1.118 +
   1.119 +  def _WaitToStartAndGetPortFromTestServer(self):
   1.120 +    """Waits for the Python test server to start and gets the port it is using.
   1.121 +
   1.122 +    The port information is passed by the Python test server with a pipe given
   1.123 +    by self.pipe_out. It is written as a result to |self.host_port|.
   1.124 +
   1.125 +    Returns:
   1.126 +      Whether the port used by the test server was successfully fetched.
   1.127 +    """
   1.128 +    assert self.host_port == 0 and self.pipe_out and self.pipe_in
   1.129 +    (in_fds, _, _) = select.select([self.pipe_in, ], [], [],
   1.130 +                                   TEST_SERVER_STARTUP_TIMEOUT)
   1.131 +    if len(in_fds) == 0:
   1.132 +      logging.error('Failed to wait to the Python test server to be started.')
   1.133 +      return False
   1.134 +    # First read the data length as an unsigned 4-byte value.  This
   1.135 +    # is _not_ using network byte ordering since the Python test server packs
   1.136 +    # size as native byte order and all Chromium platforms so far are
   1.137 +    # configured to use little-endian.
   1.138 +    # TODO(jnd): Change the Python test server and local_test_server_*.cc to
   1.139 +    # use a unified byte order (either big-endian or little-endian).
   1.140 +    data_length = os.read(self.pipe_in, struct.calcsize('=L'))
   1.141 +    if data_length:
   1.142 +      (data_length,) = struct.unpack('=L', data_length)
   1.143 +      assert data_length
   1.144 +    if not data_length:
   1.145 +      logging.error('Failed to get length of server data.')
   1.146 +      return False
   1.147 +    port_json = os.read(self.pipe_in, data_length)
   1.148 +    if not port_json:
   1.149 +      logging.error('Failed to get server data.')
   1.150 +      return False
   1.151 +    logging.info('Got port json data: %s', port_json)
   1.152 +    port_json = json.loads(port_json)
   1.153 +    if port_json.has_key('port') and isinstance(port_json['port'], int):
   1.154 +      self.host_port = port_json['port']
   1.155 +      return _CheckPortStatus(self.host_port, True)
   1.156 +    logging.error('Failed to get port information from the server data.')
   1.157 +    return False
   1.158 +
   1.159 +  def _GenerateCommandLineArguments(self):
   1.160 +    """Generates the command line to run the test server.
   1.161 +
   1.162 +    Note that all options are processed by following the definitions in
   1.163 +    testserver.py.
   1.164 +    """
   1.165 +    if self.command_line:
   1.166 +      return
   1.167 +    # The following arguments must exist.
   1.168 +    type_cmd = _GetServerTypeCommandLine(self.arguments['server-type'])
   1.169 +    if type_cmd:
   1.170 +      self.command_line.append(type_cmd)
   1.171 +    self.command_line.append('--port=%d' % self.host_port)
   1.172 +    # Use a pipe to get the port given by the instance of Python test server
   1.173 +    # if the test does not specify the port.
   1.174 +    if self.host_port == 0:
   1.175 +      (self.pipe_in, self.pipe_out) = os.pipe()
   1.176 +      self.command_line.append('--startup-pipe=%d' % self.pipe_out)
   1.177 +    self.command_line.append('--host=%s' % self.arguments['host'])
   1.178 +    data_dir = self.arguments['data-dir'] or 'chrome/test/data'
   1.179 +    if not os.path.isabs(data_dir):
   1.180 +      data_dir = os.path.join(constants.CHROME_DIR, data_dir)
   1.181 +    self.command_line.append('--data-dir=%s' % data_dir)
   1.182 +    # The following arguments are optional depending on the individual test.
   1.183 +    if self.arguments.has_key('log-to-console'):
   1.184 +      self.command_line.append('--log-to-console')
   1.185 +    if self.arguments.has_key('auth-token'):
   1.186 +      self.command_line.append('--auth-token=%s' % self.arguments['auth-token'])
   1.187 +    if self.arguments.has_key('https'):
   1.188 +      self.command_line.append('--https')
   1.189 +      if self.arguments.has_key('cert-and-key-file'):
   1.190 +        self.command_line.append('--cert-and-key-file=%s' % os.path.join(
   1.191 +            constants.CHROME_DIR, self.arguments['cert-and-key-file']))
   1.192 +      if self.arguments.has_key('ocsp'):
   1.193 +        self.command_line.append('--ocsp=%s' % self.arguments['ocsp'])
   1.194 +      if self.arguments.has_key('https-record-resume'):
   1.195 +        self.command_line.append('--https-record-resume')
   1.196 +      if self.arguments.has_key('ssl-client-auth'):
   1.197 +        self.command_line.append('--ssl-client-auth')
   1.198 +      if self.arguments.has_key('tls-intolerant'):
   1.199 +        self.command_line.append('--tls-intolerant=%s' %
   1.200 +                                 self.arguments['tls-intolerant'])
   1.201 +      if self.arguments.has_key('ssl-client-ca'):
   1.202 +        for ca in self.arguments['ssl-client-ca']:
   1.203 +          self.command_line.append('--ssl-client-ca=%s' %
   1.204 +                                   os.path.join(constants.CHROME_DIR, ca))
   1.205 +      if self.arguments.has_key('ssl-bulk-cipher'):
   1.206 +        for bulk_cipher in self.arguments['ssl-bulk-cipher']:
   1.207 +          self.command_line.append('--ssl-bulk-cipher=%s' % bulk_cipher)
   1.208 +
   1.209 +  def run(self):
   1.210 +    logging.info('Start running the thread!')
   1.211 +    self.wait_event.clear()
   1.212 +    self._GenerateCommandLineArguments()
   1.213 +    command = [os.path.join(constants.CHROME_DIR, 'net', 'tools',
   1.214 +                            'testserver', 'testserver.py')] + self.command_line
   1.215 +    logging.info('Running: %s', command)
   1.216 +    self.process = subprocess.Popen(command)
   1.217 +    if self.process:
   1.218 +      if self.pipe_out:
   1.219 +        self.is_ready = self._WaitToStartAndGetPortFromTestServer()
   1.220 +      else:
   1.221 +        self.is_ready = _CheckPortStatus(self.host_port, True)
   1.222 +    if self.is_ready:
   1.223 +      self._test_server_forwarder = Forwarder(
   1.224 +          self.adb, [(0, self.host_port)], self.tool, '127.0.0.1',
   1.225 +          self.build_type)
   1.226 +      # Check whether the forwarder is ready on the device.
   1.227 +      self.is_ready = False
   1.228 +      device_port = self._test_server_forwarder.DevicePortForHostPort(
   1.229 +          self.host_port)
   1.230 +      if device_port:
   1.231 +        for timeout in range(1, 5):
   1.232 +          if ports.IsDevicePortUsed(self.adb, device_port, 'LISTEN'):
   1.233 +            self.is_ready = True
   1.234 +            self.forwarder_device_port = device_port
   1.235 +            break
   1.236 +          time.sleep(timeout)
   1.237 +    # Wake up the request handler thread.
   1.238 +    self.ready_event.set()
   1.239 +    # Keep thread running until Stop() gets called.
   1.240 +    while not self.stop_flag:
   1.241 +      time.sleep(1)
   1.242 +    if self.process.poll() is None:
   1.243 +      self.process.kill()
   1.244 +    if self._test_server_forwarder:
   1.245 +      self._test_server_forwarder.Close()
   1.246 +    self.process = None
   1.247 +    self.is_ready = False
   1.248 +    if self.pipe_out:
   1.249 +      os.close(self.pipe_in)
   1.250 +      os.close(self.pipe_out)
   1.251 +      self.pipe_in = None
   1.252 +      self.pipe_out = None
   1.253 +    logging.info('Test-server has died.')
   1.254 +    self.wait_event.set()
   1.255 +
   1.256 +  def Stop(self):
   1.257 +    """Blocks until the loop has finished.
   1.258 +
   1.259 +    Note that this must be called in another thread.
   1.260 +    """
   1.261 +    if not self.process:
   1.262 +      return
   1.263 +    self.stop_flag = True
   1.264 +    self.wait_event.wait()
   1.265 +
   1.266 +
   1.267 +class SpawningServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
   1.268 +  """A handler used to process http GET/POST request."""
   1.269 +
   1.270 +  def _SendResponse(self, response_code, response_reason, additional_headers,
   1.271 +                    contents):
   1.272 +    """Generates a response sent to the client from the provided parameters.
   1.273 +
   1.274 +    Args:
   1.275 +      response_code: number of the response status.
   1.276 +      response_reason: string of reason description of the response.
   1.277 +      additional_headers: dict of additional headers. Each key is the name of
   1.278 +                          the header, each value is the content of the header.
   1.279 +      contents: string of the contents we want to send to client.
   1.280 +    """
   1.281 +    self.send_response(response_code, response_reason)
   1.282 +    self.send_header('Content-Type', 'text/html')
   1.283 +    # Specify the content-length as without it the http(s) response will not
   1.284 +    # be completed properly (and the browser keeps expecting data).
   1.285 +    self.send_header('Content-Length', len(contents))
   1.286 +    for header_name in additional_headers:
   1.287 +      self.send_header(header_name, additional_headers[header_name])
   1.288 +    self.end_headers()
   1.289 +    self.wfile.write(contents)
   1.290 +    self.wfile.flush()
   1.291 +
   1.292 +  def _StartTestServer(self):
   1.293 +    """Starts the test server thread."""
   1.294 +    logging.info('Handling request to spawn a test server.')
   1.295 +    content_type = self.headers.getheader('content-type')
   1.296 +    if content_type != 'application/json':
   1.297 +      raise Exception('Bad content-type for start request.')
   1.298 +    content_length = self.headers.getheader('content-length')
   1.299 +    if not content_length:
   1.300 +      content_length = 0
   1.301 +    try:
   1.302 +      content_length = int(content_length)
   1.303 +    except:
   1.304 +      raise Exception('Bad content-length for start request.')
   1.305 +    logging.info(content_length)
   1.306 +    test_server_argument_json = self.rfile.read(content_length)
   1.307 +    logging.info(test_server_argument_json)
   1.308 +    assert not self.server.test_server_instance
   1.309 +    ready_event = threading.Event()
   1.310 +    self.server.test_server_instance = TestServerThread(
   1.311 +        ready_event,
   1.312 +        json.loads(test_server_argument_json),
   1.313 +        self.server.adb,
   1.314 +        self.server.tool,
   1.315 +        self.server.build_type)
   1.316 +    self.server.test_server_instance.setDaemon(True)
   1.317 +    self.server.test_server_instance.start()
   1.318 +    ready_event.wait()
   1.319 +    if self.server.test_server_instance.is_ready:
   1.320 +      self._SendResponse(200, 'OK', {}, json.dumps(
   1.321 +          {'port': self.server.test_server_instance.forwarder_device_port,
   1.322 +           'message': 'started'}))
   1.323 +      logging.info('Test server is running on port: %d.',
   1.324 +                   self.server.test_server_instance.host_port)
   1.325 +    else:
   1.326 +      self.server.test_server_instance.Stop()
   1.327 +      self.server.test_server_instance = None
   1.328 +      self._SendResponse(500, 'Test Server Error.', {}, '')
   1.329 +      logging.info('Encounter problem during starting a test server.')
   1.330 +
   1.331 +  def _KillTestServer(self):
   1.332 +    """Stops the test server instance."""
   1.333 +    # There should only ever be one test server at a time. This may do the
   1.334 +    # wrong thing if we try and start multiple test servers.
   1.335 +    if not self.server.test_server_instance:
   1.336 +      return
   1.337 +    port = self.server.test_server_instance.host_port
   1.338 +    logging.info('Handling request to kill a test server on port: %d.', port)
   1.339 +    self.server.test_server_instance.Stop()
   1.340 +    # Make sure the status of test server is correct before sending response.
   1.341 +    if _CheckPortStatus(port, False):
   1.342 +      self._SendResponse(200, 'OK', {}, 'killed')
   1.343 +      logging.info('Test server on port %d is killed', port)
   1.344 +    else:
   1.345 +      self._SendResponse(500, 'Test Server Error.', {}, '')
   1.346 +      logging.info('Encounter problem during killing a test server.')
   1.347 +    self.server.test_server_instance = None
   1.348 +
   1.349 +  def do_POST(self):
   1.350 +    parsed_path = urlparse.urlparse(self.path)
   1.351 +    action = parsed_path.path
   1.352 +    logging.info('Action for POST method is: %s.', action)
   1.353 +    if action == '/start':
   1.354 +      self._StartTestServer()
   1.355 +    else:
   1.356 +      self._SendResponse(400, 'Unknown request.', {}, '')
   1.357 +      logging.info('Encounter unknown request: %s.', action)
   1.358 +
   1.359 +  def do_GET(self):
   1.360 +    parsed_path = urlparse.urlparse(self.path)
   1.361 +    action = parsed_path.path
   1.362 +    params = urlparse.parse_qs(parsed_path.query, keep_blank_values=1)
   1.363 +    logging.info('Action for GET method is: %s.', action)
   1.364 +    for param in params:
   1.365 +      logging.info('%s=%s', param, params[param][0])
   1.366 +    if action == '/kill':
   1.367 +      self._KillTestServer()
   1.368 +    elif action == '/ping':
   1.369 +      # The ping handler is used to check whether the spawner server is ready
   1.370 +      # to serve the requests. We don't need to test the status of the test
   1.371 +      # server when handling ping request.
   1.372 +      self._SendResponse(200, 'OK', {}, 'ready')
   1.373 +      logging.info('Handled ping request and sent response.')
   1.374 +    else:
   1.375 +      self._SendResponse(400, 'Unknown request', {}, '')
   1.376 +      logging.info('Encounter unknown request: %s.', action)
   1.377 +
   1.378 +
   1.379 +class SpawningServer(object):
   1.380 +  """The class used to start/stop a http server."""
   1.381 +
   1.382 +  def __init__(self, test_server_spawner_port, adb, tool, build_type):
   1.383 +    logging.info('Creating new spawner on port: %d.', test_server_spawner_port)
   1.384 +    self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port),
   1.385 +                                            SpawningServerRequestHandler)
   1.386 +    self.port = test_server_spawner_port
   1.387 +    self.server.adb = adb
   1.388 +    self.server.tool = tool
   1.389 +    self.server.test_server_instance = None
   1.390 +    self.server.build_type = build_type
   1.391 +
   1.392 +  def _Listen(self):
   1.393 +    logging.info('Starting test server spawner')
   1.394 +    self.server.serve_forever()
   1.395 +
   1.396 +  def Start(self):
   1.397 +    listener_thread = threading.Thread(target=self._Listen)
   1.398 +    listener_thread.setDaemon(True)
   1.399 +    listener_thread.start()
   1.400 +    time.sleep(1)
   1.401 +
   1.402 +  def Stop(self):
   1.403 +    if self.server.test_server_instance:
   1.404 +      self.server.test_server_instance.Stop()
   1.405 +    self.server.shutdown()

mercurial