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
17 # See "2.2 Characters" in the XML specification:
19 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
21 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
23 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
24 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
26 def xmlrpclib_escape(s, replace = string.replace):
28 xmlrpclib does not handle invalid 7-bit control characters. This
29 function augments xmlrpclib.escape, which by default only replaces
30 '&', '<', and '>' with entities.
33 # This is the standard xmlrpclib.escape function
34 s = replace(s, "&", "&")
35 s = replace(s, "<", "<")
36 s = replace(s, ">", ">",)
38 # Replace invalid 7-bit control characters with '?'
39 return s.translate(xml_escape_table)
41 def xmlrpclib_dump(self, value, write):
43 xmlrpclib cannot marshal instances of subclasses of built-in
44 types. This function overrides xmlrpclib.Marshaller.__dump so that
45 any value that is an instance of one of its acceptable types is
46 marshalled as that type.
48 xmlrpclib also cannot handle invalid 7-bit control characters. See
52 # Use our escape function
53 args = [self, value, write]
54 if isinstance(value, (str, unicode)):
55 args.append(xmlrpclib_escape)
58 # Try for an exact match first
59 f = self.dispatch[type(value)]
61 # Try for an isinstance() match
62 for Type, f in self.dispatch.iteritems():
63 if isinstance(value, Type):
66 raise TypeError, "cannot marshal %s objects" % type(value)
70 # You can't hide from me!
71 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
73 # SOAP support is optional
76 from SOAPpy.Parser import parseSOAPRPC
77 from SOAPpy.Types import faultType
78 from SOAPpy.NS import NS
79 from SOAPpy.SOAPBuilder import buildSOAP
83 from PLC.Config import Config
84 from PLC.Faults import *
88 def import_deep(name):
89 mod = __import__(name)
90 components = name.split('.')
91 for comp in components[1:]:
92 mod = getattr(mod, comp)
97 # flat list of method names
98 native_methods = PLC.Methods.native_methods
100 # other_methods_map : dict {methodname: fullpath}
101 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
103 for subdir in [ 'Accessors' ]:
105 # scan e.g. PLC.Accessors.__all__
106 pkg = __import__(path).__dict__[subdir]
107 for modulename in getattr(pkg,"__all__"):
108 fullpath=path+"."+modulename
109 for method in getattr(import_deep(fullpath),"methods"):
110 other_methods_map[method] = fullpath
112 all_methods = native_methods + other_methods_map.keys()
114 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
115 self.encoding = encoding
117 # Better just be documenting the API
122 self.config = Config(config)
124 # Initialize database connection
125 if self.config.PLC_DB_TYPE == "postgresql":
126 from PLC.PostgreSQL import PostgreSQL
127 self.db = PostgreSQL(self)
129 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
131 # Aspects modify the API by injecting code before, after or
132 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
134 if self.config.PLC_RATELIMIT_ENABLED:
135 from aspects import apply_ratelimit_aspect
136 apply_ratelimit_aspect()
138 if getattr(self.config, "PLC_NETCONFIG_ENABLED", False):
139 from aspects.netconfigaspects import apply_netconfig_aspect
140 apply_netconfig_aspect()
142 # Enable Caching. Only for GetSlivers for the moment.
143 # TODO: we may consider to do this in an aspect like the ones above.
145 if self.config.PLC_GETSLIVERS_CACHE:
146 getslivers_cache = True
147 except AttributeError:
148 getslivers_cache = False
151 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
152 from cache_utils.decorators import cached
153 from PLC.Methods.GetSlivers import GetSlivers
156 def cacheable_call(cls, auth, node_id_or_hostname):
157 return cls.raw_call(auth, node_id_or_hostname)
159 GetSlivers.call = cacheable_call
163 def callable(self, method):
165 Return a new instance of the specified method.
169 if method not in self.all_methods:
170 raise PLCInvalidAPIMethod, method
172 # Get new instance of method
174 classname = method.split(".")[-1]
175 if method in self.native_methods:
176 fullpath="PLC.Methods." + method
178 fullpath=self.other_methods_map[method]
179 module = __import__(fullpath, globals(), locals(), [classname])
180 return getattr(module, classname)(self)
181 except ImportError, AttributeError:
182 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
184 def call(self, source, method, *args):
186 Call the named method from the specified source with the
190 function = self.callable(method)
191 function.source = source
192 return function(*args)
194 def handle(self, source, data):
196 Handle an XML-RPC or SOAP request from the specified source.
199 # Parse request into method name and arguments
201 interface = xmlrpclib
202 (args, method) = xmlrpclib.loads(data)
203 methodresponse = True
205 if SOAPpy is not None:
207 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
210 # XXX Support named arguments
215 result = self.call(source, method, *args)
216 except PLCFault, fault:
217 # Handle expected faults
218 if interface == xmlrpclib:
220 methodresponse = None
221 elif interface == SOAPpy:
222 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
223 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
226 if interface == xmlrpclib:
227 if not isinstance(result, PLCFault):
229 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
230 elif interface == SOAPpy:
231 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
235 def handle_json(self, source, data):
237 Handle a JSON request
239 method, args = json.loads(data)
241 result = self.call(source, method, *args)
245 return json.dumps(result)