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