2 # PLCAPI XML-RPC and SOAP interfaces
4 # Aaron Klingaman <alk@absarokasoft.com>
5 # Mark Huang <mlhuang@cs.princeton.edu>
7 # Copyright (C) 2004-2006 The Trustees of Princeton University
10 from __future__ import print_function
18 # See "2.2 Characters" in the XML specification:
20 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
22 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
24 invalid_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
25 # broke with f24, somehow we get a unicode
26 # as an incoming string to be translated
27 str_xml_escape_table = \
28 string.maketrans("".join((chr(x) for x in invalid_codepoints)),
29 "?" * len(invalid_codepoints))
30 # loosely inspired from
31 # http://stackoverflow.com/questions/1324067/
32 # how-do-i-get-str-translate-to-work-with-unicode-strings
33 unicode_xml_escape_table = \
34 {invalid: u"?" for invalid in invalid_codepoints}
37 def xmlrpclib_escape(s, replace=string.replace):
39 xmlrpclib does not handle invalid 7-bit control characters. This
40 function augments xmlrpclib.escape, which by default only replaces
41 '&', '<', and '>' with entities.
44 # This is the standard xmlrpclib.escape function
45 s = replace(s, "&", "&")
46 s = replace(s, "<", "<")
47 s = replace(s, ">", ">",)
49 # Replace invalid 7-bit control characters with '?'
50 if isinstance(s, str):
51 return s.translate(str_xml_escape_table)
53 return s.translate(unicode_xml_escape_table)
56 def test_xmlrpclib_escape():
59 "".join((chr(x) for x in range(128))),
60 # likewise but as a unicode string up to 256
61 u"".join((unichr(x) for x in range(256))),
64 print("==================== xmlrpclib_escape INPUT")
65 print(type(input), '->', input)
66 print("==================== xmlrpclib_escape OUTPUT")
67 print(xmlrpclib_escape(input))
70 def xmlrpclib_dump(self, value, write):
72 xmlrpclib cannot marshal instances of subclasses of built-in
73 types. This function overrides xmlrpclib.Marshaller.__dump so that
74 any value that is an instance of one of its acceptable types is
75 marshalled as that type.
77 xmlrpclib also cannot handle invalid 7-bit control characters. See
81 # Use our escape function
82 args = [self, value, write]
83 if isinstance(value, (str, unicode)):
84 args.append(xmlrpclib_escape)
87 # Try for an exact match first
88 f = self.dispatch[type(value)]
90 # Try for an isinstance() match
91 for Type, f in self.dispatch.iteritems():
92 if isinstance(value, Type):
95 raise TypeError("cannot marshal %s objects" % type(value))
100 # You can't hide from me!
101 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
103 # SOAP support is optional
106 from SOAPpy.Parser import parseSOAPRPC
107 from SOAPpy.Types import faultType
108 from SOAPpy.NS import NS
109 from SOAPpy.SOAPBuilder import buildSOAP
113 from PLC.Config import Config
114 from PLC.Faults import *
119 def import_deep(name):
120 mod = __import__(name)
121 components = name.split('.')
122 for comp in components[1:]:
123 mod = getattr(mod, comp)
129 # flat list of method names
130 native_methods = PLC.Methods.native_methods
132 # other_methods_map : dict {methodname: fullpath}
133 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
134 other_methods_map = {}
135 for subdir in ['Accessors']:
137 # scan e.g. PLC.Accessors.__all__
138 pkg = __import__(path).__dict__[subdir]
139 for modulename in getattr(pkg, "__all__"):
140 fullpath = path + "." + modulename
141 for method in getattr(import_deep(fullpath), "methods"):
142 other_methods_map[method] = fullpath
144 all_methods = native_methods + other_methods_map.keys()
146 def __init__(self, config="/etc/planetlab/plc_config",
148 self.encoding = encoding
150 # Better just be documenting the API
155 self.config = Config(config)
156 # print("config has keys {}"
157 # .format(vars(self.config).keys()))
159 # Initialize database connection
160 if self.config.PLC_DB_TYPE == "postgresql":
161 from PLC.PostgreSQL import PostgreSQL
162 self.db = PostgreSQL(self)
164 raise PLCAPIError("Unsupported database type "
165 + self.config.PLC_DB_TYPE)
167 # Aspects modify the API by injecting code before, after or
168 # around method calls.
169 # http://github.com/baris/pyaspects/blob/master/README
171 if self.config.PLC_RATELIMIT_ENABLED:
172 from aspects import apply_ratelimit_aspect
173 apply_ratelimit_aspect()
175 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
176 from aspects.netconfigaspects import apply_netconfig_aspect
177 apply_netconfig_aspect()
179 # Enable Caching. Only for GetSlivers for the moment.
180 # TODO: we may consider to do this in an aspect like the ones above.
182 if self.config.PLC_GETSLIVERS_CACHE:
183 getslivers_cache = True
184 except AttributeError:
185 getslivers_cache = False
188 os.environ['DJANGO_SETTINGS_MODULE'] = 'plc_django_settings'
189 from cache_utils.decorators import cached
190 from PLC.Methods.GetSlivers import GetSlivers
193 def cacheable_call(cls, auth, node_id_or_hostname):
194 return cls.raw_call(auth, node_id_or_hostname)
196 GetSlivers.call = cacheable_call
198 def callable(self, method):
200 Return a new instance of the specified method.
203 if method not in self.all_methods:
204 raise PLCInvalidAPIMethod(method)
206 # Get new instance of method
208 classname = method.split(".")[-1]
209 if method in self.native_methods:
210 fullpath = "PLC.Methods." + method
212 fullpath = self.other_methods_map[method]
213 module = __import__(fullpath, globals(), locals(), [classname])
214 return getattr(module, classname)(self)
215 except (ImportError, AttributeError):
216 raise PLCInvalidAPIMethod("import error %s for %s"
217 % (AttributeError, fullpath))
219 def call(self, source, method, *args):
221 Call the named method from the specified source with the
225 function = self.callable(method)
226 function.source = source
227 return function(*args)
229 def handle(self, source, data):
231 Handle an XML-RPC or SOAP request from the specified source.
234 # Parse request into method name and arguments
236 interface = xmlrpclib
237 (args, method) = xmlrpclib.loads(data)
238 methodresponse = True
239 except Exception as exc:
240 if SOAPpy is not None:
242 (r, header, body, attrs) = \
243 parseSOAPRPC(data, header=1, body=1, attrs=1)
246 # XXX Support named arguments
251 result = self.call(source, method, *args)
252 except PLCFault as fault:
253 # Handle expected faults
254 if interface == xmlrpclib:
256 methodresponse = None
257 elif interface == SOAPpy:
258 result = faultParameter(NS.ENV_T + ":Server",
259 "Method Failed", method)
260 result._setDetail("Fault %d: %s"
261 % (fault.faultCode, fault.faultString))
264 if interface == xmlrpclib:
265 if not isinstance(result, PLCFault):
267 data = xmlrpclib.dumps(result, methodresponse=True,
268 encoding=self.encoding, allow_none=1)
269 elif interface == SOAPpy:
271 kw={'%sResponse' % method: {'Result': result}},
272 encoding=self.encoding)
276 def handle_json(self, source, data):
278 Handle a JSON request
280 method, args = json.loads(data)
282 result = self.call(source, method, *args)
283 except Exception as exc:
286 return json.dumps(result)
288 # one simple unit test
289 if __name__ == '__main__':
290 test_xmlrpclib_escape()