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 # Try to use jsonlib before using simpljson. This is a hack to get around
18 # the fact that the version of simplejson avaialble for f8 is slightly
19 # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib
20 # pacakge available for f8, so this has to be installed manually and
21 # is not expected to always be available. Remove this once we move away
22 # from f8 based MyPLC's
28 # See "2.2 Characters" in the XML specification:
30 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
32 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
34 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
35 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
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 return s.translate(xml_escape_table)
52 def xmlrpclib_dump(self, value, write):
54 xmlrpclib cannot marshal instances of subclasses of built-in
55 types. This function overrides xmlrpclib.Marshaller.__dump so that
56 any value that is an instance of one of its acceptable types is
57 marshalled as that type.
59 xmlrpclib also cannot handle invalid 7-bit control characters. See
63 # Use our escape function
64 args = [self, value, write]
65 if isinstance(value, (str, unicode)):
66 args.append(xmlrpclib_escape)
69 # Try for an exact match first
70 f = self.dispatch[type(value)]
72 # Try for an isinstance() match
73 for Type, f in self.dispatch.iteritems():
74 if isinstance(value, Type):
77 raise TypeError, "cannot marshal %s objects" % type(value)
81 # You can't hide from me!
82 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
84 # SOAP support is optional
87 from SOAPpy.Parser import parseSOAPRPC
88 from SOAPpy.Types import faultType
89 from SOAPpy.NS import NS
90 from SOAPpy.SOAPBuilder import buildSOAP
94 from PLC.Config import Config
95 from PLC.Faults import *
99 def import_deep(name):
100 mod = __import__(name)
101 components = name.split('.')
102 for comp in components[1:]:
103 mod = getattr(mod, comp)
108 # flat list of method names
109 native_methods = PLC.Methods.native_methods
111 # other_methods_map : dict {methodname: fullpath}
112 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
114 for subdir in [ 'Accessors' ]:
116 # scan e.g. PLC.Accessors.__all__
117 pkg = __import__(path).__dict__[subdir]
118 for modulename in getattr(pkg,"__all__"):
119 fullpath=path+"."+modulename
120 for method in getattr(import_deep(fullpath),"methods"):
121 other_methods_map[method] = fullpath
123 all_methods = native_methods + other_methods_map.keys()
125 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
126 self.encoding = encoding
128 # Better just be documenting the API
133 self.config = Config(config)
135 # Initialize database connection
136 if self.config.PLC_DB_TYPE == "postgresql":
137 from PLC.PostgreSQL import PostgreSQL
138 self.db = PostgreSQL(self)
140 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
142 # Aspects modify the API by injecting code before, after or
143 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
145 # As of now we only have aspects for OMF integration, that's
146 # why we enable aspects only if PLC_OMF is set to true.
147 if self.config.PLC_OMF_ENABLED:
148 from aspects import apply_omf_aspect
151 if self.config.PLC_RATELIMIT_ENABLED:
152 from aspects import apply_ratelimit_aspect
153 apply_ratelimit_aspect()
156 def callable(self, method):
158 Return a new instance of the specified method.
162 if method not in self.all_methods:
163 raise PLCInvalidAPIMethod, method
165 # Get new instance of method
167 classname = method.split(".")[-1]
168 if method in self.native_methods:
169 fullpath="PLC.Methods." + method
171 fullpath=self.other_methods_map[method]
172 module = __import__(fullpath, globals(), locals(), [classname])
173 return getattr(module, classname)(self)
174 except ImportError, AttributeError:
175 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
177 def call(self, source, method, *args):
179 Call the named method from the specified source with the
183 function = self.callable(method)
184 function.source = source
185 return function(*args)
187 def handle(self, source, data):
189 Handle an XML-RPC or SOAP request from the specified source.
192 # Parse request into method name and arguments
194 interface = xmlrpclib
195 (args, method) = xmlrpclib.loads(data)
196 methodresponse = True
198 if SOAPpy is not None:
200 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
203 # XXX Support named arguments
208 result = self.call(source, method, *args)
209 except PLCFault, fault:
210 # Handle expected faults
211 if interface == xmlrpclib:
213 methodresponse = None
214 elif interface == SOAPpy:
215 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
216 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
219 if interface == xmlrpclib:
220 if not isinstance(result, PLCFault):
222 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
223 elif interface == SOAPpy:
224 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
228 def handle_json(self, source, data):
230 Handle a JSON request
232 method, args = json.loads(data)
234 result = self.call(source, method, *args)
238 return json.dumps(result)