initial checkin of base api class
[sfa.git] / sfa / util / api.py
1 #
2 # Geniwrapper XML-RPC and SOAP interfaces
3 #
4 ### $Id: api.py 15596 2009-10-31 21:42:05Z anil $
5 ### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/api.py $
6 #
7
8 import sys
9 import os
10 import traceback
11 import string
12 import xmlrpclib
13
14 from sfa.trust.auth import Auth
15 from sfa.util.config import *
16 from sfa.util.faults import *
17 from sfa.util.debug import *
18 from sfa.trust.credential import *
19 from sfa.trust.certificate import *
20 from sfa.util.misc import *
21 from sfa.util.sfalogging import *
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 BaseAPI:
99
100     def __init__(self, config = "/etc/sfa/sfa_config", encoding = "utf-8", methods='sfa.methods',
101                  peer_cert = None, interface = None, key_file = None, cert_file = None):
102
103         self.encoding = encoding
104         
105         # flat list of method names
106         methods_module = __import__(methods)
107         self.methods = methods_module.methods.all
108
109         # Better just be documenting the API
110         if config is None:
111             return
112         
113         # Load configuration
114         self.config = Config(config)
115         self.auth = Auth(peer_cert)
116         self.interface = interface
117         self.key_file = key_file
118         self.key = Keypair(filename=self.key_file)
119         self.cert_file = cert_file
120         self.cert = Certificate(filename=self.cert_file)
121         self.credential = None
122         
123         # Initialize the PLC shell only if SFA wraps a myPLC
124         rspec_type = self.config.get_aggregate_rspec_type()
125         if (rspec_type == 'pl' or rspec_type == 'vini'):
126             self.plshell = self.getPLCShell()
127             self.plshell_version = self.getPLCShellVersion()
128
129         self.hrn = self.config.SFA_INTERFACE_HRN
130         self.time_format = "%Y-%m-%d %H:%M:%S"
131         self.logger=get_sfa_logger()
132
133
134     def callable(self, method):
135         """
136         Return a new instance of the specified method.
137         """
138         # Look up method
139         if method not in self.methods:
140             raise GeniInvalidAPIMethod, method
141         
142         # Get new instance of method
143         try:
144             classname = method.split(".")[-1]
145             module = __import__("sfa.methods." + method, globals(), locals(), [classname])
146             callablemethod = getattr(module, classname)(self)
147             return getattr(module, classname)(self)
148         except ImportError, AttributeError:
149             raise GeniInvalidAPIMethod, method
150
151     def call(self, source, method, *args):
152         """
153         Call the named method from the specified source with the
154         specified arguments.
155         """
156         function = self.callable(method)
157         function.source = source
158         return function(*args)
159
160     def handle(self, source, data):
161         """
162         Handle an XML-RPC or SOAP request from the specified source.
163         """
164         # Parse request into method name and arguments
165         try:
166             interface = xmlrpclib
167             (args, method) = xmlrpclib.loads(data)
168             methodresponse = True
169         except Exception, e:
170             if SOAPpy is not None:
171                 interface = SOAPpy
172                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
173                 method = r._name
174                 args = r._aslist()
175                 # XXX Support named arguments
176             else:
177                 raise e
178
179         try:
180             result = self.call(source, method, *args)
181         except Exception, fault:
182             traceback.print_exc(file = log)
183             # Handle expected faults
184             if interface == xmlrpclib:
185                 result = fault
186                 methodresponse = None
187             elif interface == SOAPpy:
188                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
189                 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
190             else:
191                 raise
192
193         # Return result
194         if interface == xmlrpclib:
195             if not isinstance(result, GeniFault):
196                 result = (result,)
197
198             data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
199         elif interface == SOAPpy:
200             data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
201
202         return data
203