07273caa22d6450369268fcd745e3447d491fcfd
[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, relation_name, 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' and relation_name == 'researcher':
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         elif subject_type == 'authority' and target_type == 'user' and relation_name == 'pi':
543             logger.log ("WARNING:  XXX todo pldriver.update_relation not implemented for the 'pi' relationship")
544         else:
545             logger.info('unexpected relation %s to maintain, %s -> %s'%(relation_name,subject_type,target_type))
546
547         
548     ########################################
549     ########## aggregate oriented
550     ########################################
551
552     def testbed_name (self): return "myplc"
553
554     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
555     def aggregate_version (self):
556         version_manager = VersionManager()
557         ad_rspec_versions = []
558         request_rspec_versions = []
559         for rspec_version in version_manager.versions:
560             if rspec_version.content_type in ['*', 'ad']:
561                 ad_rspec_versions.append(rspec_version.to_dict())
562             if rspec_version.content_type in ['*', 'request']:
563                 request_rspec_versions.append(rspec_version.to_dict()) 
564         return {
565             'testbed':self.testbed_name(),
566             'geni_request_rspec_versions': request_rspec_versions,
567             'geni_ad_rspec_versions': ad_rspec_versions,
568             }
569
570     def list_slices (self, creds, options):
571         # look in cache first
572         if self.cache:
573             slices = self.cache.get('slices')
574             if slices:
575                 logger.debug("PlDriver.list_slices returns from cache")
576                 return slices
577     
578         # get data from db 
579         slices = self.shell.GetSlices({'peer_id': None}, ['name'])
580         slice_hrns = [slicename_to_hrn(self.hrn, slice['name']) for slice in slices]
581         slice_urns = [hrn_to_urn(slice_hrn, 'slice') for slice_hrn in slice_hrns]
582     
583         # cache the result
584         if self.cache:
585             logger.debug ("PlDriver.list_slices stores value in cache")
586             self.cache.add('slices', slice_urns) 
587     
588         return slice_urns
589         
590     # first 2 args are None in case of resource discovery
591     def list_resources (self, slice_urn, slice_hrn, creds, options):
592         cached_requested = options.get('cached', True) 
593     
594         version_manager = VersionManager()
595         # get the rspec's return format from options
596         rspec_version = version_manager.get_version(options.get('geni_rspec_version'))
597         version_string = "rspec_%s" % (rspec_version)
598     
599         #panos adding the info option to the caching key (can be improved)
600         if options.get('info'):
601             version_string = version_string + "_"+options.get('info', 'default')
602     
603         # look in cache first
604         if cached_requested and self.cache and not slice_hrn:
605             rspec = self.cache.get(version_string)
606             if rspec:
607                 logger.debug("PlDriver.ListResources: returning cached advertisement")
608                 return rspec 
609     
610         #panos: passing user-defined options
611         #print "manager options = ",options
612         aggregate = PlAggregate(self)
613         rspec =  aggregate.get_rspec(slice_xrn=slice_urn, version=rspec_version, 
614                                      options=options)
615     
616         # cache the result
617         if self.cache and not slice_hrn:
618             logger.debug("PlDriver.ListResources: stores advertisement in cache")
619             self.cache.add(version_string, rspec)
620     
621         return rspec
622     
623     def sliver_status (self, slice_urn, slice_hrn):
624         # find out where this slice is currently running
625         slicename = hrn_to_pl_slicename(slice_hrn)
626         
627         slices = self.shell.GetSlices([slicename], ['slice_id', 'node_ids','person_ids','name','expires'])
628         if len(slices) == 0:        
629             raise SliverDoesNotExist("%s (used %s as slicename internally)" % (slice_hrn, slicename))
630         slice = slices[0]
631         
632         # report about the local nodes only
633         nodes = self.shell.GetNodes({'node_id':slice['node_ids'],'peer_id':None},
634                               ['node_id', 'hostname', 'site_id', 'boot_state', 'last_contact'])
635
636         if len(nodes) == 0:
637             raise SliverDoesNotExist("You have not allocated any slivers here") 
638
639         # get login info
640         user = {}
641         if slice['person_ids']:
642             persons = self.shell.GetPersons(slice['person_ids'], ['key_ids'])
643             key_ids = [key_id for person in persons for key_id in person['key_ids']]
644             person_keys = self.shell.GetKeys(key_ids)
645             keys = [key['key'] for key in keys]
646
647             user.update({'urn': slice_urn,
648                          'login': slice['name'],
649                          'protocol': ['ssh'],
650                          'port': ['22'],
651                          'keys': keys})
652
653         site_ids = [node['site_id'] for node in nodes]
654     
655         result = {}
656         top_level_status = 'unknown'
657         if nodes:
658             top_level_status = 'ready'
659         result['geni_urn'] = slice_urn
660         result['pl_login'] = slice['name']
661         result['pl_expires'] = datetime_to_string(utcparse(slice['expires']))
662         result['geni_expires'] = datetime_to_string(utcparse(slice['expires']))
663         
664         resources = []
665         for node in nodes:
666             res = {}
667             res['pl_hostname'] = node['hostname']
668             res['pl_boot_state'] = node['boot_state']
669             res['pl_last_contact'] = node['last_contact']
670             res['geni_expires'] = datetime_to_string(utcparse(slice['expires']))
671             if node['last_contact'] is not None:
672                 
673                 res['pl_last_contact'] = datetime_to_string(utcparse(node['last_contact']))
674             sliver_id = urn_to_sliver_id(slice_urn, slice['slice_id'], node['node_id'], authority=self.hrn) 
675             res['geni_urn'] = sliver_id
676             if node['boot_state'] == 'boot':
677                 res['geni_status'] = 'ready'
678             else:
679                 res['geni_status'] = 'failed'
680                 top_level_status = 'failed' 
681                 
682             res['geni_error'] = ''
683             res['users'] = [user]  
684     
685             resources.append(res)
686             
687         result['geni_status'] = top_level_status
688         result['geni_resources'] = resources
689         return result
690
691     def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, users, options):
692
693         aggregate = PlAggregate(self)
694         slices = PlSlices(self)
695         peer = slices.get_peer(slice_hrn)
696         sfa_peer = slices.get_sfa_peer(slice_hrn)
697         slice_record=None    
698         if users:
699             slice_record = users[0].get('slice_record', {})
700     
701         # parse rspec
702         rspec = RSpec(rspec_string)
703         requested_attributes = rspec.version.get_slice_attributes()
704         
705         # ensure site record exists
706         site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer, options=options)
707         # ensure slice record exists
708         slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer, options=options)
709         # ensure person records exists
710         persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer, options=options)
711         # ensure slice attributes exists
712         slices.verify_slice_attributes(slice, requested_attributes, options=options)
713         
714         # add/remove slice from nodes
715         requested_slivers = []
716         for node in rspec.version.get_nodes_with_slivers():
717             hostname = None
718             if node.get('component_name'):
719                 hostname = node.get('component_name')
720             elif node.get('component_id'):
721                 hostname = xrn_to_hostname(node.get('component_id'))
722             if hostname:
723                 requested_slivers.append(hostname)
724         nodes = slices.verify_slice_nodes(slice, requested_slivers, peer) 
725    
726         # add/remove links links 
727         slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes)
728     
729         # handle MyPLC peer association.
730         # only used by plc and ple.
731         slices.handle_peer(site, slice, persons, peer)
732         
733         return aggregate.get_rspec(slice_xrn=slice_urn, version=rspec.version)
734
735     def delete_sliver (self, slice_urn, slice_hrn, creds, options):
736         slicename = hrn_to_pl_slicename(slice_hrn)
737         slices = self.shell.GetSlices({'name': slicename})
738         if not slices:
739             return 1
740         slice = slices[0]
741     
742         # determine if this is a peer slice
743         # xxx I wonder if this would not need to use PlSlices.get_peer instead 
744         # in which case plc.peers could be deprecated as this here
745         # is the only/last call to this last method in plc.peers
746         peer = peers.get_peer(self, slice_hrn)
747         try:
748             if peer:
749                 self.shell.UnBindObjectFromPeer('slice', slice['slice_id'], peer)
750             self.shell.DeleteSliceFromNodes(slicename, slice['node_ids'])
751         finally:
752             if peer:
753                 self.shell.BindObjectToPeer('slice', slice['slice_id'], peer, slice['peer_slice_id'])
754         return 1
755     
756     def renew_sliver (self, slice_urn, slice_hrn, creds, expiration_time, options):
757         slicename = hrn_to_pl_slicename(slice_hrn)
758         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
759         if not slices:
760             raise RecordNotFound(slice_hrn)
761         slice = slices[0]
762         requested_time = utcparse(expiration_time)
763         record = {'expires': int(datetime_to_epoch(requested_time))}
764         try:
765             self.shell.UpdateSlice(slice['slice_id'], record)
766             return True
767         except:
768             return False
769
770     # remove the 'enabled' tag 
771     def start_slice (self, slice_urn, slice_hrn, creds):
772         slicename = hrn_to_pl_slicename(slice_hrn)
773         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
774         if not slices:
775             raise RecordNotFound(slice_hrn)
776         slice_id = slices[0]['slice_id']
777         slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'}, ['slice_tag_id'])
778         # just remove the tag if it exists
779         if slice_tags:
780             self.shell.DeleteSliceTag(slice_tags[0]['slice_tag_id'])
781         return 1
782
783     # set the 'enabled' tag to 0
784     def stop_slice (self, slice_urn, slice_hrn, creds):
785         slicename = hrn_to_pl_slicename(slice_hrn)
786         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
787         if not slices:
788             raise RecordNotFound(slice_hrn)
789         slice_id = slices[0]['slice_id']
790         slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'})
791         if not slice_tags:
792             self.shell.AddSliceTag(slice_id, 'enabled', '0')
793         elif slice_tags[0]['value'] != "0":
794             tag_id = slice_tags[0]['slice_tag_id']
795             self.shell.UpdateSliceTag(tag_id, '0')
796         return 1
797     
798     def reset_slice (self, slice_urn, slice_hrn, creds):
799         raise SfaNotImplemented ("reset_slice not available at this interface")
800     
801     # xxx this code is quite old and has not run for ages
802     # it is obviously totally broken and needs a rewrite
803     def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
804         raise SfaNotImplemented,"PlDriver.get_ticket needs a rewrite"
805 # please keep this code for future reference
806 #        slices = PlSlices(self)
807 #        peer = slices.get_peer(slice_hrn)
808 #        sfa_peer = slices.get_sfa_peer(slice_hrn)
809 #    
810 #        # get the slice record
811 #        credential = api.getCredential()
812 #        interface = api.registries[api.hrn]
813 #        registry = api.server_proxy(interface, credential)
814 #        records = registry.Resolve(xrn, credential)
815 #    
816 #        # make sure we get a local slice record
817 #        record = None
818 #        for tmp_record in records:
819 #            if tmp_record['type'] == 'slice' and \
820 #               not tmp_record['peer_authority']:
821 #    #Error (E0602, GetTicket): Undefined variable 'SliceRecord'
822 #                slice_record = SliceRecord(dict=tmp_record)
823 #        if not record:
824 #            raise RecordNotFound(slice_hrn)
825 #        
826 #        # similar to CreateSliver, we must verify that the required records exist
827 #        # at this aggregate before we can issue a ticket
828 #        # parse rspec
829 #        rspec = RSpec(rspec_string)
830 #        requested_attributes = rspec.version.get_slice_attributes()
831 #    
832 #        # ensure site record exists
833 #        site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer)
834 #        # ensure slice record exists
835 #        slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer)
836 #        # ensure person records exists
837 #    # xxx users is undefined in this context
838 #        persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer)
839 #        # ensure slice attributes exists
840 #        slices.verify_slice_attributes(slice, requested_attributes)
841 #        
842 #        # get sliver info
843 #        slivers = slices.get_slivers(slice_hrn)
844 #    
845 #        if not slivers:
846 #            raise SliverDoesNotExist(slice_hrn)
847 #    
848 #        # get initscripts
849 #        initscripts = []
850 #        data = {
851 #            'timestamp': int(time.time()),
852 #            'initscripts': initscripts,
853 #            'slivers': slivers
854 #        }
855 #    
856 #        # create the ticket
857 #        object_gid = record.get_gid_object()
858 #        new_ticket = SfaTicket(subject = object_gid.get_subject())
859 #        new_ticket.set_gid_caller(api.auth.client_gid)
860 #        new_ticket.set_gid_object(object_gid)
861 #        new_ticket.set_issuer(key=api.key, subject=self.hrn)
862 #        new_ticket.set_pubkey(object_gid.get_pubkey())
863 #        new_ticket.set_attributes(data)
864 #        new_ticket.set_rspec(rspec)
865 #        #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
866 #        new_ticket.encode()
867 #        new_ticket.sign()
868 #    
869 #        return new_ticket.save_to_string(save_parents=True)