mbligh | e8819cd | 2008-02-15 16:48:40 +0000 | [diff] [blame] | 1 | """\ |
| 2 | RPC request handler Django. Exposed RPC interface functions should be |
| 3 | defined in rpc_interface.py. |
| 4 | """ |
| 5 | |
| 6 | __author__ = 'showard@google.com (Steve Howard)' |
| 7 | |
showard | a849ceb | 2010-01-20 01:12:42 +0000 | [diff] [blame] | 8 | import traceback, pydoc, re, urllib, logging, logging.handlers, inspect |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 9 | from autotest_lib.frontend.afe.json_rpc import serviceHandler |
showard | 64a9595 | 2010-01-13 21:27:16 +0000 | [diff] [blame] | 10 | from autotest_lib.frontend.afe import models, rpc_utils |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 11 | from autotest_lib.client.common_lib import global_config |
| 12 | from autotest_lib.frontend.afe import rpcserver_logging |
| 13 | |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 14 | LOGGING_REGEXPS = [r'.*add_.*', |
| 15 | r'delete_.*', |
MK Ryu | 3e1de8b | 2015-05-27 16:47:10 -0700 | [diff] [blame] | 16 | r'.*remove_.*', |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 17 | r'modify_.*', |
MK Ryu | 3e1de8b | 2015-05-27 16:47:10 -0700 | [diff] [blame] | 18 | r'create.*', |
| 19 | r'set_.*'] |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 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) |
mbligh | e8819cd | 2008-02-15 16:48:40 +0000 | [diff] [blame] | 26 | |
mbligh | e8819cd | 2008-02-15 16:48:40 +0000 | [diff] [blame] | 27 | |
| 28 | class RpcMethodHolder(object): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 29 | 'Dummy class to hold RPC interface methods as attributes.' |
mbligh | e8819cd | 2008-02-15 16:48:40 +0000 | [diff] [blame] | 30 | |
| 31 | |
showard | 7c78528 | 2008-05-29 19:45:12 +0000 | [diff] [blame] | 32 | class RpcHandler(object): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 33 | def __init__(self, rpc_interface_modules, document_module=None): |
| 34 | self._rpc_methods = RpcMethodHolder() |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 35 | self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods) |
mbligh | e8819cd | 2008-02-15 16:48:40 +0000 | [diff] [blame] | 36 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 37 | # store all methods from interface modules |
| 38 | for module in rpc_interface_modules: |
| 39 | self._grab_methods_from(module) |
showard | 7c78528 | 2008-05-29 19:45:12 +0000 | [diff] [blame] | 40 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 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) |
showard | 7c78528 | 2008-05-29 19:45:12 +0000 | [diff] [blame] | 46 | |
| 47 | |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 48 | def get_rpc_documentation(self): |
showard | 2d7ac83 | 2009-06-22 18:14:10 +0000 | [diff] [blame] | 49 | return rpc_utils.raw_http_response(self.html_doc) |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 50 | |
| 51 | |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 52 | def raw_request_data(self, request): |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 53 | if request.method == 'POST': |
| 54 | return request.raw_post_data |
| 55 | return urllib.unquote(request.META['QUERY_STRING']) |
| 56 | |
| 57 | |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 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 | |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 70 | def log_request(self, user, decoded_request, decoded_result, |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 71 | remote_ip, log_all=False): |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 72 | if log_all or should_log_message(decoded_request['method']): |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 73 | msg = '%s| %s:%s %s' % (remote_ip, decoded_request['method'], |
| 74 | user, decoded_request['params']) |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 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 | |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 86 | def handle_rpc_request(self, request): |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 87 | remote_ip = self._get_remote_ip(request) |
showard | 64a9595 | 2010-01-13 21:27:16 +0000 | [diff] [blame] | 88 | user = models.User.current_user() |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 89 | json_request = self.raw_request_data(request) |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 90 | decoded_request = self.decode_request(json_request) |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 91 | |
| 92 | decoded_request['remote_ip'] = remote_ip |
showard | 02ed4bd | 2009-09-09 15:30:11 +0000 | [diff] [blame] | 93 | decoded_result = self.dispatch_request(decoded_request) |
| 94 | result = self.encode_result(decoded_result) |
showard | 902c96c | 2009-09-11 18:47:35 +0000 | [diff] [blame] | 95 | if rpcserver_logging.LOGGING_ENABLED: |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 96 | self.log_request(user, decoded_request, decoded_result, |
| 97 | remote_ip) |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 98 | return rpc_utils.raw_http_response(result) |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 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 | |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 107 | result = self.execute_request(request_data) |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 108 | padded_result = '%s(%s)' % (callback_name, result) |
showard | 3d6ae11 | 2009-05-02 00:45:48 +0000 | [diff] [blame] | 109 | return rpc_utils.raw_http_response(padded_result, |
| 110 | content_type='text/javascript') |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 111 | |
| 112 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 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 |
showard | 7c78528 | 2008-05-29 19:45:12 +0000 | [diff] [blame] | 128 | |
| 129 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 130 | def _grab_methods_from(self, module): |
| 131 | for name in dir(module): |
showard | 5deef7f | 2009-09-09 18:16:58 +0000 | [diff] [blame] | 132 | if name.startswith('_'): |
| 133 | continue |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 134 | attribute = getattr(module, name) |
showard | a849ceb | 2010-01-20 01:12:42 +0000 | [diff] [blame] | 135 | if not inspect.isfunction(attribute): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 136 | continue |
showard | ef6fe02 | 2009-03-27 20:55:16 +0000 | [diff] [blame] | 137 | decorated_function = RpcHandler._allow_keyword_args(attribute) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 138 | setattr(self._rpc_methods, name, decorated_function) |
MK Ryu | e782e85 | 2015-06-29 18:10:36 -0700 | [diff] [blame^] | 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 |