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 # Enable Caching. Only for GetSlivers for the moment.
168 # TODO: we may consider to do this in an aspect like the ones above.
170 if self.config.PLC_GETSLIVERS_CACHE:
171 getslivers_cache = True
172 except AttributeError:
173 getslivers_cache = False
176 os.environ['DJANGO_SETTINGS_MODULE'] = 'plc_django_settings'
177 from cache_utils.decorators import cached
178 from PLC.Methods.GetSlivers import GetSlivers
181 def cacheable_call(cls, auth, node_id_or_hostname):
182 return cls.raw_call(auth, node_id_or_hostname)
184 GetSlivers.call = cacheable_call
186 def callable(self, method):
188 Return a new instance of the specified method.
191 if method not in self.all_methods:
192 raise PLCInvalidAPIMethod(method)
194 # Get new instance of method
196 classname = method.split(".")[-1]
197 if method in self.native_methods:
198 fullpath = "PLC.Methods." + method
200 fullpath = self.other_methods_map[method]
201 module = __import__(fullpath, globals(), locals(), [classname])
202 return getattr(module, classname)(self)
203 except (ImportError, AttributeError):
204 raise PLCInvalidAPIMethod("import error %s for %s"
205 % (AttributeError, fullpath))
207 def call(self, source, method, *args):
209 Call the named method from the specified source with the
213 function = self.callable(method)
214 function.source = source
215 return function(*args)
217 def handle(self, source, data):
219 Handle an XML-RPC or SOAP request from the specified source.
222 # Parse request into method name and arguments
224 interface = xmlrpclib
225 (args, method) = xmlrpclib.loads(data)
226 methodresponse = True
227 except Exception as exc:
228 if SOAPpy is not None:
230 (r, header, body, attrs) = \
231 parseSOAPRPC(data, header=1, body=1, attrs=1)
234 # XXX Support named arguments
239 result = self.call(source, method, *args)
240 except PLCFault as fault:
241 # Handle expected faults
242 if interface == xmlrpclib:
244 methodresponse = None
245 elif interface == SOAPpy:
246 result = faultParameter(NS.ENV_T + ":Server",
247 "Method Failed", method)
248 result._setDetail("Fault %d: %s"
249 % (fault.faultCode, fault.faultString))
252 if interface == xmlrpclib:
253 if not isinstance(result, PLCFault):
255 data = xmlrpclib.dumps(result, methodresponse=True,
256 encoding=self.encoding, allow_none=1)
257 elif interface == SOAPpy:
259 kw={'%sResponse' % method: {'Result': result}},
260 encoding=self.encoding)
264 def handle_json(self, source, data):
266 Handle a JSON request
268 method, args = json.loads(data)
270 result = self.call(source, method, *args)
271 except Exception as exc:
274 return json.dumps(result)
276 # one simple unit test
277 if __name__ == '__main__':
278 test_xmlrpclib_escape()