Merge Master in geni-v3 conflict resolution
[sfa.git] / sfa / server / sfaapi.py
1 import os, os.path
2 import datetime
3
4 from sfa.util.faults import SfaFault, SfaAPIError, RecordNotFound
5 from sfa.util.genicode import GENICODE
6 from sfa.util.config import Config
7 from sfa.util.cache import Cache
8
9 from sfa.trust.auth import Auth
10 from sfa.trust.certificate import Keypair, Certificate
11 from sfa.trust.credential import Credential
12 from sfa.trust.rights import determine_rights
13 from sfa.util.version import version_core
14 from sfa.server.xmlrpcapi import XmlrpcApi
15 from sfa.client.return_value import ReturnValue
16
17
18 ####################
19 class SfaApi (XmlrpcApi): 
20     
21     """
22     An SfaApi instance is a basic xmlrcp service
23     augmented with the local cryptographic material and hrn
24
25     It also has the notion of its own interface (a string describing
26     whether we run a registry, aggregate or slicemgr) and has 
27     the notion of neighbour sfa services as defined 
28     in /etc/sfa/{aggregates,registries}.xml
29
30     Finally it contains a cache instance
31
32     It gets augmented by the generic layer with 
33     (*) an instance of manager (actually a manager module for now)
34     (*) which in turn holds an instance of a testbed driver
35     For convenience api.manager.driver == api.driver
36     """
37
38     def __init__ (self, encoding="utf-8", methods='sfa.methods', 
39                   config = "/etc/sfa/sfa_config", 
40                   peer_cert = None, interface = None, 
41                   key_file = None, cert_file = None, cache = None):
42         
43         XmlrpcApi.__init__ (self, encoding)
44         
45         # we may be just be documenting the API
46         if config is None:
47             return
48         # Load configuration
49         self.config = Config(config)
50         self.credential = None
51         self.auth = Auth(peer_cert)
52         self.interface = interface
53         self.hrn = self.config.SFA_INTERFACE_HRN
54         self.key_file = key_file
55         self.key = Keypair(filename=self.key_file)
56         self.cert_file = cert_file
57         self.cert = Certificate(filename=self.cert_file)
58         self.cache = cache
59         if self.cache is None:
60             self.cache = Cache()
61
62         # load registries
63         from sfa.server.registry import Registries
64         self.registries = Registries() 
65
66         # load aggregates
67         from sfa.server.aggregate import Aggregates
68         self.aggregates = Aggregates()
69         
70         # filled later on by generic/Generic
71         self.manager=None
72
73     def server_proxy(self, interface, cred, timeout=30):
74         """
75         Returns a connection to the specified interface. Use the specified
76         credential to determine the caller and look for the caller's key/cert 
77         in the registry hierarchy cache. 
78         """       
79         from sfa.trust.hierarchy import Hierarchy
80         if not isinstance(cred, Credential):
81             cred_obj = Credential(string=cred)
82         else:
83             cred_obj = cred
84         caller_gid = cred_obj.get_gid_caller()
85         hierarchy = Hierarchy()
86         auth_info = hierarchy.get_auth_info(caller_gid.get_hrn())
87         key_file = auth_info.get_privkey_filename()
88         cert_file = auth_info.get_gid_filename()
89         server = interface.server_proxy(key_file, cert_file, timeout)
90         return server
91                
92         
93     def getCredential(self, minimumExpiration=0):
94         """
95         Return a valid credential for this interface.
96         """
97         type = 'authority'
98         path = self.config.SFA_DATA_DIR
99         filename = ".".join([self.interface, self.hrn, type, "cred"])
100         cred_filename = os.path.join(path,filename)
101         cred = None
102         if os.path.isfile(cred_filename):
103             cred = Credential(filename = cred_filename)
104             # make sure cred isnt expired
105             if not cred.get_expiration or \
106                datetime.datetime.utcnow() + datetime.timedelta(seconds=minimumExpiration) < cred.get_expiration():
107                 return cred.save_to_string(save_parents=True)
108
109         # get a new credential
110         if self.interface in ['registry']:
111             cred =  self._getCredentialRaw()
112         else:
113             cred =  self._getCredential()
114         cred.save_to_file(cred_filename, save_parents=True)
115
116         return cred.save_to_string(save_parents=True)
117
118
119     def getDelegatedCredential(self, creds):
120         """
121         Attempt to find a credential delegated to us in
122         the specified list of creds.
123         """
124         from sfa.trust.hierarchy import Hierarchy
125         if creds and not isinstance(creds, list): 
126             creds = [creds]
127         hierarchy = Hierarchy()
128                 
129         delegated_cred = None
130         for cred in creds:
131             if hierarchy.auth_exists(Credential(cred=cred).get_gid_caller().get_hrn()):
132                 delegated_cred = cred
133                 break
134         return delegated_cred
135  
136     def _getCredential(self):
137         """ 
138         Get our credential from a remote registry 
139         """
140         from sfa.server.registry import Registries
141         registries = Registries()
142         registry = registries.server_proxy(self.hrn, self.key_file, self.cert_file)
143         cert_string=self.cert.save_to_string(save_parents=True)
144         # get self credential
145         self_cred = registry.GetSelfCredential(cert_string, self.hrn, 'authority')
146         # get credential
147         cred = registry.GetCredential(self_cred, self.hrn, 'authority')
148         return Credential(string=cred)
149
150     def _getCredentialRaw(self):
151         """
152         Get our current credential directly from the local registry.
153         """
154
155         hrn = self.hrn
156         auth_hrn = self.auth.get_authority(hrn)
157     
158         # is this a root or sub authority
159         if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN:
160             auth_hrn = hrn
161         auth_info = self.auth.get_auth_info(auth_hrn)
162         from sfa.storage.alchemy import dbsession
163         from sfa.storage.model import RegRecord
164         record = dbsession.query(RegRecord).filter_by(type='authority+sa', hrn=hrn).first()
165         if not record:
166             raise RecordNotFound(hrn)
167         type = record.type
168         object_gid = record.get_gid_object()
169         new_cred = Credential(subject = object_gid.get_subject())
170         new_cred.set_gid_caller(object_gid)
171         new_cred.set_gid_object(object_gid)
172         new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
173         
174         r1 = determine_rights(type, hrn)
175         new_cred.set_privileges(r1)
176         new_cred.encode()
177         new_cred.sign()
178
179         return new_cred
180    
181     def loadCredential (self):
182         """
183         Attempt to load credential from file if it exists. If it doesnt get
184         credential from registry.
185         """
186
187         # see if this file exists
188         # XX This is really the aggregate's credential. Using this is easier than getting
189         # the registry's credential from iteslf (ssl errors).
190         filename = self.interface + self.hrn + ".ma.cred"
191         ma_cred_path = os.path.join(self.config.SFA_DATA_DIR,filename)
192         try:
193             self.credential = Credential(filename = ma_cred_path)
194         except IOError:
195             self.credential = self.getCredentialFromRegistry()
196
197     def get_cached_server_version(self, server):
198         cache_key = server.url + "-version"
199         server_version = None
200         if self.cache:
201             server_version = self.cache.get(cache_key)
202         if not server_version:
203             result = server.GetVersion()
204             server_version = ReturnValue.get_value(result)
205             # cache version for 24 hours
206             self.cache.add(cache_key, server_version, ttl= 60*60*24)
207         return server_version
208
209
210     def get_geni_code(self, result):
211         code = {
212             'geni_code': GENICODE.SUCCESS, 
213             'am_type': 'sfa',
214         }
215         if isinstance(result, SfaFault):
216             code['geni_code'] = result.faultCode
217             code['am_code'] = result.faultCode                        
218                 
219         return code
220
221     def get_geni_value(self, result):
222         value = result
223         if isinstance(result, SfaFault):
224             value = ""
225         return value
226
227     def get_geni_output(self, result):
228         output = ""
229         if isinstance(result, SfaFault):
230             output = result.faultString 
231         return output
232
233     def prepare_response_am(self, result):
234         version = version_core() 
235         response = {
236             'geni_api': 3,              
237             'code': self.get_geni_code(result),
238             'value': self.get_geni_value(result),
239             'output': self.get_geni_output(result),
240         }
241         return response
242     
243     def prepare_response(self, result, method=""):
244         """
245         Converts the specified result into a standard GENI compliant 
246         response  
247         """
248         # as of dec 13 2011 we only support API v2
249         if self.interface.lower() in ['aggregate', 'slicemgr']: 
250             result = self.prepare_response_am(result)
251         return XmlrpcApi.prepare_response(self, result, method)
252