| #!/usr/bin/python |
| |
| # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Loads ShopFloorHandler module and wraps it as a FastCGI server. |
| |
| Note that it only accepts requests with path "/shop_floor/<port>/<token>". |
| |
| Example: |
| /path/to/shop_floor_launcher -a 127.0.0.1 -p 8085 -t abcde \ |
| -m cros.factory.umpire.<board>_shop_floor_handler |
| """ |
| |
| import logging |
| import optparse |
| import socket |
| |
| import factory_common # pylint: disable=W0611 |
| from cros.factory.umpire import common |
| from cros.factory.umpire import fastcgi_server |
| from cros.factory.umpire import shop_floor_handler |
| |
| |
| def _LoadShopFloorHandler(module_name): |
| """Loads a ShopFloorHandle module. |
| |
| Args: |
| module_name: Name of module containing a ShopFloorHandler class. |
| |
| Returns: |
| Module reference. |
| """ |
| logging.debug('_LoadShopFloorHandler: %s', module_name) |
| return __import__(module_name, fromlist=['ShopFloorHandler']).ShopFloorHandler |
| |
| |
| def _SetLogging(verbosity, quiet): |
| """Sets logging level and format. |
| |
| Args: |
| verbosity: logging verbosity. 0 for INFO and 1 for DEBUG. |
| quiet: True to disable INFO logging. |
| """ |
| VERBOSITY_MAP = {0: logging.INFO, |
| 1: logging.DEBUG} |
| log_level = VERBOSITY_MAP.get(verbosity or 0, logging.NOTSET) |
| |
| log_format = ['%(asctime)s %(levelname)s'] |
| if verbosity > 0: |
| log_format.append('(%(filename)s:%(lineno)d)') |
| log_format.append('%(message)s') |
| logging.basicConfig(level=log_level, format=' '.join(log_format)) |
| if quiet: |
| logging.disable(logging.INFO) |
| |
| |
| def _DisableDNSLookup(): |
| """Disables all DNS lookups. |
| |
| Since otherwise the logging code may try to resolve IP addresses, which may |
| delay request handling. |
| """ |
| def FakeGetFQDN(name=None): |
| return name or 'localhost' |
| socket.getfqdn = FakeGetFQDN |
| |
| |
| def _ShopFloorHandlerFactory(module): |
| """Creates ShopFloorHandler instance. |
| |
| It exists the program if either ShopFloorHandler module fails to load or |
| the module does not inherit from ShopFloorHandlerBase. |
| |
| Args: |
| module: ShopFloorHandler module name. |
| |
| Returns: |
| A ShopFloorHandler instance. |
| """ |
| try: |
| logging.debug('Loading ShopFloorHandler module: %s', module) |
| instance = _LoadShopFloorHandler(module)() |
| |
| if not isinstance(instance, shop_floor_handler.ShopFloorHandlerBase): |
| logging.critical('Module does not inherit ShopFloorHandlerBase: %s', |
| module) |
| exit(1) |
| except: # pylint: disable=W0702 |
| logging.exception('Failed loading module: %s', module) |
| exit(1) |
| return instance |
| |
| |
| def main(): |
| description = ( |
| 'It wraps the ShopFloorHandler module as an FastCGI service which ' |
| 'handles XMLRPC requests. Note that it only handles POST requests with ' |
| 'path "/shop_floor/<port>/<token>".') |
| |
| parser = optparse.OptionParser(description=description) |
| parser.add_option('-a', '--address', dest='address', metavar='ADDR', |
| default='127.0.0.1', |
| help='address to bind (default: %default)') |
| parser.add_option('-p', '--port', dest='port', metavar='PORT', type='int', |
| help='port to bind') |
| parser.add_option( |
| '-m', '--module', dest='module', metavar='MODULE', |
| help=('ShopFloorHandler module to load, in PACKAGE.MODULE format. ' |
| 'e.g. cros.factory.umpire.<board>_shop_floor_handler')) |
| parser.add_option('-t', '--token', dest='token', metavar='TOKEN', |
| help='a unique token for the handler process') |
| parser.add_option('-v', '--verbose', action='count', dest='verbose', |
| help='increase message verbosity') |
| parser.add_option('-q', '--quiet', action='store_true', dest='quiet', |
| help='turn off verbose messages') |
| |
| # Parse args. |
| (options, args) = parser.parse_args() |
| if args: |
| parser.error('Invalid args: %s' % ' '.join(args)) |
| |
| if not options.module: |
| parser.error('You need to assign the module to be loaded (-m).') |
| |
| _SetLogging(options.verbose, options.quiet) |
| _DisableDNSLookup() |
| instance = _ShopFloorHandlerFactory(options.module) |
| |
| # WSGI environ's SCRIPT_NAME to accept. |
| script_name = '%s/%d/%s' % (common.HANDLER_BASE, options.port, options.token) |
| |
| logging.info( |
| 'Starting FastCGI server (module:%s) on http://%s:%d%s', |
| options.module, options.address, options.port, script_name) |
| # FastCGI server runs forever. |
| fastcgi_server.FastCGIServer( |
| options.address, options.port, instance, script_name=script_name) |
| |
| |
| if __name__ == '__main__': |
| logging.basicConfig(level=logging.DEBUG, |
| format='%(levelname)5s %(message)s') |
| main() |