PL Driver: 1st shot rebase of PL operations around the HRN tags of Slice/Person/Site
[sfa.git] / sfa / planetlab / pldriver.py
1 import time
2 import datetime
3 #
4 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
5     RecordNotFound, SfaNotImplemented, SliverDoesNotExist
6
7 from sfa.util.sfalogging import logger
8 from sfa.util.defaultdict import defaultdict
9 from sfa.util.sfatime import utcparse, datetime_to_string, datetime_to_epoch
10 from sfa.util.xrn import Xrn, hrn_to_urn, get_leaf
11 from sfa.util.cache import Cache
12
13 # one would think the driver should not need to mess with the SFA db, but..
14 from sfa.storage.alchemy import dbsession
15 from sfa.storage.model import RegRecord
16
17 # used to be used in get_ticket
18 #from sfa.trust.sfaticket import SfaTicket
19
20 from sfa.rspecs.version_manager import VersionManager
21 from sfa.rspecs.rspec import RSpec
22
23 # the driver interface, mostly provides default behaviours
24 from sfa.managers.driver import Driver
25
26 from sfa.planetlab.plshell import PlShell
27 import sfa.planetlab.peers as peers
28 from sfa.planetlab.plaggregate import PlAggregate
29 from sfa.planetlab.plslices import PlSlices
30 from sfa.planetlab.plxrn import PlXrn, slicename_to_hrn, hostname_to_hrn, hrn_to_pl_slicename, xrn_to_hostname, top_auth, hash_loginbase
31
32
33 def list_to_dict(recs, key):
34     """
35     convert a list of dictionaries into a dictionary keyed on the 
36     specified dictionary key 
37     """
38     return dict ( [ (rec[key],rec) for rec in recs ] )
39
40 #
41 # PlShell is just an xmlrpc serverproxy where methods
42 # can be sent as-is; it takes care of authentication
43 # from the global config
44
45 class PlDriver (Driver):
46
47     # the cache instance is a class member so it survives across incoming requests
48     cache = None
49
50     def __init__ (self, config):
51         Driver.__init__ (self, config)
52         self.shell = PlShell (config)
53         self.cache=None
54         if config.SFA_AGGREGATE_CACHING:
55             if PlDriver.cache is None:
56                 PlDriver.cache = Cache()
57             self.cache = PlDriver.cache
58  
59     ########################################
60     ########## registry oriented
61     ########################################
62
63     def augment_records_with_testbed_info (self, sfa_records):
64         return self.fill_record_info (sfa_records)
65
66     ########## 
67     def register (self, sfa_record, hrn, pub_key):
68         type = sfa_record['type']
69         pl_record = self.sfa_fields_to_pl_fields(type, hrn, sfa_record)
70
71         if type == 'authority':
72             sites = self.shell.GetSites([pl_record['login_base']])
73             if not sites:
74                 # xxx when a site gets registered through SFA we need to set its max_slices
75                 if 'max_slices' not in pl_record:
76                     pl_record['max_slices']=2
77                 pointer = self.shell.AddSite(pl_record)
78                 self.shell.SetSiteHrn(int(pointer), hrn)
79             else:
80                 pointer = sites[0]['site_id']
81
82         elif type == 'slice':
83             acceptable_fields=['url', 'instantiation', 'name', 'description']
84             for key in pl_record.keys():
85                 if key not in acceptable_fields:
86                     pl_record.pop(key)
87             slices = self.shell.GetSlices([pl_record['name']])
88             if not slices:
89                  pointer = self.shell.AddSlice(pl_record)
90                  self.shell.SetSliceHrn(int(pointer), hrn)
91             else:
92                  pointer = slices[0]['slice_id']
93
94         elif type == 'user':
95             persons = self.shell.GetPersons({'email':sfa_record['email']})
96             if not persons:
97                 for key in ['first_name','last_name']:
98                     if key not in sfa_record: sfa_record[key]='*from*sfa*'
99                 # AddPerson does not allow everything to be set
100                 can_add = ['first_name', 'last_name', 'title','email', 'password', 'phone', 'url', 'bio']
101                 add_person_dict=dict ( [ (k,sfa_record[k]) for k in sfa_record if k in can_add ] )
102                 pointer = self.shell.AddPerson(add_person_dict)
103                 self.shell.SetPersonHrn(int(pointer), hrn)
104             else:
105                 pointer = persons[0]['person_id']
106     
107             if 'enabled' in sfa_record and sfa_record['enabled']:
108                 self.shell.UpdatePerson(pointer, {'enabled': sfa_record['enabled']})
109             # add this person to the site only if she is being added for the first
110             # time by sfa and doesont already exist in plc
111             if not persons or not persons[0]['site_ids']:
112                 login_base = get_leaf(sfa_record['authority'])
113                 self.shell.AddPersonToSite(pointer, login_base)
114     
115             # What roles should this user have?
116             roles=[]
117             if 'roles' in sfa_record: 
118                 # if specified in xml, but only low-level roles
119                 roles = [ role for role in sfa_record['roles'] if role in ['user','tech'] ]
120             # at least user if no other cluse could be found
121             if not roles:
122                 roles=['user']
123             for role in roles:
124                 self.shell.AddRoleToPerson(role, pointer)
125             # Add the user's key
126             if pub_key:
127                 self.shell.AddPersonKey(pointer, {'key_type' : 'ssh', 'key' : pub_key})
128
129         elif type == 'node':
130             login_base = PlXrn(xrn=sfa_record['authority'],type='authority').pl_login_base()
131             nodes = self.shell.GetNodes([pl_record['hostname']])
132             if not nodes:
133                 pointer = self.shell.AddNode(login_base, pl_record)
134                 self.shell.SetNodeHrn(int(pointer), hrn)
135             else:
136                 pointer = nodes[0]['node_id']
137     
138         return pointer
139         
140     ##########
141     # xxx actually old_sfa_record comes filled with plc stuff as well in the original code
142     def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
143         pointer = old_sfa_record['pointer']
144         type = old_sfa_record['type']
145         new_key_pointer = None
146
147         # new_key implemented for users only
148         if new_key and type not in [ 'user' ]:
149             raise UnknownSfaType(type)
150
151         if (type == "authority"):
152             self.shell.UpdateSite(pointer, new_sfa_record)
153             self.shell.SetSiteHrn(pointer, hrn)
154     
155         elif type == "slice":
156             pl_record=self.sfa_fields_to_pl_fields(type, hrn, new_sfa_record)
157             if 'name' in pl_record:
158                 pl_record.pop('name')
159                 self.shell.UpdateSlice(pointer, pl_record)
160                 self.shell.SetSliceHrn(pointer, hrn)
161     
162         elif type == "user":
163             # SMBAKER: UpdatePerson only allows a limited set of fields to be
164             #    updated. Ideally we should have a more generic way of doing
165             #    this. I copied the field names from UpdatePerson.py...
166             update_fields = {}
167             all_fields = new_sfa_record
168             for key in all_fields.keys():
169                 if key in ['first_name', 'last_name', 'title', 'email',
170                            'password', 'phone', 'url', 'bio', 'accepted_aup',
171                            'enabled']:
172                     update_fields[key] = all_fields[key]
173             # when updating a user, we always get a 'email' field at this point
174             # this is because 'email' is a native field in the RegUser object...
175             if 'email' in update_fields and not update_fields['email']:
176                 del update_fields['email']
177             self.shell.UpdatePerson(pointer, update_fields)
178             self.shell.SetPersonHrn(pointer, hrn)
179     
180             if new_key:
181                 # must check this key against the previous one if it exists
182                 persons = self.shell.GetPersons([pointer], ['key_ids'])
183                 person = persons[0]
184                 keys = person['key_ids']
185                 keys = self.shell.GetKeys(person['key_ids'])
186                 
187                 key_exists = False
188                 for key in keys:
189                     if new_key == key['key']:
190                         key_exists = True
191                         new_key_pointer = key['key_id']
192                         break
193                 if not key_exists:
194                     new_key_pointer = self.shell.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
195     
196         elif type == "node":
197             self.shell.UpdateNode(pointer, new_sfa_record)
198
199         return (pointer, new_key_pointer)
200         
201
202     ##########
203     def remove (self, sfa_record):
204         type=sfa_record['type']
205         pointer=sfa_record['pointer']
206         if type == 'user':
207             persons = self.shell.GetPersons(pointer)
208             # only delete this person if he has site ids. if he doesnt, it probably means
209             # he was just removed from a site, not actually deleted
210             if persons and persons[0]['site_ids']:
211                 self.shell.DeletePerson(pointer)
212         elif type == 'slice':
213             if self.shell.GetSlices(pointer):
214                 self.shell.DeleteSlice(pointer)
215         elif type == 'node':
216             if self.shell.GetNodes(pointer):
217                 self.shell.DeleteNode(pointer)
218         elif type == 'authority':
219             if self.shell.GetSites(pointer):
220                 self.shell.DeleteSite(pointer)
221
222         return True
223
224
225
226
227
228     ##
229     # Convert SFA fields to PLC fields for use when registering or updating
230     # registry record in the PLC database
231     #
232
233     def sfa_fields_to_pl_fields(self, type, hrn, sfa_record):
234
235         pl_record = {}
236  
237         if type == "slice":
238             pl_record["name"] = hrn_to_pl_slicename(hrn)
239             if "instantiation" in sfa_record:
240                 pl_record['instantiation']=sfa_record['instantiation']
241             else:
242                 pl_record["instantiation"] = "plc-instantiated"
243             if "url" in sfa_record:
244                pl_record["url"] = sfa_record["url"]
245             if "description" in sfa_record:
246                 pl_record["description"] = sfa_record["description"]
247             if "expires" in sfa_record:
248                 date = utcparse(sfa_record['expires'])
249                 expires = datetime_to_epoch(date)
250                 pl_record["expires"] = expires
251
252         elif type == "node":
253             if not "hostname" in pl_record:
254                 # fetch from sfa_record
255                 if "hostname" not in sfa_record:
256                     raise MissingSfaInfo("hostname")
257                 pl_record["hostname"] = sfa_record["hostname"]
258             if "model" in sfa_record: 
259                 pl_record["model"] = sfa_record["model"]
260             else:
261                 pl_record["model"] = "geni"
262
263         elif type == "authority":
264             pl_record["login_base"] = PlXrn(xrn=hrn,type='authority').pl_login_base()
265             if "name" not in sfa_record:
266                 pl_record["name"] = hrn
267             if "abbreviated_name" not in sfa_record:
268                 pl_record["abbreviated_name"] = hrn
269             if "enabled" not in sfa_record:
270                 pl_record["enabled"] = True
271             if "is_public" not in sfa_record:
272                 pl_record["is_public"] = True
273
274         return pl_record
275
276     ####################
277     def fill_record_info(self, records):
278         """
279         Given a (list of) SFA record, fill in the PLC specific 
280         and SFA specific fields in the record. 
281         """
282         if not isinstance(records, list):
283             records = [records]
284
285         self.fill_record_pl_info(records)
286         self.fill_record_hrns(records)
287         self.fill_record_sfa_info(records)
288         return records
289
290     def fill_record_pl_info(self, records):
291         """
292         Fill in the planetlab specific fields of a SFA record. This
293         involves calling the appropriate PLC method to retrieve the 
294         database record for the object.
295             
296         @param record: record to fill in field (in/out param)     
297         """
298         # get ids by type
299         node_ids, site_ids, slice_ids = [], [], [] 
300         person_ids, key_ids = [], []
301         type_map = {'node': node_ids, 'authority': site_ids,
302                     'slice': slice_ids, 'user': person_ids}
303                   
304         for record in records:
305             for type in type_map:
306                 if type == record['type']:
307                     type_map[type].append(record['pointer'])
308
309         # get pl records
310         nodes, sites, slices, persons, keys = {}, {}, {}, {}, {}
311         if node_ids:
312             node_list = self.shell.GetNodes(node_ids)
313             nodes = list_to_dict(node_list, 'node_id')
314         if site_ids:
315             site_list = self.shell.GetSites(site_ids)
316             sites = list_to_dict(site_list, 'site_id')
317         if slice_ids:
318             slice_list = self.shell.GetSlices(slice_ids)
319             slices = list_to_dict(slice_list, 'slice_id')
320         if person_ids:
321             person_list = self.shell.GetPersons(person_ids)
322             persons = list_to_dict(person_list, 'person_id')
323             for person in persons:
324                 key_ids.extend(persons[person]['key_ids'])
325
326         pl_records = {'node': nodes, 'authority': sites,
327                       'slice': slices, 'user': persons}
328
329         if key_ids:
330             key_list = self.shell.GetKeys(key_ids)
331             keys = list_to_dict(key_list, 'key_id')
332
333         # fill record info
334         for record in records:
335             # records with pointer==-1 do not have plc info.
336             # for example, the top level authority records which are
337             # authorities, but not PL "sites"
338             if record['pointer'] == -1:
339                 continue
340            
341             for type in pl_records:
342                 if record['type'] == type:
343                     if record['pointer'] in pl_records[type]:
344                         record.update(pl_records[type][record['pointer']])
345                         break
346             # fill in key info
347             if record['type'] == 'user':
348                 if 'key_ids' not in record:
349                     logger.info("user record has no 'key_ids' - need to import from myplc ?")
350                 else:
351                     pubkeys = [keys[key_id]['key'] for key_id in record['key_ids'] if key_id in keys] 
352                     record['keys'] = pubkeys
353
354         return records
355
356     def fill_record_hrns(self, records):
357         """
358         convert pl ids to hrns
359         """
360
361         # get ids
362         slice_ids, person_ids, site_ids, node_ids = [], [], [], []
363         for record in records:
364             if 'site_id' in record:
365                 site_ids.append(record['site_id'])
366             if 'site_ids' in record:
367                 site_ids.extend(record['site_ids'])
368             if 'person_ids' in record:
369                 person_ids.extend(record['person_ids'])
370             if 'slice_ids' in record:
371                 slice_ids.extend(record['slice_ids'])
372             if 'node_ids' in record:
373                 node_ids.extend(record['node_ids'])
374
375         # get pl records
376         slices, persons, sites, nodes = {}, {}, {}, {}
377         if site_ids:
378             site_list = self.shell.GetSites(site_ids, ['site_id', 'login_base'])
379             sites = list_to_dict(site_list, 'site_id')
380         if person_ids:
381             person_list = self.shell.GetPersons(person_ids, ['person_id', 'email'])
382             persons = list_to_dict(person_list, 'person_id')
383         if slice_ids:
384             slice_list = self.shell.GetSlices(slice_ids, ['slice_id', 'name'])
385             slices = list_to_dict(slice_list, 'slice_id')       
386         if node_ids:
387             node_list = self.shell.GetNodes(node_ids, ['node_id', 'hostname'])
388             nodes = list_to_dict(node_list, 'node_id')
389        
390         # convert ids to hrns
391         for record in records:
392             # get all relevant data
393             type = record['type']
394             pointer = record['pointer']
395             auth_hrn = self.hrn
396             login_base = ''
397             if pointer == -1:
398                 continue
399
400             if 'site_id' in record:
401                 site = sites[record['site_id']]
402                 login_base = site['login_base']
403                 record['site'] = ".".join([auth_hrn, login_base])
404             if 'person_ids' in record:
405                 emails = [persons[person_id]['email'] for person_id in record['person_ids'] \
406                           if person_id in  persons]
407                 usernames = [email.split('@')[0] for email in emails]
408                 person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
409                 record['persons'] = person_hrns 
410             if 'slice_ids' in record:
411                 slicenames = [slices[slice_id]['name'] for slice_id in record['slice_ids'] \
412                               if slice_id in slices]
413                 slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
414                 record['slices'] = slice_hrns
415             if 'node_ids' in record:
416                 hostnames = [nodes[node_id]['hostname'] for node_id in record['node_ids'] \
417                              if node_id in nodes]
418                 node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
419                 record['nodes'] = node_hrns
420             if 'site_ids' in record:
421                 login_bases = [sites[site_id]['login_base'] for site_id in record['site_ids'] \
422                                if site_id in sites]
423                 site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
424                 record['sites'] = site_hrns
425
426             if 'expires' in record:
427                 date = utcparse(record['expires'])
428                 datestring = datetime_to_string(date)
429                 record['expires'] = datestring 
430             
431         return records   
432
433     def fill_record_sfa_info(self, records):
434
435         def startswith(prefix, values):
436             return [value for value in values if value.startswith(prefix)]
437
438         # get person ids
439         person_ids = []
440         site_ids = []
441         for record in records:
442             person_ids.extend(record.get("person_ids", []))
443             site_ids.extend(record.get("site_ids", [])) 
444             if 'site_id' in record:
445                 site_ids.append(record['site_id']) 
446         
447         # get all pis from the sites we've encountered
448         # and store them in a dictionary keyed on site_id 
449         site_pis = {}
450         if site_ids:
451             pi_filter = {'|roles': ['pi'], '|site_ids': site_ids} 
452             pi_list = self.shell.GetPersons(pi_filter, ['person_id', 'site_ids'])
453             for pi in pi_list:
454                 # we will need the pi's hrns also
455                 person_ids.append(pi['person_id'])
456                 
457                 # we also need to keep track of the sites these pis
458                 # belong to
459                 for site_id in pi['site_ids']:
460                     if site_id in site_pis:
461                         site_pis[site_id].append(pi)
462                     else:
463                         site_pis[site_id] = [pi]
464                  
465         # get sfa records for all records associated with these records.   
466         # we'll replace pl ids (person_ids) with hrns from the sfa records
467         # we obtain
468         
469         # get the registry records
470         person_list, persons = [], {}
471         person_list = dbsession.query (RegRecord).filter(RegRecord.pointer.in_(person_ids))
472         # create a hrns keyed on the sfa record's pointer.
473         # Its possible for multiple records to have the same pointer so
474         # the dict's value will be a list of hrns.
475         persons = defaultdict(list)
476         for person in person_list:
477             persons[person.pointer].append(person)
478
479         # get the pl records
480         pl_person_list, pl_persons = [], {}
481         pl_person_list = self.shell.GetPersons(person_ids, ['person_id', 'roles'])
482         pl_persons = list_to_dict(pl_person_list, 'person_id')
483
484         # fill sfa info
485         for record in records:
486             # skip records with no pl info (top level authorities)
487             #if record['pointer'] == -1:
488             #    continue 
489             sfa_info = {}
490             type = record['type']
491             logger.info("fill_record_sfa_info - incoming record typed %s"%type)
492             if (type == "slice"):
493                 # all slice users are researchers
494                 record['geni_urn'] = hrn_to_urn(record['hrn'], 'slice')
495                 record['PI'] = []
496                 record['researcher'] = []
497                 for person_id in record.get('person_ids', []):
498                     hrns = [person.hrn for person in persons[person_id]]
499                     record['researcher'].extend(hrns)                
500
501                 # pis at the slice's site
502                 if 'site_id' in record and record['site_id'] in site_pis:
503                     pl_pis = site_pis[record['site_id']]
504                     pi_ids = [pi['person_id'] for pi in pl_pis]
505                     for person_id in pi_ids:
506                         hrns = [person.hrn for person in persons[person_id]]
507                         record['PI'].extend(hrns)
508                         record['geni_creator'] = record['PI'] 
509                 
510             elif (type.startswith("authority")):
511                 record['url'] = None
512                 logger.info("fill_record_sfa_info - authority xherex")
513                 if record['pointer'] != -1:
514                     record['PI'] = []
515                     record['operator'] = []
516                     record['owner'] = []
517                     for pointer in record.get('person_ids', []):
518                         if pointer not in persons or pointer not in pl_persons:
519                             # this means there is not sfa or pl record for this user
520                             continue   
521                         hrns = [person.hrn for person in persons[pointer]] 
522                         roles = pl_persons[pointer]['roles']   
523                         if 'pi' in roles:
524                             record['PI'].extend(hrns)
525                         if 'tech' in roles:
526                             record['operator'].extend(hrns)
527                         if 'admin' in roles:
528                             record['owner'].extend(hrns)
529                         # xxx TODO: OrganizationName
530             elif (type == "node"):
531                 sfa_info['dns'] = record.get("hostname", "")
532                 # xxx TODO: URI, LatLong, IP, DNS
533     
534             elif (type == "user"):
535                 logger.info('setting user.email')
536                 sfa_info['email'] = record.get("email", "")
537                 sfa_info['geni_urn'] = hrn_to_urn(record['hrn'], 'user')
538                 sfa_info['geni_certificate'] = record['gid'] 
539                 # xxx TODO: PostalAddress, Phone
540             record.update(sfa_info)
541
542
543     ####################
544     # plcapi works by changes, compute what needs to be added/deleted
545     def update_relation (self, subject_type, target_type, relation_name, subject_id, target_ids):
546         # hard-wire the code for slice/user for now, could be smarter if needed
547         if subject_type =='slice' and target_type == 'user' and relation_name == 'researcher':
548             subject=self.shell.GetSlices (subject_id)[0]
549             current_target_ids = subject['person_ids']
550             add_target_ids = list ( set (target_ids).difference(current_target_ids))
551             del_target_ids = list ( set (current_target_ids).difference(target_ids))
552             logger.debug ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
553             for target_id in add_target_ids:
554                 self.shell.AddPersonToSlice (target_id,subject_id)
555                 logger.debug ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
556             for target_id in del_target_ids:
557                 logger.debug ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
558                 self.shell.DeletePersonFromSlice (target_id, subject_id)
559         elif subject_type == 'authority' and target_type == 'user' and relation_name == 'pi':
560             # due to the plcapi limitations this means essentially adding pi role to all people in the list
561             # it's tricky to remove any pi role here, although it might be desirable
562             persons = self.shell.GetPersons (target_ids)
563             for person in persons: 
564                 if 'pi' not in person['roles']:
565                     self.shell.AddRoleToPerson('pi',person['person_id'])
566         else:
567             logger.info('unexpected relation %s to maintain, %s -> %s'%(relation_name,subject_type,target_type))
568
569         
570     ########################################
571     ########## aggregate oriented
572     ########################################
573
574     def testbed_name (self): return "myplc"
575
576     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
577     def aggregate_version (self):
578         version_manager = VersionManager()
579         ad_rspec_versions = []
580         request_rspec_versions = []
581         for rspec_version in version_manager.versions:
582             if rspec_version.content_type in ['*', 'ad']:
583                 ad_rspec_versions.append(rspec_version.to_dict())
584             if rspec_version.content_type in ['*', 'request']:
585                 request_rspec_versions.append(rspec_version.to_dict()) 
586         return {
587             'testbed':self.testbed_name(),
588             'geni_request_rspec_versions': request_rspec_versions,
589             'geni_ad_rspec_versions': ad_rspec_versions,
590             }
591
592     def list_slices (self, creds, options):
593         # look in cache first
594         if self.cache:
595             slices = self.cache.get('slices')
596             if slices:
597                 logger.debug("PlDriver.list_slices returns from cache")
598                 return slices
599     
600         # get data from db 
601         slices = self.shell.GetSlices({'peer_id': None}, ['name'])
602         slice_hrns = [slicename_to_hrn(self.hrn, slice['name']) for slice in slices]
603         slice_urns = [hrn_to_urn(slice_hrn, 'slice') for slice_hrn in slice_hrns]
604     
605         # cache the result
606         if self.cache:
607             logger.debug ("PlDriver.list_slices stores value in cache")
608             self.cache.add('slices', slice_urns) 
609     
610         return slice_urns
611         
612     # first 2 args are None in case of resource discovery
613     def list_resources (self, slice_urn, slice_hrn, creds, options):
614         cached_requested = options.get('cached', True) 
615     
616         version_manager = VersionManager()
617         # get the rspec's return format from options
618         rspec_version = version_manager.get_version(options.get('geni_rspec_version'))
619         version_string = "rspec_%s" % (rspec_version)
620     
621         #panos adding the info option to the caching key (can be improved)
622         if options.get('info'):
623             version_string = version_string + "_"+options.get('info', 'default')
624
625         # Adding the list_leases option to the caching key
626         if options.get('list_leases'):
627             version_string = version_string + "_"+options.get('list_leases', 'default')
628
629         # Adding geni_available to caching key
630         if options.get('geni_available'):
631             version_string = version_string + "_" + str(options.get('geni_available'))
632     
633         # look in cache first
634         if cached_requested and self.cache and not slice_hrn:
635             rspec = self.cache.get(version_string)
636             if rspec:
637                 logger.debug("PlDriver.ListResources: returning cached advertisement")
638                 return rspec 
639     
640         #panos: passing user-defined options
641         #print "manager options = ",options
642         aggregate = PlAggregate(self)
643         rspec =  aggregate.get_rspec(slice_xrn=slice_urn, version=rspec_version, 
644                                      options=options)
645     
646         # cache the result
647         if self.cache and not slice_hrn:
648             logger.debug("PlDriver.ListResources: stores advertisement in cache")
649             self.cache.add(version_string, rspec)
650     
651         return rspec
652     
653     def sliver_status (self, slice_urn, slice_hrn):
654         # find out where this slice is currently running
655         slicename = hrn_to_pl_slicename(slice_hrn)
656         
657         slices = self.shell.GetSlices([slicename], ['slice_id', 'node_ids','person_ids','name','expires'])
658         if len(slices) == 0:        
659             raise SliverDoesNotExist("%s (used %s as slicename internally)" % (slice_hrn, slicename))
660         slice = slices[0]
661         
662         # report about the local nodes only
663         nodes = self.shell.GetNodes({'node_id':slice['node_ids'],'peer_id':None},
664                               ['node_id', 'hostname', 'site_id', 'boot_state', 'last_contact'])
665
666         if len(nodes) == 0:
667             raise SliverDoesNotExist("You have not allocated any slivers here") 
668
669         # get login info
670         user = {}
671         if slice['person_ids']:
672             persons = self.shell.GetPersons(slice['person_ids'], ['key_ids'])
673             key_ids = [key_id for person in persons for key_id in person['key_ids']]
674             person_keys = self.shell.GetKeys(key_ids)
675             keys = [key['key'] for key in person_keys]
676
677             user.update({'urn': slice_urn,
678                          'login': slice['name'],
679                          'protocol': ['ssh'],
680                          'port': ['22'],
681                          'keys': keys})
682
683         site_ids = [node['site_id'] for node in nodes]
684     
685         result = {}
686         top_level_status = 'unknown'
687         if nodes:
688             top_level_status = 'ready'
689         result['geni_urn'] = slice_urn
690         result['pl_login'] = slice['name']
691         result['pl_expires'] = datetime_to_string(utcparse(slice['expires']))
692         result['geni_expires'] = datetime_to_string(utcparse(slice['expires']))
693         
694         resources = []
695         for node in nodes:
696             res = {}
697             res['pl_hostname'] = node['hostname']
698             res['pl_boot_state'] = node['boot_state']
699             res['pl_last_contact'] = node['last_contact']
700             res['geni_expires'] = datetime_to_string(utcparse(slice['expires']))
701             if node['last_contact'] is not None:
702                 
703                 res['pl_last_contact'] = datetime_to_string(utcparse(node['last_contact']))
704             sliver_xrn = Xrn(slice_urn, type='sliver', id=node['node_id'])
705             sliver_xrn.set_authority(self.hrn)
706              
707             res['geni_urn'] = sliver_xrn.urn
708             if node['boot_state'] == 'boot':
709                 res['geni_status'] = 'ready'
710             else:
711                 res['geni_status'] = 'failed'
712                 top_level_status = 'failed' 
713                 
714             res['geni_error'] = ''
715             res['users'] = [user]  
716     
717             resources.append(res)
718             
719         result['geni_status'] = top_level_status
720         result['geni_resources'] = resources
721         return result
722
723     def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, users, options):
724
725         aggregate = PlAggregate(self)
726         slices = PlSlices(self)
727         peer = slices.get_peer(slice_hrn)
728         sfa_peer = slices.get_sfa_peer(slice_hrn)
729         slice_record=None    
730         if users:
731             slice_record = users[0].get('slice_record', {})
732     
733         # parse rspec
734         rspec = RSpec(rspec_string)
735         requested_attributes = rspec.version.get_slice_attributes()
736         
737         # ensure site record exists
738         site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer, options=options)
739         # ensure slice record exists
740         slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer, options=options)
741         # ensure person records exists
742         persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer, options=options)
743         # ensure slice attributes exists
744         slices.verify_slice_attributes(slice, requested_attributes, options=options)
745         
746         # add/remove slice from nodes
747         requested_slivers = {}
748         slivers = rspec.version.get_nodes_with_slivers() 
749         nodes = slices.verify_slice_nodes(slice, slivers, peer) 
750    
751         # add/remove links links 
752         slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes)
753
754         # add/remove leases
755         try:
756            rspec_requested_leases = rspec.version.get_leases()
757            leases = slices.verify_slice_leases(slice, rspec_requested_leases, peer)
758         except:
759            pass
760         #requested_leases = []
761         #kept_leases = []
762         #for lease in rspec.version.get_leases():
763         #    requested_lease = {}
764         #    if not lease.get('lease_id'):
765         #       requested_lease['hostname'] = xrn_to_hostname(lease.get('component_id').strip())
766         #       requested_lease['start_time'] = lease.get('start_time')
767         #       requested_lease['duration'] = lease.get('duration')
768         #    else:
769         #       kept_leases.append(int(lease['lease_id']))
770         #    if requested_lease.get('hostname'):
771         #        requested_leases.append(requested_lease)
772
773         #leases = slices.verify_slice_leases(slice, requested_leases, kept_leases, peer)
774     
775         # handle MyPLC peer association.
776         # only used by plc and ple.
777         slices.handle_peer(site, slice, persons, peer)
778         
779         return aggregate.get_rspec(slice_xrn=slice_urn, 
780                                    version=rspec.version)
781
782     def delete_sliver (self, slice_urn, slice_hrn, creds, options):
783
784         top_auth_hrn = top_auth(slice_hrn)
785         site_hrn = '.'.join(slice_hrn.split('.')[:-1])
786         slice_part = slice_hrn.split('.')[-1]
787         if top_auth_hrn == self.driver.hrn:
788             login_base = slice_hrn.split('.')[-2][:12]
789         else:
790             login_base = hash_loginbase(site_hrn)
791
792         slicename = '_'.join([login_base, slice_part])
793
794         slices = self.shell.GetSlices({'name': slicename})
795         if not slices:
796             return True
797         slice = slices[0]
798         
799         # leases
800         leases = self.shell.GetLeases({'name': slicename})
801         leases_ids = [lease['lease_id'] for lease in leases ]
802         # determine if this is a peer slice
803         # xxx I wonder if this would not need to use PlSlices.get_peer instead 
804         # in which case plc.peers could be deprecated as this here
805         # is the only/last call to this last method in plc.peers
806         peer = peers.get_peer(self, slice_hrn)
807         try:
808             if peer:
809                 self.shell.UnBindObjectFromPeer('slice', slice['slice_id'], peer)
810             self.shell.DeleteSliceFromNodes(slicename, slice['node_ids'])
811             if len(leases_ids) > 0:
812                 self.shell.DeleteLeases(leases_ids)
813         finally:
814             if peer:
815                 self.shell.BindObjectToPeer('slice', slice['slice_id'], peer, slice['peer_slice_id'])
816         return True
817     
818     def renew_sliver (self, slice_urn, slice_hrn, creds, expiration_time, options):
819         top_auth_hrn = top_auth(slice_hrn)
820         site_hrn = '.'.join(slice_hrn.split('.')[:-1])
821         slice_part = slice_hrn.split('.')[-1]
822         if top_auth_hrn == self.driver.hrn:
823             login_base = slice_hrn.split('.')[-2][:12]
824         else:
825             login_base = hash_loginbase(site_hrn)
826
827         slicename = '_'.join([login_base, slice_part])
828         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
829         if not slices:
830             raise RecordNotFound(slice_hrn)
831         slice = slices[0]
832         requested_time = utcparse(expiration_time)
833         record = {'expires': int(datetime_to_epoch(requested_time))}
834         try:
835             self.shell.UpdateSlice(slice['slice_id'], record)
836             return True
837         except:
838             return False
839
840     # remove the 'enabled' tag 
841     def start_slice (self, slice_urn, slice_hrn, creds):
842         top_auth_hrn = top_auth(slice_hrn)
843         site_hrn = '.'.join(slice_hrn.split('.')[:-1])
844         slice_part = slice_hrn.split('.')[-1]
845         if top_auth_hrn == self.driver.hrn:
846             login_base = slice_hrn.split('.')[-2][:12]
847         else:
848             login_base = hash_loginbase(site_hrn)
849
850         slicename = '_'.join([login_base, slice_part])
851
852         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
853         if not slices:
854             raise RecordNotFound(slice_hrn)
855         slice_id = slices[0]['slice_id']
856         slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'}, ['slice_tag_id'])
857         # just remove the tag if it exists
858         if slice_tags:
859             self.shell.DeleteSliceTag(slice_tags[0]['slice_tag_id'])
860         return 1
861
862     # set the 'enabled' tag to 0
863     def stop_slice (self, slice_urn, slice_hrn, creds):
864         top_auth_hrn = top_auth(slice_hrn)
865         site_hrn = '.'.join(slice_hrn.split('.')[:-1])
866         slice_part = slice_hrn.split('.')[-1]
867         if top_auth_hrn == self.driver.hrn:
868             login_base = slice_hrn.split('.')[-2][:12]
869         else:
870             login_base = hash_loginbase(site_hrn)
871
872         slicename = '_'.join([login_base, slice_part])
873
874         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
875         if not slices:
876             raise RecordNotFound(slice_hrn)
877         slice_id = slices[0]['slice_id']
878         slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'})
879         if not slice_tags:
880             self.shell.AddSliceTag(slice_id, 'enabled', '0')
881         elif slice_tags[0]['value'] != "0":
882             tag_id = slice_tags[0]['slice_tag_id']
883             self.shell.UpdateSliceTag(tag_id, '0')
884         return 1
885     
886     def reset_slice (self, slice_urn, slice_hrn, creds):
887         raise SfaNotImplemented ("reset_slice not available at this interface")
888     
889     # xxx this code is quite old and has not run for ages
890     # it is obviously totally broken and needs a rewrite
891     def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
892         raise SfaNotImplemented,"PlDriver.get_ticket needs a rewrite"
893 # please keep this code for future reference
894 #        slices = PlSlices(self)
895 #        peer = slices.get_peer(slice_hrn)
896 #        sfa_peer = slices.get_sfa_peer(slice_hrn)
897 #    
898 #        # get the slice record
899 #        credential = api.getCredential()
900 #        interface = api.registries[api.hrn]
901 #        registry = api.server_proxy(interface, credential)
902 #        records = registry.Resolve(xrn, credential)
903 #    
904 #        # make sure we get a local slice record
905 #        record = None
906 #        for tmp_record in records:
907 #            if tmp_record['type'] == 'slice' and \
908 #               not tmp_record['peer_authority']:
909 #    #Error (E0602, GetTicket): Undefined variable 'SliceRecord'
910 #                slice_record = SliceRecord(dict=tmp_record)
911 #        if not record:
912 #            raise RecordNotFound(slice_hrn)
913 #        
914 #        # similar to CreateSliver, we must verify that the required records exist
915 #        # at this aggregate before we can issue a ticket
916 #        # parse rspec
917 #        rspec = RSpec(rspec_string)
918 #        requested_attributes = rspec.version.get_slice_attributes()
919 #    
920 #        # ensure site record exists
921 #        site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer)
922 #        # ensure slice record exists
923 #        slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer)
924 #        # ensure person records exists
925 #    # xxx users is undefined in this context
926 #        persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer)
927 #        # ensure slice attributes exists
928 #        slices.verify_slice_attributes(slice, requested_attributes)
929 #        
930 #        # get sliver info
931 #        slivers = slices.get_slivers(slice_hrn)
932 #    
933 #        if not slivers:
934 #            raise SliverDoesNotExist(slice_hrn)
935 #    
936 #        # get initscripts
937 #        initscripts = []
938 #        data = {
939 #            'timestamp': int(time.time()),
940 #            'initscripts': initscripts,
941 #            'slivers': slivers
942 #        }
943 #    
944 #        # create the ticket
945 #        object_gid = record.get_gid_object()
946 #        new_ticket = SfaTicket(subject = object_gid.get_subject())
947 #        new_ticket.set_gid_caller(api.auth.client_gid)
948 #        new_ticket.set_gid_object(object_gid)
949 #        new_ticket.set_issuer(key=api.key, subject=self.hrn)
950 #        new_ticket.set_pubkey(object_gid.get_pubkey())
951 #        new_ticket.set_attributes(data)
952 #        new_ticket.set_rspec(rspec)
953 #        #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
954 #        new_ticket.encode()
955 #        new_ticket.sign()
956 #    
957 #        return new_ticket.save_to_string(save_parents=True)