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
17 # See "2.2 Characters" in the XML specification:
19 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
21 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
23 invalid_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
24 # broke with f24, somehow we get a unicode as an incoming string to be translated
25 str_xml_escape_table = string.maketrans("".join((chr(x) for x in invalid_codepoints)),
26 "?" * len(invalid_codepoints))
27 # loosely inspired from
28 # http://www.terminally-incoherent.com/blog/2010/05/06/character-mapping-must-return-integer-none-or-unicode/
29 unicode_xml_escape_table = { invalid : u"?" for invalid in invalid_codepoints}
31 def xmlrpclib_escape(s, replace = string.replace):
33 xmlrpclib does not handle invalid 7-bit control characters. This
34 function augments xmlrpclib.escape, which by default only replaces
35 '&', '<', and '>' with entities.
38 # This is the standard xmlrpclib.escape function
39 s = replace(s, "&", "&")
40 s = replace(s, "<", "<")
41 s = replace(s, ">", ">",)
43 # Replace invalid 7-bit control characters with '?'
44 if isinstance(s, str):
45 return s.translate(str_xml_escape_table)
47 return s.translate(unicode_xml_escape_table)
49 def test_xmlrpclib_escape():
52 "".join( (chr(x) for x in range(128))),
53 # likewise but as a unicode string up to 256
54 u"".join( (unichr(x) for x in range(256))),
57 print "==================== xmlrpclib_escape INPUT"
58 print type(input), '->', input
59 print "==================== xmlrpclib_escape OUTPUT"
60 print xmlrpclib_escape(input)
62 def xmlrpclib_dump(self, value, write):
64 xmlrpclib cannot marshal instances of subclasses of built-in
65 types. This function overrides xmlrpclib.Marshaller.__dump so that
66 any value that is an instance of one of its acceptable types is
67 marshalled as that type.
69 xmlrpclib also cannot handle invalid 7-bit control characters. See
73 # Use our escape function
74 args = [self, value, write]
75 if isinstance(value, (str, unicode)):
76 args.append(xmlrpclib_escape)
79 # Try for an exact match first
80 f = self.dispatch[type(value)]
82 # Try for an isinstance() match
83 for Type, f in self.dispatch.iteritems():
84 if isinstance(value, Type):
87 raise TypeError, "cannot marshal %s objects" % type(value)
91 # You can't hide from me!
92 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
94 # SOAP support is optional
97 from SOAPpy.Parser import parseSOAPRPC
98 from SOAPpy.Types import faultType
99 from SOAPpy.NS import NS
100 from SOAPpy.SOAPBuilder import buildSOAP
104 from PLC.Config import Config
105 from PLC.Faults import *
109 def import_deep(name):
110 mod = __import__(name)
111 components = name.split('.')
112 for comp in components[1:]:
113 mod = getattr(mod, comp)
118 # flat list of method names
119 native_methods = PLC.Methods.native_methods
121 # other_methods_map : dict {methodname: fullpath}
122 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
124 for subdir in [ 'Accessors' ]:
126 # scan e.g. PLC.Accessors.__all__
127 pkg = __import__(path).__dict__[subdir]
128 for modulename in getattr(pkg,"__all__"):
129 fullpath=path+"."+modulename
130 for method in getattr(import_deep(fullpath),"methods"):
131 other_methods_map[method] = fullpath
133 all_methods = native_methods + other_methods_map.keys()
135 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
136 self.encoding = encoding
138 # Better just be documenting the API
143 self.config = Config(config)
145 # Initialize database connection
146 if self.config.PLC_DB_TYPE == "postgresql":
147 from PLC.PostgreSQL import PostgreSQL
148 self.db = PostgreSQL(self)
150 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
152 # Aspects modify the API by injecting code before, after or
153 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
155 if self.config.PLC_RATELIMIT_ENABLED:
156 from aspects import apply_ratelimit_aspect
157 apply_ratelimit_aspect()
159 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
160 from aspects.netconfigaspects import apply_netconfig_aspect
161 apply_netconfig_aspect()
163 # Enable Caching. Only for GetSlivers for the moment.
164 # TODO: we may consider to do this in an aspect like the ones above.
166 if self.config.PLC_GETSLIVERS_CACHE:
167 getslivers_cache = True
168 except AttributeError:
169 getslivers_cache = False
172 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
173 from cache_utils.decorators import cached
174 from PLC.Methods.GetSlivers import GetSlivers
177 def cacheable_call(cls, auth, node_id_or_hostname):
178 return cls.raw_call(auth, node_id_or_hostname)
180 GetSlivers.call = cacheable_call
184 def callable(self, method):
186 Return a new instance of the specified method.
190 if method not in self.all_methods:
191 raise PLCInvalidAPIMethod, method
193 # Get new instance of method
195 classname = method.split(".")[-1]
196 if method in self.native_methods:
197 fullpath="PLC.Methods." + method
199 fullpath=self.other_methods_map[method]
200 module = __import__(fullpath, globals(), locals(), [classname])
201 return getattr(module, classname)(self)
202 except ImportError, AttributeError:
203 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
205 def call(self, source, method, *args):
207 Call the named method from the specified source with the
211 function = self.callable(method)
212 function.source = source
213 return function(*args)
215 def handle(self, source, data):
217 Handle an XML-RPC or SOAP request from the specified source.
220 # Parse request into method name and arguments
222 interface = xmlrpclib
223 (args, method) = xmlrpclib.loads(data)
224 methodresponse = True
226 if SOAPpy is not None:
228 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
231 # XXX Support named arguments
236 result = self.call(source, method, *args)
237 except PLCFault, fault:
238 # Handle expected faults
239 if interface == xmlrpclib:
241 methodresponse = None
242 elif interface == SOAPpy:
243 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
244 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
247 if interface == xmlrpclib:
248 if not isinstance(result, PLCFault):
250 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
251 elif interface == SOAPpy:
252 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
256 def handle_json(self, source, data):
258 Handle a JSON request
260 method, args = json.loads(data)
262 result = self.call(source, method, *args)
266 return json.dumps(result)
268 # one simple unit test
269 if __name__ == '__main__':
270 test_xmlrpclib_escape()