blob: 1e1df4c75f78d6faf5801994e0ded0354d8e029f [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
Jakob Juelicha94efe62014-09-18 16:02:49 -070022import os
Dan Shif770c452016-03-04 12:51:18 -080023import socket
mblighe8819cd2008-02-15 16:48:40 +000024import urllib2
Chris Masonef8b53062012-05-08 22:14:18 -070025from autotest_lib.client.common_lib import error as exceptions
mblighe8819cd2008-02-15 16:48:40 +000026
Jakob Juelicha94efe62014-09-18 16:02:49 -070027from json import decoder
28
29from json import encoder as json_encoder
30json_encoder_class = json_encoder.JSONEncoder
31
32
33# Try to upgrade to the Django JSON encoder. It uses the standard json encoder
34# but can handle DateTime
35try:
36 # See http://crbug.com/418022 too see why the try except is needed here.
37 from django import conf as django_conf
38 # The serializers can't be imported if django isn't configured.
39 # Using try except here doesn't work, as test_that initializes it's own
40 # django environment (setup_django_lite_environment) which raises import
41 # errors if the django dbutils have been previously imported, as importing
42 # them leaves some state behind.
43 # This the variable name must not be undefined or empty string.
44 if os.environ.get(django_conf.ENVIRONMENT_VARIABLE, None):
45 from django.core.serializers import json as django_encoder
46 json_encoder_class = django_encoder.DjangoJSONEncoder
47except ImportError:
48 pass
49
50
mblighe8819cd2008-02-15 16:48:40 +000051class JSONRPCException(Exception):
52 pass
53
Chris Masone47c9e642012-04-25 14:22:18 -070054class ValidationError(JSONRPCException):
55 """Raised when the RPC is malformed."""
56 def __init__(self, error, formatted_message):
57 """Constructor.
58
59 @param error: a dict of error info like so:
60 {error['name']: 'ErrorKind',
61 error['message']: 'Pithy error description.',
62 error['traceback']: 'Multi-line stack trace'}
63 @formatted_message: string representation of this exception.
64 """
65 self.problem_keys = eval(error['message'])
66 self.traceback = error['traceback']
67 super(ValidationError, self).__init__(formatted_message)
68
69def BuildException(error):
70 """Exception factory.
71
72 Given a dict of error info, determine which subclass of
73 JSONRPCException to build and return. If can't determine the right one,
74 just return a JSONRPCException with a pretty-printed error string.
75
76 @param error: a dict of error info like so:
77 {error['name']: 'ErrorKind',
78 error['message']: 'Pithy error description.',
79 error['traceback']: 'Multi-line stack trace'}
80 """
81 error_message = '%(name)s: %(message)s\n%(traceback)s' % error
82 for cls in JSONRPCException.__subclasses__():
83 if error['name'] == cls.__name__:
84 return cls(error, error_message)
Alex Miller4a193692013-08-21 13:59:01 -070085 for cls in (exceptions.CrosDynamicSuiteException.__subclasses__() +
86 exceptions.RPCException.__subclasses__()):
Chris Masonef8b53062012-05-08 22:14:18 -070087 if error['name'] == cls.__name__:
88 return cls(error_message)
Chris Masone47c9e642012-04-25 14:22:18 -070089 return JSONRPCException(error_message)
90
mblighe8819cd2008-02-15 16:48:40 +000091class ServiceProxy(object):
92 def __init__(self, serviceURL, serviceName=None, headers=None):
93 self.__serviceURL = serviceURL
94 self.__serviceName = serviceName
showard8e855a22008-04-15 17:32:20 +000095 self.__headers = headers or {}
mblighe8819cd2008-02-15 16:48:40 +000096
97 def __getattr__(self, name):
mblighd876f452008-12-03 15:09:17 +000098 if self.__serviceName is not None:
mblighe8819cd2008-02-15 16:48:40 +000099 name = "%s.%s" % (self.__serviceName, name)
100 return ServiceProxy(self.__serviceURL, name, self.__headers)
101
102 def __call__(self, *args, **kwargs):
Dan Shif770c452016-03-04 12:51:18 -0800103 # Caller can pass in a minimum value of timeout to be used for urlopen
104 # call. Otherwise, the default socket timeout will be used.
105 min_rpc_timeout = kwargs.pop('min_rpc_timeout', None)
Jakob Juelicha94efe62014-09-18 16:02:49 -0700106 postdata = json_encoder_class().encode({'method': self.__serviceName,
jadmanski08ff9ad2010-01-27 23:42:42 +0000107 'params': args + (kwargs,),
Jakob Juelicha94efe62014-09-18 16:02:49 -0700108 'id': 'jsonrpc'})
jadmanski0afbb632008-06-06 21:10:57 +0000109 request = urllib2.Request(self.__serviceURL, data=postdata,
110 headers=self.__headers)
Dan Shif770c452016-03-04 12:51:18 -0800111 default_timeout = socket.getdefaulttimeout()
112 if not default_timeout:
113 # If default timeout is None, socket will never time out.
114 respdata = urllib2.urlopen(request).read()
115 else:
116 timeout = max(min_rpc_timeout, default_timeout)
117 respdata = urllib2.urlopen(request, timeout=timeout).read()
jadmanski0afbb632008-06-06 21:10:57 +0000118 try:
jadmanski08ff9ad2010-01-27 23:42:42 +0000119 resp = decoder.JSONDecoder().decode(respdata)
jadmanski0afbb632008-06-06 21:10:57 +0000120 except ValueError:
121 raise JSONRPCException('Error decoding JSON reponse:\n' + respdata)
mblighd876f452008-12-03 15:09:17 +0000122 if resp['error'] is not None:
Chris Masone47c9e642012-04-25 14:22:18 -0700123 raise BuildException(resp['error'])
jadmanski0afbb632008-06-06 21:10:57 +0000124 else:
125 return resp['result']