Home | History | Annotate | Download | only in afe
      1 """\
      2 RPC request handler Django.  Exposed RPC interface functions should be
      3 defined in rpc_interface.py.
      4 """
      5 
      6 __author__ = 'showard (at] google.com (Steve Howard)'
      7 
      8 import traceback, pydoc, re, urllib, logging, logging.handlers, inspect
      9 from autotest_lib.frontend.afe.json_rpc import serviceHandler
     10 from autotest_lib.frontend.afe import models, rpc_utils
     11 from autotest_lib.client.common_lib import global_config
     12 from autotest_lib.frontend.afe import rpcserver_logging
     13 
     14 LOGGING_REGEXPS = [r'.*add_.*',
     15                    r'delete_.*',
     16                    r'.*remove_.*',
     17                    r'modify_.*',
     18                    r'create.*',
     19                    r'set_.*']
     20 FULL_REGEXP = '(' + '|'.join(LOGGING_REGEXPS) + ')'
     21 COMPILED_REGEXP = re.compile(FULL_REGEXP)
     22 
     23 
     24 def should_log_message(name):
     25     return COMPILED_REGEXP.match(name)
     26 
     27 
     28 class RpcMethodHolder(object):
     29     'Dummy class to hold RPC interface methods as attributes.'
     30 
     31 
     32 class RpcHandler(object):
     33     def __init__(self, rpc_interface_modules, document_module=None):
     34         self._rpc_methods = RpcMethodHolder()
     35         self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods)
     36 
     37         # store all methods from interface modules
     38         for module in rpc_interface_modules:
     39             self._grab_methods_from(module)
     40 
     41         # get documentation for rpc_interface we can send back to the
     42         # user
     43         if document_module is None:
     44             document_module = rpc_interface_modules[0]
     45         self.html_doc = pydoc.html.document(document_module)
     46 
     47 
     48     def get_rpc_documentation(self):
     49         return rpc_utils.raw_http_response(self.html_doc)
     50 
     51 
     52     def raw_request_data(self, request):
     53         if request.method == 'POST':
     54             return request.raw_post_data
     55         return urllib.unquote(request.META['QUERY_STRING'])
     56 
     57 
     58     def execute_request(self, json_request):
     59         return self._dispatcher.handleRequest(json_request)
     60 
     61 
     62     def decode_request(self, json_request):
     63         return self._dispatcher.translateRequest(json_request)
     64 
     65 
     66     def dispatch_request(self, decoded_request):
     67         return self._dispatcher.dispatchRequest(decoded_request)
     68 
     69 
     70     def log_request(self, user, decoded_request, decoded_result,
     71                     remote_ip, log_all=False):
     72         if log_all or should_log_message(decoded_request['method']):
     73             msg = '%s| %s:%s %s'  % (remote_ip, decoded_request['method'],
     74                                      user, decoded_request['params'])
     75             if decoded_result['err']:
     76                 msg += '\n' + decoded_result['err_traceback']
     77                 rpcserver_logging.rpc_logger.error(msg)
     78             else:
     79                 rpcserver_logging.rpc_logger.info(msg)
     80 
     81 
     82     def encode_result(self, results):
     83         return self._dispatcher.translateResult(results)
     84 
     85 
     86     def handle_rpc_request(self, request):
     87         remote_ip = self._get_remote_ip(request)
     88         user = models.User.current_user()
     89         json_request = self.raw_request_data(request)
     90         decoded_request = self.decode_request(json_request)
     91 
     92         decoded_request['remote_ip'] = remote_ip
     93         decoded_result = self.dispatch_request(decoded_request)
     94         result = self.encode_result(decoded_result)
     95         if rpcserver_logging.LOGGING_ENABLED:
     96             self.log_request(user, decoded_request, decoded_result,
     97                              remote_ip)
     98         return rpc_utils.raw_http_response(result)
     99 
    100 
    101     def handle_jsonp_rpc_request(self, request):
    102         request_data = request.GET['request']
    103         callback_name = request.GET['callback']
    104         # callback_name must be a simple identifier
    105         assert re.search(r'^\w+$', callback_name)
    106 
    107         result = self.execute_request(request_data)
    108         padded_result = '%s(%s)' % (callback_name, result)
    109         return rpc_utils.raw_http_response(padded_result,
    110                                            content_type='text/javascript')
    111 
    112 
    113     @staticmethod
    114     def _allow_keyword_args(f):
    115         """\
    116         Decorator to allow a function to take keyword args even though
    117         the RPC layer doesn't support that.  The decorated function
    118         assumes its last argument is a dictionary of keyword args and
    119         passes them to the original function as keyword args.
    120         """
    121         def new_fn(*args):
    122             assert args
    123             keyword_args = args[-1]
    124             args = args[:-1]
    125             return f(*args, **keyword_args)
    126         new_fn.func_name = f.func_name
    127         return new_fn
    128 
    129 
    130     def _grab_methods_from(self, module):
    131         for name in dir(module):
    132             if name.startswith('_'):
    133                 continue
    134             attribute = getattr(module, name)
    135             if not inspect.isfunction(attribute):
    136                 continue
    137             decorated_function = RpcHandler._allow_keyword_args(attribute)
    138             setattr(self._rpc_methods, name, decorated_function)
    139 
    140 
    141     def _get_remote_ip(self, request):
    142         """Get the ip address of a RPC caller.
    143 
    144         Returns the IP of the request, accounting for the possibility of
    145         being behind a proxy.
    146         If a Django server is behind a proxy, request.META["REMOTE_ADDR"] will
    147         return the proxy server's IP, not the client's IP.
    148         The proxy server would provide the client's IP in the
    149         HTTP_X_FORWARDED_FOR header.
    150 
    151         @param request: django.core.handlers.wsgi.WSGIRequest object.
    152 
    153         @return: IP address of remote host as a string.
    154                  Empty string if the IP cannot be found.
    155         """
    156         remote = request.META.get('HTTP_X_FORWARDED_FOR', None)
    157         if remote:
    158             # X_FORWARDED_FOR returns client1, proxy1, proxy2,...
    159             remote = remote.split(',')[0].strip()
    160         else:
    161             remote = request.META.get('REMOTE_ADDR', '')
    162         return remote
    163