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
18 # See "2.2 Characters" in the XML specification:
20 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
22 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
24 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
25 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
27 def xmlrpclib_escape(s, replace = string.replace):
29 xmlrpclib does not handle invalid 7-bit control characters. This
30 function augments xmlrpclib.escape, which by default only replaces
31 '&', '<', and '>' with entities.
34 # This is the standard xmlrpclib.escape function
35 s = replace(s, "&", "&")
36 s = replace(s, "<", "<")
37 s = replace(s, ">", ">",)
39 # Replace invalid 7-bit control characters with '?'
40 return s.translate(xml_escape_table)
42 def xmlrpclib_dump(self, value, write):
44 xmlrpclib cannot marshal instances of subclasses of built-in
45 types. This function overrides xmlrpclib.Marshaller.__dump so that
46 any value that is an instance of one of its acceptable types is
47 marshalled as that type.
49 xmlrpclib also cannot handle invalid 7-bit control characters. See
53 # Use our escape function
54 args = [self, value, write]
55 if isinstance(value, (str, unicode)):
56 args.append(xmlrpclib_escape)
59 # Try for an exact match first
60 f = self.dispatch[type(value)]
62 # Try for an isinstance() match
63 for Type, f in self.dispatch.iteritems():
64 if isinstance(value, Type):
67 raise TypeError, "cannot marshal %s objects" % type(value)
71 # You can't hide from me!
72 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
74 # SOAP support is optional
77 from SOAPpy.Parser import parseSOAPRPC
78 from SOAPpy.Types import faultType
79 from SOAPpy.NS import NS
80 from SOAPpy.SOAPBuilder import buildSOAP
84 from PLC.Config import Config
85 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
101 # other_methods_map : dict {methodname: fullpath}
102 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
104 for subdir in [ 'Accessors' ]:
106 # scan e.g. PLC.Accessors.__all__
107 pkg = __import__(path).__dict__[subdir]
108 for modulename in getattr(pkg,"__all__"):
109 fullpath=path+"."+modulename
110 for method in getattr(import_deep(fullpath),"methods"):
111 other_methods_map[method] = fullpath
113 all_methods = native_methods + other_methods_map.keys()
115 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
116 self.encoding = encoding
118 # Better just be documenting the API
123 self.config = Config(config)
125 # Initialize database connection
126 if self.config.PLC_DB_TYPE == "postgresql":
127 from PLC.PostgreSQL import PostgreSQL
128 self.db = PostgreSQL(self)
130 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
132 # Aspects modify the API injecting code before/after method
133 # calls. As of now we only have aspects for OMF integration,
134 # that's why we enable aspects only if PLC_OMF is set to true.
135 if self.config.PLC_OMF_ENABLED:
136 from aspects import apply_aspects; apply_aspects()
139 def callable(self, method):
141 Return a new instance of the specified method.
145 if method not in self.all_methods:
146 raise PLCInvalidAPIMethod, method
148 # Get new instance of method
150 classname = method.split(".")[-1]
151 if method in self.native_methods:
152 fullpath="PLC.Methods." + method
154 fullpath=self.other_methods_map[method]
155 module = __import__(fullpath, globals(), locals(), [classname])
156 return getattr(module, classname)(self)
157 except ImportError, AttributeError:
158 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
160 def call(self, source, method, *args):
162 Call the named method from the specified source with the
166 function = self.callable(method)
167 function.source = source
168 return function(*args)
170 def handle(self, source, data):
172 Handle an XML-RPC or SOAP request from the specified source.
175 # Parse request into method name and arguments
177 interface = xmlrpclib
178 (args, method) = xmlrpclib.loads(data)
179 methodresponse = True
181 if SOAPpy is not None:
183 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
186 # XXX Support named arguments
191 result = self.call(source, method, *args)
192 except PLCFault, fault:
193 # Handle expected faults
194 if interface == xmlrpclib:
196 methodresponse = None
197 elif interface == SOAPpy:
198 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
199 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
202 if interface == xmlrpclib:
203 if not isinstance(result, PLCFault):
205 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
206 elif interface == SOAPpy:
207 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)