when creating slices trough SFA we're getting
[plcapi.git] / PLC / API.py
1 #
2 # PLCAPI XML-RPC and SOAP interfaces
3 #
4 # Aaron Klingaman <alk@absarokasoft.com>
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 #
7 # Copyright (C) 2004-2006 The Trustees of Princeton University
8 #
9
10 import os
11 import sys
12 import traceback
13 import string
14
15 import xmlrpclib
16 import simplejson
17 # use this one
18 json=simplejson
19 #try:
20 #    # Try to use jsonlib before using simpljson. This is a hack to get around
21 #    # the fact that the version of simplejson available for f8 is slightly 
22 #    # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib 
23 #    # package available for f8, so this has to be installed manually and
24 #    # is not expected to always be available. Remove this once we move away
25 #    # from f8 based MyPLC's         
26 #    import jsonlib
27 #    json = jsonlib
28 #except:
29 #    json = simplejson 
30
31 # See "2.2 Characters" in the XML specification:
32 #
33 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
34 # avoiding
35 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
36
37 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
38 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
39
40 def xmlrpclib_escape(s, replace = string.replace):
41     """
42     xmlrpclib does not handle invalid 7-bit control characters. This
43     function augments xmlrpclib.escape, which by default only replaces
44     '&', '<', and '>' with entities.
45     """
46
47     # This is the standard xmlrpclib.escape function
48     s = replace(s, "&", "&amp;")
49     s = replace(s, "<", "&lt;")
50     s = replace(s, ">", "&gt;",)
51
52     # Replace invalid 7-bit control characters with '?'
53     return s.translate(xml_escape_table)
54
55 def xmlrpclib_dump(self, value, write):
56     """
57     xmlrpclib cannot marshal instances of subclasses of built-in
58     types. This function overrides xmlrpclib.Marshaller.__dump so that
59     any value that is an instance of one of its acceptable types is
60     marshalled as that type.
61
62     xmlrpclib also cannot handle invalid 7-bit control characters. See
63     above.
64     """
65
66     # Use our escape function
67     args = [self, value, write]
68     if isinstance(value, (str, unicode)):
69         args.append(xmlrpclib_escape)
70
71     try:
72         # Try for an exact match first
73         f = self.dispatch[type(value)]
74     except KeyError:
75         # Try for an isinstance() match
76         for Type, f in self.dispatch.iteritems():
77             if isinstance(value, Type):
78                 f(*args)
79                 return
80         raise TypeError, "cannot marshal %s objects" % type(value)
81     else:
82         f(*args)
83
84 # You can't hide from me!
85 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
86
87 # SOAP support is optional
88 try:
89     import SOAPpy
90     from SOAPpy.Parser import parseSOAPRPC
91     from SOAPpy.Types import faultType
92     from SOAPpy.NS import NS
93     from SOAPpy.SOAPBuilder import buildSOAP
94 except ImportError:
95     SOAPpy = None
96
97 from PLC.Config import Config
98 from PLC.Faults import *
99 import PLC.Methods
100 import PLC.Accessors
101
102 def import_deep(name):
103     mod = __import__(name)
104     components = name.split('.')
105     for comp in components[1:]:
106         mod = getattr(mod, comp)
107     return mod
108
109 class PLCAPI:
110
111     # flat list of method names
112     native_methods = PLC.Methods.native_methods
113
114     # other_methods_map : dict {methodname: fullpath}
115     # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
116     other_methods_map={}
117     for subdir in [ 'Accessors' ]:
118         path="PLC."+subdir
119         # scan e.g. PLC.Accessors.__all__
120         pkg = __import__(path).__dict__[subdir]
121         for modulename in getattr(pkg,"__all__"):
122             fullpath=path+"."+modulename
123             for method in getattr(import_deep(fullpath),"methods"):
124                 other_methods_map[method] = fullpath
125
126     all_methods = native_methods + other_methods_map.keys()
127
128     def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
129         self.encoding = encoding
130
131         # Better just be documenting the API
132         if config is None:
133             return
134
135         # Load configuration
136         self.config = Config(config)
137
138         # Initialize database connection
139         if self.config.PLC_DB_TYPE == "postgresql":
140             from PLC.PostgreSQL import PostgreSQL
141             self.db = PostgreSQL(self)
142         else:
143             raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
144
145         # Aspects modify the API by injecting code before, after or
146         # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
147         # 
148         # As of now we only have aspects for OMF integration, that's
149         # why we enable aspects only if PLC_OMF is set to true.
150         if self.config.PLC_OMF_ENABLED:
151             from aspects import apply_omf_aspect
152             apply_omf_aspect()
153         
154         if self.config.PLC_RATELIMIT_ENABLED:
155             from aspects import apply_ratelimit_aspect
156             apply_ratelimit_aspect()
157
158
159         # Enable Caching. Only for GetSlivers for the moment.
160         # TODO: we may consider to do this in an aspect like the ones above.
161         try:
162             if self.config.PLC_GETSLIVERS_CACHE:
163                 getslivers_cache = True
164         except AttributeError:
165             getslivers_cache = False
166
167         if getslivers_cache:
168             os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
169             from cache_utils.decorators import cached
170             from PLC.Methods.GetSlivers import GetSlivers
171
172             @cached(7200)
173             def cacheable_call(cls, auth, node_id_or_hostname):
174                 return cls.raw_call(auth, node_id_or_hostname)
175             
176             GetSlivers.call = cacheable_call
177             
178
179
180     def callable(self, method):
181         """
182         Return a new instance of the specified method.
183         """
184
185         # Look up method
186         if method not in self.all_methods:
187             raise PLCInvalidAPIMethod, method
188
189         # Get new instance of method
190         try:
191             classname = method.split(".")[-1]
192             if method in self.native_methods:
193                 fullpath="PLC.Methods." + method
194             else:
195                 fullpath=self.other_methods_map[method]
196             module = __import__(fullpath, globals(), locals(), [classname])
197             return getattr(module, classname)(self)
198         except ImportError, AttributeError:
199             raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
200
201     def call(self, source, method, *args):
202         """
203         Call the named method from the specified source with the
204         specified arguments.
205         """
206
207         function = self.callable(method)
208         function.source = source
209         return function(*args)
210
211     def handle(self, source, data):
212         """
213         Handle an XML-RPC or SOAP request from the specified source.
214         """
215
216         # Parse request into method name and arguments
217         try:
218             interface = xmlrpclib
219             (args, method) = xmlrpclib.loads(data)
220             methodresponse = True
221         except Exception, e:
222             if SOAPpy is not None:
223                 interface = SOAPpy
224                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
225                 method = r._name
226                 args = r._aslist()
227                 # XXX Support named arguments
228             else:
229                 raise e
230
231         try:
232             result = self.call(source, method, *args)
233         except PLCFault, fault:
234             # Handle expected faults
235             if interface == xmlrpclib:
236                 result = fault
237                 methodresponse = None
238             elif interface == SOAPpy:
239                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
240                 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
241
242         # Return result
243         if interface == xmlrpclib:
244             if not isinstance(result, PLCFault):
245                 result = (result,)
246             data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
247         elif interface == SOAPpy:
248             data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
249
250         return data
251
252     def handle_json(self, source, data):
253         """
254         Handle a JSON request 
255         """
256         method, args = json.loads(data)
257         try:
258             result = self.call(source, method, *args)
259         except Exception, e:
260             result = str(e)
261        
262         return json.dumps(result) 
263         
264