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://stackoverflow.com/questions/1324067/how-do-i-get-str-translate-to-work-with-unicode-strings
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)
144 # print("config has keys {}"
145 # .format(vars(self.config).keys()))
147 # Initialize database connection
148 if self.config.PLC_DB_TYPE == "postgresql":
149 from PLC.PostgreSQL import PostgreSQL
150 self.db = PostgreSQL(self)
152 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
154 # Aspects modify the API by injecting code before, after or
155 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
157 if self.config.PLC_RATELIMIT_ENABLED:
158 from aspects import apply_ratelimit_aspect
159 apply_ratelimit_aspect()
161 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
162 from aspects.netconfigaspects import apply_netconfig_aspect
163 apply_netconfig_aspect()
165 # Enable Caching. Only for GetSlivers for the moment.
166 # TODO: we may consider to do this in an aspect like the ones above.
168 if self.config.PLC_GETSLIVERS_CACHE:
169 getslivers_cache = True
170 except AttributeError:
171 getslivers_cache = False
174 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
175 from cache_utils.decorators import cached
176 from PLC.Methods.GetSlivers import GetSlivers
179 def cacheable_call(cls, auth, node_id_or_hostname):
180 return cls.raw_call(auth, node_id_or_hostname)
182 GetSlivers.call = cacheable_call
186 def callable(self, method):
188 Return a new instance of the specified method.
192 if method not in self.all_methods:
193 raise PLCInvalidAPIMethod, method
195 # Get new instance of method
197 classname = method.split(".")[-1]
198 if method in self.native_methods:
199 fullpath="PLC.Methods." + method
201 fullpath=self.other_methods_map[method]
202 module = __import__(fullpath, globals(), locals(), [classname])
203 return getattr(module, classname)(self)
204 except ImportError, AttributeError:
205 raise PLCInvalidAPIMethod, "import error %s for %s" % (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
228 if SOAPpy is not None:
230 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
233 # XXX Support named arguments
238 result = self.call(source, method, *args)
239 except PLCFault, fault:
240 # Handle expected faults
241 if interface == xmlrpclib:
243 methodresponse = None
244 elif interface == SOAPpy:
245 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
246 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
249 if interface == xmlrpclib:
250 if not isinstance(result, PLCFault):
252 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
253 elif interface == SOAPpy:
254 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
258 def handle_json(self, source, data):
260 Handle a JSON request
262 method, args = json.loads(data)
264 result = self.call(source, method, *args)
268 return json.dumps(result)
270 # one simple unit test
271 if __name__ == '__main__':
272 test_xmlrpclib_escape()