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
18 # Try to use jsonlib before using simpljson. This is a hack to get around
19 # the fact that the version of simplejson avaialble for f8 is slightly
20 # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib
21 # pacakge available for f8, so this has to be installed manually and
22 # is not expected to always be available. Remove this once we move away
23 # from f8 based MyPLC's
29 # See "2.2 Characters" in the XML specification:
31 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
33 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
35 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
36 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
38 def xmlrpclib_escape(s, replace = string.replace):
40 xmlrpclib does not handle invalid 7-bit control characters. This
41 function augments xmlrpclib.escape, which by default only replaces
42 '&', '<', and '>' with entities.
45 # This is the standard xmlrpclib.escape function
46 s = replace(s, "&", "&")
47 s = replace(s, "<", "<")
48 s = replace(s, ">", ">",)
50 # Replace invalid 7-bit control characters with '?'
51 return s.translate(xml_escape_table)
53 def xmlrpclib_dump(self, value, write):
55 xmlrpclib cannot marshal instances of subclasses of built-in
56 types. This function overrides xmlrpclib.Marshaller.__dump so that
57 any value that is an instance of one of its acceptable types is
58 marshalled as that type.
60 xmlrpclib also cannot handle invalid 7-bit control characters. See
64 # Use our escape function
65 args = [self, value, write]
66 if isinstance(value, (str, unicode)):
67 args.append(xmlrpclib_escape)
70 # Try for an exact match first
71 f = self.dispatch[type(value)]
73 # Try for an isinstance() match
74 for Type, f in self.dispatch.iteritems():
75 if isinstance(value, Type):
78 raise TypeError, "cannot marshal %s objects" % type(value)
82 # You can't hide from me!
83 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
85 # SOAP support is optional
88 from SOAPpy.Parser import parseSOAPRPC
89 from SOAPpy.Types import faultType
90 from SOAPpy.NS import NS
91 from SOAPpy.SOAPBuilder import buildSOAP
95 from PLC.Config import Config
96 from PLC.Logger import logger
97 from PLC.Faults import *
101 def import_deep(name):
102 mod = __import__(name)
103 components = name.split('.')
104 for comp in components[1:]:
105 mod = getattr(mod, comp)
110 # flat list of method names
111 native_methods = PLC.Methods.native_methods
113 # other_methods_map : dict {methodname: fullpath}
114 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
116 #for subdir in [ 'Accessors' ]:
118 # # scan e.g. PLC.Accessors.__all__
119 # pkg = __import__(path).__dict__[subdir]
120 # for modulename in getattr(pkg,"__all__"):
121 # fullpath=path+"."+modulename
122 # for method in getattr(import_deep(fullpath),"methods"):
123 # other_methods_map[method] = fullpath
125 all_methods = native_methods + other_methods_map.keys()
127 def __init__(self, config = "/etc/planetlab/plcapi_config", encoding = "utf-8"):
128 self.encoding = encoding
130 # Better just be documenting the API
135 self.config = Config(config)
137 # Aspects modify the API by injecting code before, after or
138 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
140 # As of now we only have aspects for OMF integration, that's
141 # why we enable aspects only if PLC_OMF is set to true.
142 if self.config.api_omf_enabled:
143 from aspects import apply_omf_aspect
146 if self.config.api_ratelimit_enabled:
147 from aspects import apply_ratelimit_aspect
148 apply_ratelimit_aspect()
150 def callable(self, method):
152 Return a new instance of the specified method.
156 if method not in self.all_methods:
157 raise PLCInvalidAPIMethod, method
159 # Get new instance of method
161 classname = method.split(".")[-1]
162 if method in self.native_methods:
163 fullpath="PLC.Methods." + method
165 fullpath=self.other_methods_map[method]
166 module = __import__(fullpath, globals(), locals(), [classname])
167 return getattr(module, classname)(self)
168 except ImportError, AttributeError:
169 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
171 def call(self, source, method, *args):
173 Call the named method from the specified source with the
177 function = self.callable(method)
178 function.source = source
179 return function(*args)
181 def handle(self, source, data):
183 Handle an XML-RPC or SOAP request from the specified source.
186 # Parse request into method name and arguments
189 interface = xmlrpclib
190 (args, method) = xmlrpclib.loads(data)
191 methodresponse = True
193 if SOAPpy is not None:
195 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
198 # XXX Support named arguments
203 result = self.call(source, method, *args)
205 logger.info("%s %s %s" % (source, method, end-start))
206 except PLCFault, fault:
208 logger.log_exc("%s %s %s" % (source, method, end-start))
209 # Handle expected faults
210 if interface == xmlrpclib:
212 methodresponse = None
213 elif interface == SOAPpy:
214 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
215 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
216 except Exception, fault:
218 logger.log_exc("%s %s %s" % (source, method, end-start))
219 if interface == xmlrpclib:
220 result = PLCFault(500, str(fault))
221 methodresponse = None
222 elif interface == SOAPpy:
223 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
224 result._setDetail("Fault %d: %s" % (500, str(fault)))
227 if interface == xmlrpclib:
228 if not isinstance(result, PLCFault):
230 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
231 elif interface == SOAPpy:
232 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
236 def handle_json(self, source, data):
238 Handle a JSON request
240 method, args = json.loads(data)
242 result = self.call(source, method, *args)
246 return json.dumps(result)