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 if self.config.PLC_RATELIMIT_ENABLED:
149 from aspects import apply_ratelimit_aspect
150 apply_ratelimit_aspect()
152 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
153 from aspects.netconfigaspects import apply_netconfig_aspect
154 apply_netconfig_aspect()
156 # Enable Caching. Only for GetSlivers for the moment.
157 # TODO: we may consider to do this in an aspect like the ones above.
159 if self.config.PLC_GETSLIVERS_CACHE:
160 getslivers_cache = True
161 except AttributeError:
162 getslivers_cache = False
165 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
166 from cache_utils.decorators import cached
167 from PLC.Methods.GetSlivers import GetSlivers
170 def cacheable_call(cls, auth, node_id_or_hostname):
171 return cls.raw_call(auth, node_id_or_hostname)
173 GetSlivers.call = cacheable_call
177 def callable(self, method):
179 Return a new instance of the specified method.
183 if method not in self.all_methods:
184 raise PLCInvalidAPIMethod, method
186 # Get new instance of method
188 classname = method.split(".")[-1]
189 if method in self.native_methods:
190 fullpath="PLC.Methods." + method
192 fullpath=self.other_methods_map[method]
193 module = __import__(fullpath, globals(), locals(), [classname])
194 return getattr(module, classname)(self)
195 except ImportError, AttributeError:
196 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
198 def call(self, source, method, *args):
200 Call the named method from the specified source with the
204 function = self.callable(method)
205 function.source = source
206 return function(*args)
208 def handle(self, source, data):
210 Handle an XML-RPC or SOAP request from the specified source.
213 # Parse request into method name and arguments
215 interface = xmlrpclib
216 (args, method) = xmlrpclib.loads(data)
217 methodresponse = True
219 if SOAPpy is not None:
221 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
224 # XXX Support named arguments
229 result = self.call(source, method, *args)
230 except PLCFault, fault:
231 # Handle expected faults
232 if interface == xmlrpclib:
234 methodresponse = None
235 elif interface == SOAPpy:
236 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
237 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
240 if interface == xmlrpclib:
241 if not isinstance(result, PLCFault):
243 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
244 elif interface == SOAPpy:
245 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
249 def handle_json(self, source, data):
251 Handle a JSON request
253 method, args = json.loads(data)
255 result = self.call(source, method, *args)
259 return json.dumps(result)