blob: ab3ed386e175db3ed11e8d0ac517966691679362 [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001"""\
2RPC request handler Django. Exposed RPC interface functions should be
3defined in rpc_interface.py.
4"""
5
6__author__ = 'showard@google.com (Steve Howard)'
7
showarda849ceb2010-01-20 01:12:42 +00008import traceback, pydoc, re, urllib, logging, logging.handlers, inspect
showarda5288b42009-07-28 20:06:08 +00009from autotest_lib.frontend.afe.json_rpc import serviceHandler
showard64a95952010-01-13 21:27:16 +000010from autotest_lib.frontend.afe import models, rpc_utils
showard02ed4bd2009-09-09 15:30:11 +000011from autotest_lib.client.common_lib import global_config
12from autotest_lib.frontend.afe import rpcserver_logging
13
showard02ed4bd2009-09-09 15:30:11 +000014LOGGING_REGEXPS = [r'.*add_.*',
15 r'delete_.*',
MK Ryu3e1de8b2015-05-27 16:47:10 -070016 r'.*remove_.*',
showard02ed4bd2009-09-09 15:30:11 +000017 r'modify_.*',
MK Ryu3e1de8b2015-05-27 16:47:10 -070018 r'create.*',
19 r'set_.*']
showard02ed4bd2009-09-09 15:30:11 +000020FULL_REGEXP = '(' + '|'.join(LOGGING_REGEXPS) + ')'
21COMPILED_REGEXP = re.compile(FULL_REGEXP)
22
23
24def should_log_message(name):
25 return COMPILED_REGEXP.match(name)
mblighe8819cd2008-02-15 16:48:40 +000026
mblighe8819cd2008-02-15 16:48:40 +000027
28class RpcMethodHolder(object):
jadmanski0afbb632008-06-06 21:10:57 +000029 'Dummy class to hold RPC interface methods as attributes.'
mblighe8819cd2008-02-15 16:48:40 +000030
31
showard7c785282008-05-29 19:45:12 +000032class RpcHandler(object):
jadmanski0afbb632008-06-06 21:10:57 +000033 def __init__(self, rpc_interface_modules, document_module=None):
34 self._rpc_methods = RpcMethodHolder()
showardef6fe022009-03-27 20:55:16 +000035 self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods)
mblighe8819cd2008-02-15 16:48:40 +000036
jadmanski0afbb632008-06-06 21:10:57 +000037 # store all methods from interface modules
38 for module in rpc_interface_modules:
39 self._grab_methods_from(module)
showard7c785282008-05-29 19:45:12 +000040
jadmanski0afbb632008-06-06 21:10:57 +000041 # 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)
showard7c785282008-05-29 19:45:12 +000046
47
showardef6fe022009-03-27 20:55:16 +000048 def get_rpc_documentation(self):
showard2d7ac832009-06-22 18:14:10 +000049 return rpc_utils.raw_http_response(self.html_doc)
showardef6fe022009-03-27 20:55:16 +000050
51
showard3d6ae112009-05-02 00:45:48 +000052 def raw_request_data(self, request):
showardef6fe022009-03-27 20:55:16 +000053 if request.method == 'POST':
54 return request.raw_post_data
55 return urllib.unquote(request.META['QUERY_STRING'])
56
57
showard3d6ae112009-05-02 00:45:48 +000058 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
showard02ed4bd2009-09-09 15:30:11 +000070 def log_request(self, user, decoded_request, decoded_result,
MK Ryue782e852015-06-29 18:10:36 -070071 remote_ip, log_all=False):
showard02ed4bd2009-09-09 15:30:11 +000072 if log_all or should_log_message(decoded_request['method']):
MK Ryue782e852015-06-29 18:10:36 -070073 msg = '%s| %s:%s %s' % (remote_ip, decoded_request['method'],
74 user, decoded_request['params'])
showard02ed4bd2009-09-09 15:30:11 +000075 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
showardef6fe022009-03-27 20:55:16 +000086 def handle_rpc_request(self, request):
MK Ryue782e852015-06-29 18:10:36 -070087 remote_ip = self._get_remote_ip(request)
showard64a95952010-01-13 21:27:16 +000088 user = models.User.current_user()
showard3d6ae112009-05-02 00:45:48 +000089 json_request = self.raw_request_data(request)
showard02ed4bd2009-09-09 15:30:11 +000090 decoded_request = self.decode_request(json_request)
MK Ryue782e852015-06-29 18:10:36 -070091
92 decoded_request['remote_ip'] = remote_ip
showard02ed4bd2009-09-09 15:30:11 +000093 decoded_result = self.dispatch_request(decoded_request)
94 result = self.encode_result(decoded_result)
showard902c96c2009-09-11 18:47:35 +000095 if rpcserver_logging.LOGGING_ENABLED:
MK Ryue782e852015-06-29 18:10:36 -070096 self.log_request(user, decoded_request, decoded_result,
97 remote_ip)
showard3d6ae112009-05-02 00:45:48 +000098 return rpc_utils.raw_http_response(result)
showardef6fe022009-03-27 20:55:16 +000099
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
showard3d6ae112009-05-02 00:45:48 +0000107 result = self.execute_request(request_data)
showardef6fe022009-03-27 20:55:16 +0000108 padded_result = '%s(%s)' % (callback_name, result)
showard3d6ae112009-05-02 00:45:48 +0000109 return rpc_utils.raw_http_response(padded_result,
110 content_type='text/javascript')
showardef6fe022009-03-27 20:55:16 +0000111
112
jadmanski0afbb632008-06-06 21:10:57 +0000113 @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
showard7c785282008-05-29 19:45:12 +0000128
129
jadmanski0afbb632008-06-06 21:10:57 +0000130 def _grab_methods_from(self, module):
131 for name in dir(module):
showard5deef7f2009-09-09 18:16:58 +0000132 if name.startswith('_'):
133 continue
jadmanski0afbb632008-06-06 21:10:57 +0000134 attribute = getattr(module, name)
showarda849ceb2010-01-20 01:12:42 +0000135 if not inspect.isfunction(attribute):
jadmanski0afbb632008-06-06 21:10:57 +0000136 continue
showardef6fe022009-03-27 20:55:16 +0000137 decorated_function = RpcHandler._allow_keyword_args(attribute)
jadmanski0afbb632008-06-06 21:10:57 +0000138 setattr(self._rpc_methods, name, decorated_function)
MK Ryue782e852015-06-29 18:10:36 -0700139
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