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