added origin_hrn as argument fro getCredential()
[sfa.git] / sfa / plc / api.py
1 #
2 # Geniwrapper 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 from sfa.trust.auth import Auth
14 from sfa.util.config import *
15 from sfa.util.faults import *
16 from sfa.util.debug import *
17 from sfa.trust.rights import *
18 from sfa.trust.credential import *
19 from sfa.trust.certificate import *
20 from sfa.util.misc import *
21 from sfa.util.api import *
22 from sfa.util.nodemanager import NodeManager
23 from sfa.util.sfalogging import *
24
25 class GeniAPI(BaseAPI):
26
27     # flat list of method names
28     import sfa.methods
29     methods = sfa.methods.all
30     
31     def __init__(self, config = "/etc/sfa/sfa_config", encoding = "utf-8", methods='sfa.methods', 
32                  peer_cert = None, interface = None, key_file = None, cert_file = None):
33         BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
34                          interface=interface, key_file=key_file, cert_file=cert_file)
35  
36         self.encoding = encoding
37
38         # Better just be documenting the API
39         if config is None:
40             return
41
42         # Load configuration
43         self.config = Config(config)
44         self.auth = Auth(peer_cert)
45         self.interface = interface
46         self.key_file = key_file
47         self.key = Keypair(filename=self.key_file)
48         self.cert_file = cert_file
49         self.cert = Certificate(filename=self.cert_file)
50         self.credential = None
51         
52         # Initialize the PLC shell only if SFA wraps a myPLC
53         rspec_type = self.config.get_aggregate_rspec_type()
54         if (rspec_type == 'pl' or rspec_type == 'vini'):
55             self.plshell = self.getPLCShell()
56             self.plshell_version = self.getPLCShellVersion()
57
58         self.hrn = self.config.SFA_INTERFACE_HRN
59         self.time_format = "%Y-%m-%d %H:%M:%S"
60         self.logger=get_sfa_logger()
61
62     def getPLCShell(self):
63         self.plauth = {'Username': self.config.SFA_PLC_USER,
64                        'AuthMethod': 'password',
65                        'AuthString': self.config.SFA_PLC_PASSWORD}
66         try:
67             self.plshell_type = 'direct'
68             import PLC.Shell
69             shell = PLC.Shell.Shell(globals = globals())
70             shell.AuthCheck(self.plauth)
71             return shell
72         except ImportError:
73             self.plshell_type = 'xmlrpc' 
74             # connect via xmlrpc
75             url = self.config.SFA_PLC_URL
76             shell = xmlrpclib.Server(url, verbose = 0, allow_none = True)
77             shell.AuthCheck(self.plauth)
78             return shell
79
80     def getPLCShellVersion(self):
81         # We need to figure out what version of PLCAPI we are talking to.
82         # Some calls we need to make later will be different depending on
83         # the api version. 
84         try:
85             # This is probably a bad way to determine api versions
86             # but its easy and will work for now. Lets try to make 
87             # a call that only exists is PLCAPI.4.3. If it fails, we
88             # can assume the api version is 4.2
89             self.plshell.GetTagTypes(self.plauth)
90             return '4.3'
91         except:
92             return '4.2'
93             
94
95     def getCredential(self, origin_hrn=None):
96         if self.interface in ['registry']:
97             return self.getCredentialFromLocalRegistry(origin_hrn)
98         else:
99             return self.getCredentialFromRegistry(origin_hrn)
100     
101     def getCredentialFromRegistry(self, origin_hrn=None):
102         """ 
103         Get our credential from a remote registry using a geniclient connection
104         """
105         type = 'authority'
106         path = self.config.SFA_DATA_DIR
107         filename = ".".join([self.interface, self.hrn, type, "cred"])
108         cred_filename = path + os.sep + filename
109         try:
110             credential = Credential(filename = cred_filename)
111             return credential.save_to_string(save_parents=True)
112         except IOError:
113             from sfa.server.registry import Registries
114             registries = Registries(self)
115             registry = registries[self.hrn]
116             cert_string=self.cert.save_to_string(save_parents=True)
117             # get self credential
118             arg_list = [cert_string,type,self.hrn]
119             request_hash=self.key.compute_hash(arg_list)
120             self_cred = registry.get_self_credential(cert_string, type, self.hrn, request_hash)
121             # get credential
122             arg_list = [self_cred,type,self.hrn,origin_hrn]
123             request_hash=self.key.compute_hash(arg_list)
124             cred = registry.get_credential(self_cred, type, self.hrn, origin_hrn, request_hash)
125             
126             # save cred to file
127             Credential(string=cred).save_to_file(cred_filename, save_parents=True)
128             return cred
129
130     def getCredentialFromLocalRegistry(self, origin_hrn=None):
131         """
132         Get our current credential directly from the local registry.
133         """
134
135         hrn = self.hrn
136         auth_hrn = self.auth.get_authority(hrn)
137     
138         # is this a root or sub authority
139         if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN:
140             auth_hrn = hrn
141         auth_info = self.auth.get_auth_info(auth_hrn)
142         from sfa.util.genitable import GeniTable
143         table = GeniTable()
144         records = table.findObjects(hrn)
145         if not records:
146             raise RecordNotFound
147         record = records[0]
148         type = record['type']
149         object_gid = record.get_gid_object()
150         
151         # get the origin caller's gid (this is the caller's gid by default)
152         if origin_hrn:
153             orgin_records = table.find({'hrn': origin_hrn})
154             if not origin_records:
155                 raise RecordNotFound(origin_hrn)
156             origin_record = origin_records[0]
157             origin_caller_gid_object = GID(string = record['gid'])
158         else:
159             origin_caller_gid_object = object_gid
160
161         new_cred = Credential(subject = object_gid.get_subject())
162         new_cred.set_gid_caller(object_gid)
163         new_cred.set_gid_object(object_gid)
164         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
165         new_cred.set_pubkey(object_gid.get_pubkey())
166         r1 = determine_rights(type, hrn)
167         new_cred.set_privileges(r1)
168
169         auth_kind = "authority,ma,sa"
170
171         new_cred.set_parent(self.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
172
173         new_cred.encode()
174         new_cred.sign()
175
176         return new_cred.save_to_string(save_parents=True)
177    
178
179     def loadCredential (self):
180         """
181         Attempt to load credential from file if it exists. If it doesnt get
182         credential from registry.
183         """
184
185         # see if this file exists
186         # XX This is really the aggregate's credential. Using this is easier than getting
187         # the registry's credential from iteslf (ssl errors).   
188         ma_cred_filename = self.config.SFA_DATA_DIR + os.sep + self.interface + self.hrn + ".ma.cred"
189         try:
190             self.credential = Credential(filename = ma_cred_filename)
191         except IOError:
192             self.credential = self.getCredentialFromRegistry()
193
194     ##
195     # Convert geni fields to PLC fields for use when registering up updating
196     # registry record in the PLC database
197     #
198     # @param type type of record (user, slice, ...)
199     # @param hrn human readable name
200     # @param geni_fields dictionary of geni fields
201     # @param pl_fields dictionary of PLC fields (output)
202
203     def geni_fields_to_pl_fields(self, type, hrn, record):
204
205         def convert_ints(tmpdict, int_fields):
206             for field in int_fields:
207                 if field in tmpdict:
208                     tmpdict[field] = int(tmpdict[field])
209
210         pl_record = {}
211         #for field in record:
212         #    pl_record[field] = record[field]
213  
214         if type == "slice":
215             if not "instantiation" in pl_record:
216                 pl_record["instantiation"] = "plc-instantiated"
217             pl_record["name"] = hrn_to_pl_slicename(hrn)
218             if "url" in record:
219                pl_record["url"] = record["url"]
220             if "description" in record:
221                 pl_record["description"] = record["description"]
222             if "expires" in record:
223                 pl_record["expires"] = int(record["expires"])
224
225         elif type == "node":
226             if not "hostname" in pl_record:
227                 if not "hostname" in record:
228                     raise MissingGeniInfo("hostname")
229                 pl_record["hostname"] = record["hostname"]
230             if not "model" in pl_record:
231                 pl_record["model"] = "geni"
232
233         elif type == "authority":
234             pl_record["login_base"] = hrn_to_pl_login_base(hrn)
235
236             if not "name" in pl_record:
237                 pl_record["name"] = hrn
238
239             if not "abbreviated_name" in pl_record:
240                 pl_record["abbreviated_name"] = hrn
241
242             if not "enabled" in pl_record:
243                 pl_record["enabled"] = True
244
245             if not "is_public" in pl_record:
246                 pl_record["is_public"] = True
247
248         return pl_record
249
250     def fill_record_pl_info(self, record):
251         """
252         Fill in the planetlab specific fields of a Geni record. This
253         involves calling the appropriate PLC method to retrieve the 
254         database record for the object.
255         
256         PLC data is filled into the pl_info field of the record.
257     
258         @param record: record to fill in field (in/out param)     
259         """
260         type = record['type']
261         pointer = record['pointer']
262         auth_hrn = self.hrn
263         login_base = ''
264         # records with pointer==-1 do not have plc info associated with them.
265         # for example, the top level authority records which are
266         # authorities, but not PL "sites"
267         if pointer == -1:
268             record.update({})
269             return
270
271         if (type in ["authority"]):
272             pl_res = self.plshell.GetSites(self.plauth, [pointer])
273         elif (type == "slice"):
274             pl_res = self.plshell.GetSlices(self.plauth, [pointer])
275         elif (type == "user"):
276             pl_res = self.plshell.GetPersons(self.plauth, [pointer])
277         elif (type == "node"):
278             pl_res = self.plshell.GetNodes(self.plauth, [pointer])
279         else:
280             raise UnknownGeniType(type)
281         
282         if not pl_res:
283             raise PlanetLabRecordDoesNotExist(record['hrn'])
284
285         # convert ids to hrns
286         pl_record = pl_res[0]
287         if 'site_id' in pl_record:
288             sites = self.plshell.GetSites(self.plauth, pl_record['site_id'], ['login_base'])
289             site = sites[0]
290             login_base = site['login_base']
291             pl_record['site'] = ".".join([auth_hrn, login_base])
292         if 'person_ids' in pl_record:
293             persons =  self.plshell.GetPersons(self.plauth, pl_record['person_ids'], ['email'])
294             emails = [person['email'] for person in persons]
295             usernames = [email.split('@')[0] for email in emails]
296             person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
297             pl_record['persons'] = person_hrns 
298         if 'slice_ids' in pl_record:
299             slices = self.plshell.GetSlices(self.plauth, pl_record['slice_ids'], ['name'])
300             slicenames = [slice['name'] for slice in slices]
301             slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
302             pl_record['slices'] = slice_hrns
303         if 'node_ids' in pl_record:
304             nodes = self.plshell.GetNodes(self.plauth, pl_record['node_ids'], ['hostname'])
305             hostnames = [node['hostname'] for node in nodes]
306             node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
307             pl_record['nodes'] = node_hrns
308         if 'site_ids' in pl_record:
309             sites = self.plshell.GetSites(self.plauth, pl_record['site_ids'], ['login_base'])
310             login_bases = [site['login_base'] for site in sites]
311             site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
312             pl_record['sites'] = site_hrns
313         if 'key_ids' in pl_record:
314             keys = self.plshell.GetKeys(self.plauth, pl_record['key_ids'])
315             pubkeys = []
316             if keys:
317                 pubkeys = [key['key'] for key in keys]
318             pl_record['keys'] = pubkeys     
319
320         record.update(pl_record)
321
322
323
324     def fill_record_geni_info(self, record):
325         geni_info = {}
326         type = record['type']
327         from sfa.util.genitable import GeniTable
328         table = GeniTable()
329         if (type == "slice"):
330             person_ids = record.get("person_ids", [])
331             persons = table.find({'type': 'user', 'pointer': person_ids})
332             researchers = [person['hrn'] for person in persons]
333             geni_info['researcher'] = researchers
334
335         elif (type == "authority"):
336             person_ids = record.get("person_ids", [])
337             persons = table.find({'type': 'user', 'pointer': person_ids})
338             persons_dict = {}
339             for person in persons:
340                 persons_dict[person['pointer']] = person 
341             pl_persons = self.plshell.GetPersons(self.plauth, person_ids, ['person_id', 'roles'])
342             pis, techs, admins = [], [], []
343             for person in pl_persons:
344                 pointer = person['person_id']
345                 
346                 if pointer not in persons_dict:
347                     # this means there is not sfa record for this user
348                     continue    
349                 hrn = persons_dict[pointer]['hrn']    
350                 if 'pi' in person['roles']:
351                     pis.append(hrn)
352                 if 'tech' in person['roles']:
353                     techs.append(hrn)
354                 if 'admin' in person['roles']:
355                     admins.append(hrn)
356             
357             geni_info['PI'] = pis
358             geni_info['operator'] = techs
359             geni_info['owner'] = admins
360             # xxx TODO: OrganizationName
361
362         elif (type == "node"):
363             geni_info['dns'] = record.get("hostname", "")
364             # xxx TODO: URI, LatLong, IP, DNS
365     
366         elif (type == "user"):
367             geni_info['email'] = record.get("email", "")
368             # xxx TODO: PostalAddress, Phone
369
370         record.update(geni_info)
371
372     def fill_record_info(self, record):
373         """
374         Given a geni record, fill in the PLC specific and Geni specific
375         fields in the record. 
376         """
377         self.fill_record_pl_info(record)
378         self.fill_record_geni_info(record)
379
380     def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
381         # get a list of the HRNs tht are members of the old and new records
382         if oldRecord:
383             oldList = oldRecord.get(listName, [])
384         else:
385             oldList = []     
386         newList = record.get(listName, [])
387
388         # if the lists are the same, then we don't have to update anything
389         if (oldList == newList):
390             return
391
392         # build a list of the new person ids, by looking up each person to get
393         # their pointer
394         newIdList = []
395         from sfa.util.genitable import GeniTable
396         table = GeniTable()
397         records = table.find({'type': 'user', 'hrn': newList})
398         for rec in records:
399             newIdList.append(rec['pointer'])
400
401         # build a list of the old person ids from the person_ids field 
402         if oldRecord:
403             oldIdList = oldRecord.get("person_ids", [])
404             containerId = oldRecord.get_pointer()
405         else:
406             # if oldRecord==None, then we are doing a Register, instead of an
407             # update.
408             oldIdList = []
409             containerId = record.get_pointer()
410
411     # add people who are in the new list, but not the oldList
412         for personId in newIdList:
413             if not (personId in oldIdList):
414                 addFunc(self.plauth, personId, containerId)
415
416         # remove people who are in the old list, but not the new list
417         for personId in oldIdList:
418             if not (personId in newIdList):
419                 delFunc(self.plauth, personId, containerId)
420
421     def update_membership(self, oldRecord, record):
422         if record.type == "slice":
423             self.update_membership_list(oldRecord, record, 'researcher',
424                                         self.plshell.AddPersonToSlice,
425                                         self.plshell.DeletePersonFromSlice)
426         elif record.type == "authority":
427             # xxx TODO
428             pass
429
430
431
432 class ComponentAPI(BaseAPI):
433
434     def __init__(self, config = "/etc/sfa/sfa_config", encoding = "utf-8", methods='sfa.methods',
435                  peer_cert = None, interface = None, key_file = None, cert_file = None):
436
437         BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
438                          interface=interface, key_file=key_file, cert_file=cert_file)
439         self.encoding = encoding
440
441         # Better just be documenting the API
442         if config is None:
443             return
444
445         self.nodemanager = NodeManager()
446
447     def sliver_exists(self):
448         sliver_dict = self.nodemanager.GetXIDs()
449         if slicename in sliver_dict.keys():
450             return True
451         else:
452             return False