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
20 # # Try to use jsonlib before using simpljson. This is a hack to get around
21 # # the fact that the version of simplejson available for f8 is slightly
22 # # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib
23 # # package available for f8, so this has to be installed manually and
24 # # is not expected to always be available. Remove this once we move away
25 # # from f8 based MyPLC's
31 # See "2.2 Characters" in the XML specification:
33 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
35 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
37 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
38 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
40 def xmlrpclib_escape(s, replace = string.replace):
42 xmlrpclib does not handle invalid 7-bit control characters. This
43 function augments xmlrpclib.escape, which by default only replaces
44 '&', '<', and '>' with entities.
47 # This is the standard xmlrpclib.escape function
48 s = replace(s, "&", "&")
49 s = replace(s, "<", "<")
50 s = replace(s, ">", ">",)
52 # Replace invalid 7-bit control characters with '?'
53 return s.translate(xml_escape_table)
55 def xmlrpclib_dump(self, value, write):
57 xmlrpclib cannot marshal instances of subclasses of built-in
58 types. This function overrides xmlrpclib.Marshaller.__dump so that
59 any value that is an instance of one of its acceptable types is
60 marshalled as that type.
62 xmlrpclib also cannot handle invalid 7-bit control characters. See
66 # Use our escape function
67 args = [self, value, write]
68 if isinstance(value, (str, unicode)):
69 args.append(xmlrpclib_escape)
72 # Try for an exact match first
73 f = self.dispatch[type(value)]
75 # Try for an isinstance() match
76 for Type, f in self.dispatch.iteritems():
77 if isinstance(value, Type):
80 raise TypeError, "cannot marshal %s objects" % type(value)
84 # You can't hide from me!
85 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
87 # SOAP support is optional
90 from SOAPpy.Parser import parseSOAPRPC
91 from SOAPpy.Types import faultType
92 from SOAPpy.NS import NS
93 from SOAPpy.SOAPBuilder import buildSOAP
97 from PLC.Config import Config
98 from PLC.Faults import *
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/plc_config", encoding = "utf-8"):
129 self.encoding = encoding
131 # Better just be documenting the API
136 self.config = Config(config)
138 # Initialize database connection
139 if self.config.PLC_DB_TYPE == "postgresql":
140 from PLC.PostgreSQL import PostgreSQL
141 self.db = PostgreSQL(self)
143 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
145 # Aspects modify the API by injecting code before, after or
146 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
148 # As of now we only have aspects for OMF integration, that's
149 # why we enable aspects only if PLC_OMF is set to true.
150 if self.config.PLC_OMF_ENABLED:
151 from aspects import apply_omf_aspect
154 if self.config.PLC_RATELIMIT_ENABLED:
155 from aspects import apply_ratelimit_aspect
156 apply_ratelimit_aspect()
159 # Enable Caching. Only for GetSlivers for the moment.
160 # TODO: we may consider to do this in an aspect like the ones above.
162 if self.config.PLC_GETSLIVERS_CACHE:
163 getslivers_cache = True
164 except AttributeError:
165 getslivers_cache = False
168 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
169 from cache_utils.decorators import cached
170 from PLC.Methods.GetSlivers import GetSlivers
173 def cacheable_call(cls, auth, node_id_or_hostname):
174 return cls.raw_call(auth, node_id_or_hostname)
176 GetSlivers.call = cacheable_call
180 def callable(self, method):
182 Return a new instance of the specified method.
186 if method not in self.all_methods:
187 raise PLCInvalidAPIMethod, method
189 # Get new instance of method
191 classname = method.split(".")[-1]
192 if method in self.native_methods:
193 fullpath="PLC.Methods." + method
195 fullpath=self.other_methods_map[method]
196 module = __import__(fullpath, globals(), locals(), [classname])
197 return getattr(module, classname)(self)
198 except ImportError, AttributeError:
199 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
201 def call(self, source, method, *args):
203 Call the named method from the specified source with the
207 function = self.callable(method)
208 function.source = source
209 return function(*args)
211 def handle(self, source, data):
213 Handle an XML-RPC or SOAP request from the specified source.
216 # Parse request into method name and arguments
218 interface = xmlrpclib
219 (args, method) = xmlrpclib.loads(data)
220 methodresponse = True
222 if SOAPpy is not None:
224 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
227 # XXX Support named arguments
232 result = self.call(source, method, *args)
233 except PLCFault, fault:
234 # Handle expected faults
235 if interface == xmlrpclib:
237 methodresponse = None
238 elif interface == SOAPpy:
239 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
240 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
243 if interface == xmlrpclib:
244 if not isinstance(result, PLCFault):
246 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
247 elif interface == SOAPpy:
248 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
252 def handle_json(self, source, data):
254 Handle a JSON request
256 method, args = json.loads(data)
258 result = self.call(source, method, *args)
262 return json.dumps(result)