blob: 3d0325deed9a727feec3170f9a245c06b84b6718 [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001
2"""
3 Copyright (c) 2007 Jan-Klaas Kollhof
4
5 This file is part of jsonrpc.
6
7 jsonrpc is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 This software is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with this software; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20"""
21
Simran Basi9344c802016-05-05 14:14:01 -070022import logging
Jakob Juelicha94efe62014-09-18 16:02:49 -070023import os
Dan Shif770c452016-03-04 12:51:18 -080024import socket
mblighe8819cd2008-02-15 16:48:40 +000025import urllib2
Chris Masonef8b53062012-05-08 22:14:18 -070026from autotest_lib.client.common_lib import error as exceptions
mblighe8819cd2008-02-15 16:48:40 +000027
Jakob Juelicha94efe62014-09-18 16:02:49 -070028from json import decoder
29
30from json import encoder as json_encoder
31json_encoder_class = json_encoder.JSONEncoder
32
33
34# Try to upgrade to the Django JSON encoder. It uses the standard json encoder
35# but can handle DateTime
36try:
37 # See http://crbug.com/418022 too see why the try except is needed here.
38 from django import conf as django_conf
39 # The serializers can't be imported if django isn't configured.
40 # Using try except here doesn't work, as test_that initializes it's own
41 # django environment (setup_django_lite_environment) which raises import
42 # errors if the django dbutils have been previously imported, as importing
43 # them leaves some state behind.
44 # This the variable name must not be undefined or empty string.
45 if os.environ.get(django_conf.ENVIRONMENT_VARIABLE, None):
46 from django.core.serializers import json as django_encoder
47 json_encoder_class = django_encoder.DjangoJSONEncoder
48except ImportError:
49 pass
50
51
mblighe8819cd2008-02-15 16:48:40 +000052class JSONRPCException(Exception):
53 pass
54
Chris Masone47c9e642012-04-25 14:22:18 -070055class ValidationError(JSONRPCException):
56 """Raised when the RPC is malformed."""
57 def __init__(self, error, formatted_message):
58 """Constructor.
59
60 @param error: a dict of error info like so:
61 {error['name']: 'ErrorKind',
62 error['message']: 'Pithy error description.',
63 error['traceback']: 'Multi-line stack trace'}
64 @formatted_message: string representation of this exception.
65 """
66 self.problem_keys = eval(error['message'])
67 self.traceback = error['traceback']
68 super(ValidationError, self).__init__(formatted_message)
69
70def BuildException(error):
71 """Exception factory.
72
73 Given a dict of error info, determine which subclass of
74 JSONRPCException to build and return. If can't determine the right one,
75 just return a JSONRPCException with a pretty-printed error string.
76
77 @param error: a dict of error info like so:
78 {error['name']: 'ErrorKind',
79 error['message']: 'Pithy error description.',
80 error['traceback']: 'Multi-line stack trace'}
81 """
82 error_message = '%(name)s: %(message)s\n%(traceback)s' % error
83 for cls in JSONRPCException.__subclasses__():
84 if error['name'] == cls.__name__:
85 return cls(error, error_message)
Alex Miller4a193692013-08-21 13:59:01 -070086 for cls in (exceptions.CrosDynamicSuiteException.__subclasses__() +
87 exceptions.RPCException.__subclasses__()):
Chris Masonef8b53062012-05-08 22:14:18 -070088 if error['name'] == cls.__name__:
89 return cls(error_message)
Chris Masone47c9e642012-04-25 14:22:18 -070090 return JSONRPCException(error_message)
91
mblighe8819cd2008-02-15 16:48:40 +000092class ServiceProxy(object):
93 def __init__(self, serviceURL, serviceName=None, headers=None):
94 self.__serviceURL = serviceURL
95 self.__serviceName = serviceName
showard8e855a22008-04-15 17:32:20 +000096 self.__headers = headers or {}
mblighe8819cd2008-02-15 16:48:40 +000097
98 def __getattr__(self, name):
mblighd876f452008-12-03 15:09:17 +000099 if self.__serviceName is not None:
mblighe8819cd2008-02-15 16:48:40 +0000100 name = "%s.%s" % (self.__serviceName, name)
101 return ServiceProxy(self.__serviceURL, name, self.__headers)
102
103 def __call__(self, *args, **kwargs):
Dan Shif770c452016-03-04 12:51:18 -0800104 # Caller can pass in a minimum value of timeout to be used for urlopen
105 # call. Otherwise, the default socket timeout will be used.
106 min_rpc_timeout = kwargs.pop('min_rpc_timeout', None)
Jakob Juelicha94efe62014-09-18 16:02:49 -0700107 postdata = json_encoder_class().encode({'method': self.__serviceName,
jadmanski08ff9ad2010-01-27 23:42:42 +0000108 'params': args + (kwargs,),
Jakob Juelicha94efe62014-09-18 16:02:49 -0700109 'id': 'jsonrpc'})
jadmanski0afbb632008-06-06 21:10:57 +0000110 request = urllib2.Request(self.__serviceURL, data=postdata,
111 headers=self.__headers)
Dan Shif770c452016-03-04 12:51:18 -0800112 default_timeout = socket.getdefaulttimeout()
113 if not default_timeout:
114 # If default timeout is None, socket will never time out.
Simran Basi9344c802016-05-05 14:14:01 -0700115 urlfile = urllib2.urlopen(request)
Dan Shif770c452016-03-04 12:51:18 -0800116 else:
117 timeout = max(min_rpc_timeout, default_timeout)
Simran Basi9344c802016-05-05 14:14:01 -0700118 urlfile = urllib2.urlopen(request, timeout=timeout)
119 # Keep calling read on the urlfile to ensure all bytes are received.
120 respdata = b''
121 chunk = urlfile.read()
122 while len(chunk) != 0:
123 respdata += chunk
124 chunk = urlfile.read()
125 # TODO (sbasi): crbug.com/606071 Remove logging once json decode
126 # errors stop occuring.
127 if len(chunk) != 0:
128 logging.debug('Proxy required multiple reads to receive data.')
jadmanski0afbb632008-06-06 21:10:57 +0000129 try:
jadmanski08ff9ad2010-01-27 23:42:42 +0000130 resp = decoder.JSONDecoder().decode(respdata)
jadmanski0afbb632008-06-06 21:10:57 +0000131 except ValueError:
132 raise JSONRPCException('Error decoding JSON reponse:\n' + respdata)
mblighd876f452008-12-03 15:09:17 +0000133 if resp['error'] is not None:
Chris Masone47c9e642012-04-25 14:22:18 -0700134 raise BuildException(resp['error'])
jadmanski0afbb632008-06-06 21:10:57 +0000135 else:
136 return resp['result']