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