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()
158 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
159 from aspects.netconfigaspects import apply_netconfig_aspect
160 apply_netconfig_aspect()
162 # Enable Caching. Only for GetSlivers for the moment.
163 # TODO: we may consider to do this in an aspect like the ones above.
165 if self.config.PLC_GETSLIVERS_CACHE:
166 getslivers_cache = True
167 except AttributeError:
168 getslivers_cache = False
171 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
172 from cache_utils.decorators import cached
173 from PLC.Methods.GetSlivers import GetSlivers
176 def cacheable_call(cls, auth, node_id_or_hostname):
177 return cls.raw_call(auth, node_id_or_hostname)
179 GetSlivers.call = cacheable_call
183 def callable(self, method):
185 Return a new instance of the specified method.
189 if method not in self.all_methods:
190 raise PLCInvalidAPIMethod, method
192 # Get new instance of method
194 classname = method.split(".")[-1]
195 if method in self.native_methods:
196 fullpath="PLC.Methods." + method
198 fullpath=self.other_methods_map[method]
199 module = __import__(fullpath, globals(), locals(), [classname])
200 return getattr(module, classname)(self)
201 except ImportError, AttributeError:
202 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
204 def call(self, source, method, *args):
206 Call the named method from the specified source with the
210 function = self.callable(method)
211 function.source = source
212 return function(*args)
214 def handle(self, source, data):
216 Handle an XML-RPC or SOAP request from the specified source.
219 # Parse request into method name and arguments
221 interface = xmlrpclib
222 (args, method) = xmlrpclib.loads(data)
223 methodresponse = True
225 if SOAPpy is not None:
227 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
230 # XXX Support named arguments
235 result = self.call(source, method, *args)
236 except PLCFault, fault:
237 # Handle expected faults
238 if interface == xmlrpclib:
240 methodresponse = None
241 elif interface == SOAPpy:
242 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
243 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
246 if interface == xmlrpclib:
247 if not isinstance(result, PLCFault):
249 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
250 elif interface == SOAPpy:
251 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
255 def handle_json(self, source, data):
257 Handle a JSON request
259 method, args = json.loads(data)
261 result = self.call(source, method, *args)
265 return json.dumps(result)