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 *
98 from PLC.NovaShell import NovaShell
102 def import_deep(name):
103 mod = __import__(name)
104 components = name.split('.')
105 for comp in components[1:]:
106 mod = getattr(mod, comp)
111 # flat list of method names
112 native_methods = PLC.Methods.native_methods
114 # other_methods_map : dict {methodname: fullpath}
115 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
117 #for subdir in [ 'Accessors' ]:
119 # # scan e.g. PLC.Accessors.__all__
120 # pkg = __import__(path).__dict__[subdir]
121 # for modulename in getattr(pkg,"__all__"):
122 # fullpath=path+"."+modulename
123 # for method in getattr(import_deep(fullpath),"methods"):
124 # other_methods_map[method] = fullpath
126 all_methods = native_methods + other_methods_map.keys()
128 def __init__(self, config = "/etc/planetlab/plcapi_config", encoding = "utf-8"):
129 self.encoding = encoding
131 # Better just be documenting the API
136 self.config = Config(config)
138 # admin connection to nova api
139 self.admin_shell = NovaShell()
140 self.client_shell = NovaShell()
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.api_omf_enabled:
148 from aspects import apply_omf_aspect
151 if self.config.api_ratelimit_enabled:
152 from aspects import apply_ratelimit_aspect
153 apply_ratelimit_aspect()
155 def callable(self, method):
157 Return a new instance of the specified method.
161 if method not in self.all_methods:
162 raise PLCInvalidAPIMethod, method
164 # Get new instance of method
166 classname = method.split(".")[-1]
167 if method in self.native_methods:
168 fullpath="PLC.Methods." + method
170 fullpath=self.other_methods_map[method]
171 module = __import__(fullpath, globals(), locals(), [classname])
172 return getattr(module, classname)(self)
173 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
195 interface = xmlrpclib
196 (args, method) = xmlrpclib.loads(data)
197 methodresponse = True
199 if SOAPpy is not None:
201 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
204 # XXX Support named arguments
209 result = self.call(source, method, *args)
211 logger.info("%s %s %s" % (source, method, end-start))
212 except PLCFault, fault:
214 logger.log_exc("%s %s %s" % (source, method, end-start))
215 # Handle expected faults
216 if interface == xmlrpclib:
218 methodresponse = None
219 elif interface == SOAPpy:
220 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
221 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
222 except Exception, fault:
224 logger.log_exc("%s %s %s" % (source, method, end-start))
225 if interface == xmlrpclib:
226 result = PLCFault(500, str(fault))
227 methodresponse = None
228 elif interface == SOAPpy:
229 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
230 result._setDetail("Fault %d: %s" % (500, str(fault)))
233 if interface == xmlrpclib:
234 if not isinstance(result, PLCFault):
236 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
237 elif interface == SOAPpy:
238 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
242 def handle_json(self, source, data):
244 Handle a JSON request
246 method, args = json.loads(data)
248 result = self.call(source, method, *args)
252 return json.dumps(result)