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
8 # $Id: API.py,v 1.7 2006/10/30 16:37:11 mlhuang Exp $
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 *
86 from PLC.sendmail import sendmail
89 methods = PLC.Methods.methods
91 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
92 self.encoding = encoding
94 # Better just be documenting the API
99 self.config = Config(config)
102 self.mailer = sendmail(self.config)
104 # Initialize database connection
105 if self.config.PLC_DB_TYPE == "postgresql":
106 from PLC.PostgreSQL import PostgreSQL
107 self.db = PostgreSQL(self)
110 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
112 def callable(self, method):
114 Return a new instance of the specified method.
118 if method not in self.methods:
119 raise PLCInvalidAPIMethod, method
121 # Get new instance of method
123 classname = method.split(".")[-1]
124 module = __import__("PLC.Methods." + method, globals(), locals(), [classname])
125 return getattr(module, classname)(self)
126 except ImportError, AttributeError:
127 raise PLCInvalidAPIMethod, method
129 def call(self, source, method, *args):
131 Call the named method from the specified source with the
135 function = self.callable(method)
136 function.source = source
137 return function(*args)
139 def handle(self, source, data):
141 Handle an XML-RPC or SOAP request from the specified source.
144 # Parse request into method name and arguments
146 interface = xmlrpclib
147 (args, method) = xmlrpclib.loads(data)
148 methodresponse = True
150 if SOAPpy is not None:
152 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
155 # XXX Support named arguments
160 result = self.call(source, method, *args)
161 except PLCFault, fault:
162 # Handle expected faults
163 if interface == xmlrpclib:
165 methodresponse = None
166 elif interface == SOAPpy:
167 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
168 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
171 if interface == xmlrpclib:
172 if not isinstance(result, PLCFault):
174 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
175 elif interface == SOAPpy:
176 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)