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 # Try to use jsonlib before using simpljson. This is a hack to get around
19 # the fact that the version of simplejson avaialble for f8 is slightly
20 # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib
21 # pacakge available for f8, so this has to be installed manually and
22 # is not expected to always be available. Remove this once we move away
23 # from f8 based MyPLC's
29 # See "2.2 Characters" in the XML specification:
31 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
33 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
35 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
36 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
38 def xmlrpclib_escape(s, replace = string.replace):
40 xmlrpclib does not handle invalid 7-bit control characters. This
41 function augments xmlrpclib.escape, which by default only replaces
42 '&', '<', and '>' with entities.
45 # This is the standard xmlrpclib.escape function
46 s = replace(s, "&", "&")
47 s = replace(s, "<", "<")
48 s = replace(s, ">", ">",)
50 # Replace invalid 7-bit control characters with '?'
51 return s.translate(xml_escape_table)
53 def xmlrpclib_dump(self, value, write):
55 xmlrpclib cannot marshal instances of subclasses of built-in
56 types. This function overrides xmlrpclib.Marshaller.__dump so that
57 any value that is an instance of one of its acceptable types is
58 marshalled as that type.
60 xmlrpclib also cannot handle invalid 7-bit control characters. See
64 # Use our escape function
65 args = [self, value, write]
66 if isinstance(value, (str, unicode)):
67 args.append(xmlrpclib_escape)
70 # Try for an exact match first
71 f = self.dispatch[type(value)]
73 # Try for an isinstance() match
74 for Type, f in self.dispatch.iteritems():
75 if isinstance(value, Type):
78 raise TypeError, "cannot marshal %s objects" % type(value)
82 # You can't hide from me!
83 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
85 # SOAP support is optional
88 from SOAPpy.Parser import parseSOAPRPC
89 from SOAPpy.Types import faultType
90 from SOAPpy.NS import NS
91 from SOAPpy.SOAPBuilder import buildSOAP
95 from PLC.Config import Config
96 from PLC.Faults import *
100 def import_deep(name):
101 mod = __import__(name)
102 components = name.split('.')
103 for comp in components[1:]:
104 mod = getattr(mod, comp)
109 # flat list of method names
110 native_methods = PLC.Methods.native_methods
112 # other_methods_map : dict {methodname: fullpath}
113 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
115 for subdir in [ 'Accessors' ]:
117 # scan e.g. PLC.Accessors.__all__
118 pkg = __import__(path).__dict__[subdir]
119 for modulename in getattr(pkg,"__all__"):
120 fullpath=path+"."+modulename
121 for method in getattr(import_deep(fullpath),"methods"):
122 other_methods_map[method] = fullpath
124 all_methods = native_methods + other_methods_map.keys()
126 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
127 self.encoding = encoding
129 # Better just be documenting the API
134 self.config = Config(config)
136 # Initialize database connection
137 if self.config.PLC_DB_TYPE == "postgresql":
138 from PLC.PostgreSQL import PostgreSQL
139 self.db = PostgreSQL(self)
141 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
143 # Aspects modify the API by injecting code before, after or
144 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
146 # As of now we only have aspects for OMF integration, that's
147 # why we enable aspects only if PLC_OMF is set to true.
148 if self.config.PLC_OMF_ENABLED:
149 from aspects import apply_omf_aspect
152 if self.config.PLC_RATELIMIT_ENABLED:
153 from aspects import apply_ratelimit_aspect
154 apply_ratelimit_aspect()
157 # Enable Caching. Only for GetSlivers for the moment.
158 # TODO: we may consider to do this in an aspect like the ones above.
160 if self.config.PLC_GETSLIVERS_CACHE:
161 getslivers_cache = true
162 except AttributeError:
163 getslivers_cache = false
166 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
167 from cache_utils.decorators import cached
168 from PLC.Methods.GetSlivers import GetSlivers
171 def cacheable_call(cls, auth, node_id_or_hostname):
172 return cls.raw_call(auth, node_id_or_hostname)
174 GetSlivers.call = cacheable_call
178 def callable(self, method):
180 Return a new instance of the specified method.
184 if method not in self.all_methods:
185 raise PLCInvalidAPIMethod, method
187 # Get new instance of method
189 classname = method.split(".")[-1]
190 if method in self.native_methods:
191 fullpath="PLC.Methods." + method
193 fullpath=self.other_methods_map[method]
194 module = __import__(fullpath, globals(), locals(), [classname])
195 return getattr(module, classname)(self)
196 except ImportError, AttributeError:
197 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
199 def call(self, source, method, *args):
201 Call the named method from the specified source with the
205 function = self.callable(method)
206 function.source = source
207 return function(*args)
209 def handle(self, source, data):
211 Handle an XML-RPC or SOAP request from the specified source.
214 # Parse request into method name and arguments
216 interface = xmlrpclib
217 (args, method) = xmlrpclib.loads(data)
218 methodresponse = True
220 if SOAPpy is not None:
222 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
225 # XXX Support named arguments
230 result = self.call(source, method, *args)
231 except PLCFault, fault:
232 # Handle expected faults
233 if interface == xmlrpclib:
235 methodresponse = None
236 elif interface == SOAPpy:
237 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
238 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
241 if interface == xmlrpclib:
242 if not isinstance(result, PLCFault):
244 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
245 elif interface == SOAPpy:
246 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
250 def handle_json(self, source, data):
252 Handle a JSON request
254 method, args = json.loads(data)
256 result = self.call(source, method, *args)
260 return json.dumps(result)