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