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