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 *
89 def import_deep(name):
90 mod = __import__(name)
91 components = name.split('.')
92 for comp in components[1:]:
93 mod = getattr(mod, comp)
98 # flat list of method names
99 native_methods = PLC.Methods.native_methods
100 legacy_methods = PLC.Legacy.native_methods
102 # other_methods_map : dict {methodname: fullpath}
103 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
105 for subdir in [ 'Accessors' ]:
107 # scan e.g. PLC.Accessors.__all__
108 pkg = __import__(path).__dict__[subdir]
109 for modulename in getattr(pkg,"__all__"):
110 fullpath=path+"."+modulename
111 for method in getattr(import_deep(fullpath),"methods"):
112 other_methods_map[method] = fullpath
114 all_methods = native_methods + legacy_methods + other_methods_map.keys()
116 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
117 self.encoding = encoding
119 # Better just be documenting the API
124 self.config = Config(config)
126 # Initialize database connection
127 if self.config.PLC_DB_TYPE == "postgresql":
128 from PLC.PostgreSQL import PostgreSQL
129 self.db = PostgreSQL(self)
132 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
134 def callable(self, method):
136 Return a new instance of the specified method.
140 if method not in self.all_methods:
141 raise PLCInvalidAPIMethod, method
143 # Get new instance of method
145 classname = method.split(".")[-1]
146 if method in self.native_methods:
147 fullpath="PLC.Methods." + method
148 elif method in self.legacy_methods:
149 fullpath="PLC.Legacy." + method
151 fullpath=self.other_methods_map[method]
152 module = __import__(fullpath, globals(), locals(), [classname])
153 return getattr(module, classname)(self)
154 except ImportError, AttributeError:
155 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
157 def call(self, source, method, *args):
159 Call the named method from the specified source with the
163 function = self.callable(method)
164 function.source = source
165 return function(*args)
167 def handle(self, source, data):
169 Handle an XML-RPC or SOAP request from the specified source.
172 # Parse request into method name and arguments
174 interface = xmlrpclib
175 (args, method) = xmlrpclib.loads(data)
176 methodresponse = True
178 if SOAPpy is not None:
180 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
183 # XXX Support named arguments
188 result = self.call(source, method, *args)
189 except PLCFault, fault:
190 # Handle expected faults
191 if interface == xmlrpclib:
193 methodresponse = None
194 elif interface == SOAPpy:
195 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
196 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
199 if interface == xmlrpclib:
200 if not isinstance(result, PLCFault):
202 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
203 elif interface == SOAPpy:
204 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)