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