2 # SFA XML-RPC and SOAP interfaces
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
15 # this is wrong all right, but temporary
16 from sfa.managers.import_manager import import_manager
18 # See "2.2 Characters" in the XML specification:
20 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
22 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
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))
27 def xmlrpclib_escape(s, replace = string.replace):
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.
34 # This is the standard xmlrpclib.escape function
35 s = replace(s, "&", "&")
36 s = replace(s, "<", "<")
37 s = replace(s, ">", ">",)
39 # Replace invalid 7-bit control characters with '?'
40 return s.translate(xml_escape_table)
42 def xmlrpclib_dump(self, value, write):
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.
49 xmlrpclib also cannot handle invalid 7-bit control characters. See
53 # Use our escape function
54 args = [self, value, write]
55 if isinstance(value, (str, unicode)):
56 args.append(xmlrpclib_escape)
59 # Try for an exact match first
60 f = self.dispatch[type(value)]
63 # Try for an isinstance() match
64 for Type, f in self.dispatch.iteritems():
65 if isinstance(value, Type):
68 raise TypeError, "cannot marshal %s objects" % type(value)
72 # You can't hide from me!
73 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
75 # SOAP support is optional
78 from SOAPpy.Parser import parseSOAPRPC
79 from SOAPpy.Types import faultType
80 from SOAPpy.NS import NS
81 from SOAPpy.SOAPBuilder import buildSOAP
86 def import_deep(name):
87 mod = __import__(name)
88 components = name.split('.')
89 for comp in components[1:]:
90 mod = getattr(mod, comp)
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
104 def __init__(self, manager, interface):
105 self.manager = manager
106 self.interface = interface
108 def __getattr__(self, method):
109 if not hasattr(self.manager, method):
110 raise SfaNotImplemented(method, self.interface)
111 return getattr(self.manager, method)
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):
121 self.encoding = encoding
123 # flat list of method names
124 self.methods_module = methods_module = __import__(methods, fromlist=[methods])
125 self.methods = methods_module.all
127 # Better just be documenting the API
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)
140 if self.cache is None:
142 self.credential = None
144 self.time_format = "%Y-%m-%d %H:%M:%S"
148 from sfa.server.registry import Registries
149 self.registries = Registries()
152 from sfa.server.aggregate import Aggregates
153 self.aggregates = Aggregates()
156 def get_interface_manager(self, manager_base = 'sfa.managers'):
158 Returns the appropriate manager module for this interface.
159 Modules are usually found in sfa/managers/
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)
171 raise SfaAPIError("No manager for interface: %s" % self.interface)
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)
179 def callable(self, method):
181 Return a new instance of the specified method.
184 if method not in self.methods:
185 raise SfaInvalidAPIMethod, method
187 # Get new instance of method
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
196 def call(self, source, method, *args):
198 Call the named method from the specified source with the
201 function = self.callable(method)
202 function.source = source
204 return function(*args)
207 def handle(self, source, data, method_map):
209 Handle an XML-RPC or SOAP request from the specified source.
211 # Parse request into method name and arguments
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
221 if SOAPpy is not None:
222 self.protocol = 'soap'
224 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
227 # XXX Support named arguments
232 result = self.call(source, method, *args)
233 except SfaFault, fault:
235 except Exception, fault:
236 logger.log_exc("BaseAPI.handle has caught Exception")
237 result = SfaAPIError(fault)
241 response = self.prepare_response(result, method)
244 def prepare_response(self, result, method=""):
246 convert result to a valid xmlrpc or soap response
249 if self.protocol == 'xmlrpclib':
250 if not isinstance(result, SfaFault):
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))
258 response = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
260 if isinstance(result, Exception):
265 def get_cached_server_version(self, server):
266 cache_key = server.url + "-version"
267 server_version = None
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