39d6538254d2fa04ba85a52d29d6fcf38ec61351
[sfa.git] / sfa / util / api.py
1 #
2 # SFA XML-RPC and SOAP interfaces
3 #
4
5 import sys
6 import os
7 import traceback
8 import string
9 import xmlrpclib
10
11 from sfa.util.faults import *
12 from sfa.util.config import *
13 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
14 from sfa.util.sfalogging import logger
15 from sfa.trust.auth import Auth
16 from sfa.util.cache import Cache
17 from sfa.trust.credential import *
18 from sfa.trust.certificate import *
19
20 # this is wrong all right, but temporary 
21 from sfa.managers.import_manager import import_manager
22
23 # See "2.2 Characters" in the XML specification:
24 #
25 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
26 # avoiding
27 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
28
29 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
30 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
31
32 def xmlrpclib_escape(s, replace = string.replace):
33     """
34     xmlrpclib does not handle invalid 7-bit control characters. This
35     function augments xmlrpclib.escape, which by default only replaces
36     '&', '<', and '>' with entities.
37     """
38
39     # This is the standard xmlrpclib.escape function
40     s = replace(s, "&", "&amp;")
41     s = replace(s, "<", "&lt;")
42     s = replace(s, ">", "&gt;",)
43
44     # Replace invalid 7-bit control characters with '?'
45     return s.translate(xml_escape_table)
46
47 def xmlrpclib_dump(self, value, write):
48     """
49     xmlrpclib cannot marshal instances of subclasses of built-in
50     types. This function overrides xmlrpclib.Marshaller.__dump so that
51     any value that is an instance of one of its acceptable types is
52     marshalled as that type.
53
54     xmlrpclib also cannot handle invalid 7-bit control characters. See
55     above.
56     """
57
58     # Use our escape function
59     args = [self, value, write]
60     if isinstance(value, (str, unicode)):
61         args.append(xmlrpclib_escape)
62
63     try:
64         # Try for an exact match first
65         f = self.dispatch[type(value)]
66     except KeyError:
67         raise
68         # Try for an isinstance() match
69         for Type, f in self.dispatch.iteritems():
70             if isinstance(value, Type):
71                 f(*args)
72                 return
73         raise TypeError, "cannot marshal %s objects" % type(value)
74     else:
75         f(*args)
76
77 # You can't hide from me!
78 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
79
80 # SOAP support is optional
81 try:
82     import SOAPpy
83     from SOAPpy.Parser import parseSOAPRPC
84     from SOAPpy.Types import faultType
85     from SOAPpy.NS import NS
86     from SOAPpy.SOAPBuilder import buildSOAP
87 except ImportError:
88     SOAPpy = None
89
90
91 def import_deep(name):
92     mod = __import__(name)
93     components = name.split('.')
94     for comp in components[1:]:
95         mod = getattr(mod, comp)
96     return mod
97
98 class ManagerWrapper:
99     """
100     This class acts as a wrapper around an SFA interface manager module, but
101     can be used with any python module. The purpose of this class is raise a 
102     SfaNotImplemented exception if someone attempts to use an attribute 
103     (could be a callable) thats not available in the library by checking the
104     library using hasattr. This helps to communicate better errors messages 
105     to the users and developers in the event that a specifiec operation 
106     is not implemented by a libarary and will generally be more helpful than
107     the standard AttributeError         
108     """
109     def __init__(self, manager, interface):
110         self.manager = manager
111         self.interface = interface
112         
113     def __getattr__(self, method):
114         if not hasattr(self.manager, method):
115             raise SfaNotImplemented(method, self.interface)
116         return getattr(self.manager, method)
117         
118 class BaseAPI:
119
120     protocol = None
121   
122     def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", 
123                  methods='sfa.methods', peer_cert = None, interface = None, 
124                  key_file = None, cert_file = None, cache = None):
125
126         self.encoding = encoding
127         
128         # flat list of method names
129         self.methods_module = methods_module = __import__(methods, fromlist=[methods])
130         self.methods = methods_module.all
131
132         # Better just be documenting the API
133         if config is None:
134             return
135         # Load configuration
136         self.config = Config(config)
137         self.auth = Auth(peer_cert)
138         self.hrn = self.config.SFA_INTERFACE_HRN
139         self.interface = interface
140         self.key_file = key_file
141         self.key = Keypair(filename=self.key_file)
142         self.cert_file = cert_file
143         self.cert = Certificate(filename=self.cert_file)
144         self.cache = cache
145         if self.cache is None:
146             self.cache = Cache()
147         self.credential = None
148         self.source = None 
149         self.time_format = "%Y-%m-%d %H:%M:%S"
150         self.logger = logger
151  
152         # load registries
153         from sfa.server.registry import Registries
154         self.registries = Registries() 
155
156         # load aggregates
157         from sfa.server.aggregate import Aggregates
158         self.aggregates = Aggregates()
159
160
161     def get_interface_manager(self, manager_base = 'sfa.managers'):
162         """
163         Returns the appropriate manager module for this interface.
164         Modules are usually found in sfa/managers/
165         """
166         manager=None
167         if self.interface in ['registry']:
168             manager=import_manager ("registry",  self.config.SFA_REGISTRY_TYPE)
169         elif self.interface in ['aggregate']:
170             manager=import_manager ("aggregate", self.config.SFA_AGGREGATE_TYPE)
171         elif self.interface in ['slicemgr', 'sm']:
172             manager=import_manager ("slice",     self.config.SFA_SM_TYPE)
173         elif self.interface in ['component', 'cm']:
174             manager=import_manager ("component", self.config.SFA_CM_TYPE)
175         if not manager:
176             raise SfaAPIError("No manager for interface: %s" % self.interface)  
177             
178         # this isnt necessary but will help to produce better error messages
179         # if someone tries to access an operation this manager doesn't implement  
180         manager = ManagerWrapper(manager, self.interface)
181
182         return manager
183
184     def callable(self, method):
185         """
186         Return a new instance of the specified method.
187         """
188         # Look up method
189         if method not in self.methods:
190             raise SfaInvalidAPIMethod, method
191         
192         # Get new instance of method
193         try:
194             classname = method.split(".")[-1]
195             module = __import__(self.methods_module.__name__ + "." + method, globals(), locals(), [classname])
196             callablemethod = getattr(module, classname)(self)
197             return getattr(module, classname)(self)
198         except ImportError, AttributeError:
199             raise SfaInvalidAPIMethod, method
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         function = self.callable(method)
207         function.source = source
208         self.source = source
209         return function(*args)
210
211     
212     def handle(self, source, data, method_map):
213         """
214         Handle an XML-RPC or SOAP request from the specified source.
215         """
216         # Parse request into method name and arguments
217         try:
218             interface = xmlrpclib
219             self.protocol = 'xmlrpclib'
220             (args, method) = xmlrpclib.loads(data)
221             if method_map.has_key(method):
222                 method = method_map[method]
223             methodresponse = True
224             
225         except Exception, e:
226             if SOAPpy is not None:
227                 self.protocol = 'soap'
228                 interface = SOAPpy
229                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
230                 method = r._name
231                 args = r._aslist()
232                 # XXX Support named arguments
233             else:
234                 raise e
235
236         try:
237             result = self.call(source, method, *args)
238         except SfaFault, fault:
239             result = fault 
240         except Exception, fault:
241             logger.log_exc("BaseAPI.handle has caught Exception")
242             result = SfaAPIError(fault)
243
244
245         # Return result
246         response = self.prepare_response(result, method)
247         return response
248     
249     def prepare_response(self, result, method=""):
250         """
251         convert result to a valid xmlrpc or soap response
252         """   
253  
254         if self.protocol == 'xmlrpclib':
255             if not isinstance(result, SfaFault):
256                 result = (result,)
257             response = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
258         elif self.protocol == 'soap':
259             if isinstance(result, Exception):
260                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
261                 result._setDetail("Fault %d: %s" % (result.faultCode, result.faultString))
262             else:
263                 response = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
264         else:
265             if isinstance(result, Exception):
266                 raise result 
267             
268         return response
269
270     def get_cached_server_version(self, server):
271         cache_key = server.url + "-version"
272         server_version = None
273         if self.cache:
274             server_version = self.cache.get(cache_key)
275         if not server_version:
276             server_version = server.GetVersion()
277             # cache version for 24 hours
278             self.cache.add(cache_key, server_version, ttl= 60*60*24)
279         return server_version