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