renew returns a list of structs
[sfa.git] / sfa / planetlab / pldriver.py
1 import datetime
2 #
3 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
4     RecordNotFound, SfaNotImplemented, SliverDoesNotExist, SearchFailed, \
5     UnsupportedOperation 
6 from sfa.util.sfalogging import logger
7 from sfa.util.defaultdict import defaultdict
8 from sfa.util.sfatime import utcparse, datetime_to_string, datetime_to_epoch
9 from sfa.util.xrn import Xrn, hrn_to_urn, get_leaf
10 from sfa.util.cache import Cache
11
12 # one would think the driver should not need to mess with the SFA db, but..
13 from sfa.storage.alchemy import dbsession
14 from sfa.storage.model import RegRecord, SliverAllocation
15
16 # used to be used in get_ticket
17 #from sfa.trust.sfaticket import SfaTicket
18 from sfa.rspecs.version_manager import VersionManager
19 from sfa.rspecs.rspec import RSpec
20
21 # the driver interface, mostly provides default behaviours
22 from sfa.managers.driver import Driver
23 from sfa.planetlab.plshell import PlShell
24 import sfa.planetlab.peers as peers
25 from sfa.planetlab.plaggregate import PlAggregate
26 from sfa.planetlab.plslices import PlSlices
27 from sfa.planetlab.plxrn import PlXrn, slicename_to_hrn, hostname_to_hrn, hrn_to_pl_slicename, xrn_to_hostname
28
29
30 def list_to_dict(recs, key):
31     """
32     convert a list of dictionaries into a dictionary keyed on the 
33     specified dictionary key 
34     """
35     return dict ( [ (rec[key],rec) for rec in recs ] )
36
37 #
38 # PlShell is just an xmlrpc serverproxy where methods
39 # can be sent as-is; it takes care of authentication
40 # from the global config
41
42 class PlDriver (Driver):
43
44     # the cache instance is a class member so it survives across incoming requests
45     cache = None
46
47     def __init__ (self, config):
48         Driver.__init__ (self, config)
49         self.shell = PlShell (config)
50         self.cache=None
51         if config.SFA_AGGREGATE_CACHING:
52             if PlDriver.cache is None:
53                 PlDriver.cache = Cache()
54             self.cache = PlDriver.cache
55  
56     ########################################
57     ########## registry oriented
58     ########################################
59
60     def augment_records_with_testbed_info (self, sfa_records):
61         return self.fill_record_info (sfa_records)
62
63     ########## 
64     def register (self, sfa_record, hrn, pub_key):
65         type = sfa_record['type']
66         pl_record = self.sfa_fields_to_pl_fields(type, hrn, sfa_record)
67
68         if type == 'authority':
69             sites = self.shell.GetSites([pl_record['login_base']])
70             if not sites:
71                 # xxx when a site gets registered through SFA we need to set its max_slices
72                 if 'max_slices' not in pl_record:
73                     pl_record['max_slices']=2
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             # when updating a user, we always get a 'email' field at this point
161             # this is because 'email' is a native field in the RegUser object...
162             if 'email' in update_fields and not update_fields['email']:
163                 del update_fields['email']
164             self.shell.UpdatePerson(pointer, update_fields)
165     
166             if new_key:
167                 # must check this key against the previous one if it exists
168                 persons = self.shell.GetPersons([pointer], ['key_ids'])
169                 person = persons[0]
170                 keys = person['key_ids']
171                 keys = self.shell.GetKeys(person['key_ids'])
172                 
173                 # Delete all stale keys
174                 key_exists = False
175                 for key in keys:
176                     if new_key != key['key']:
177                         self.shell.DeleteKey(key['key_id'])
178                     else:
179                         key_exists = True
180                 if not key_exists:
181                     self.shell.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
182     
183         elif type == "node":
184             self.shell.UpdateNode(pointer, new_sfa_record)
185
186         return True
187         
188
189     ##########
190     def remove (self, sfa_record):
191         type=sfa_record['type']
192         pointer=sfa_record['pointer']
193         if type == 'user':
194             persons = self.shell.GetPersons(pointer)
195             # only delete this person if he has site ids. if he doesnt, it probably means
196             # he was just removed from a site, not actually deleted
197             if persons and persons[0]['site_ids']:
198                 self.shell.DeletePerson(pointer)
199         elif type == 'slice':
200             if self.shell.GetSlices(pointer):
201                 self.shell.DeleteSlice(pointer)
202         elif type == 'node':
203             if self.shell.GetNodes(pointer):
204                 self.shell.DeleteNode(pointer)
205         elif type == 'authority':
206             if self.shell.GetSites(pointer):
207                 self.shell.DeleteSite(pointer)
208
209         return True
210
211
212
213
214
215     ##
216     # Convert SFA fields to PLC fields for use when registering or updating
217     # registry record in the PLC database
218     #
219
220     def sfa_fields_to_pl_fields(self, type, hrn, sfa_record):
221
222         pl_record = {}
223  
224         if type == "slice":
225             pl_record["name"] = hrn_to_pl_slicename(hrn)
226             if "instantiation" in sfa_record:
227                 pl_record['instantiation']=sfa_record['instantiation']
228             else:
229                 pl_record["instantiation"] = "plc-instantiated"
230             if "url" in sfa_record:
231                pl_record["url"] = sfa_record["url"]
232             if "description" in sfa_record:
233                 pl_record["description"] = sfa_record["description"]
234             if "expires" in sfa_record:
235                 date = utcparse(sfa_record['expires'])
236                 expires = datetime_to_epoch(date)
237                 pl_record["expires"] = expires
238
239         elif type == "node":
240             if not "hostname" in pl_record:
241                 # fetch from sfa_record
242                 if "hostname" not in sfa_record:
243                     raise MissingSfaInfo("hostname")
244                 pl_record["hostname"] = sfa_record["hostname"]
245             if "model" in sfa_record: 
246                 pl_record["model"] = sfa_record["model"]
247             else:
248                 pl_record["model"] = "geni"
249
250         elif type == "authority":
251             pl_record["login_base"] = PlXrn(xrn=hrn,type='authority').pl_login_base()
252             if "name" not in sfa_record:
253                 pl_record["name"] = hrn
254             if "abbreviated_name" not in sfa_record:
255                 pl_record["abbreviated_name"] = hrn
256             if "enabled" not in sfa_record:
257                 pl_record["enabled"] = True
258             if "is_public" not in sfa_record:
259                 pl_record["is_public"] = True
260
261         return pl_record
262
263     ####################
264     def fill_record_info(self, records):
265         """
266         Given a (list of) SFA record, fill in the PLC specific 
267         and SFA specific fields in the record. 
268         """
269         if not isinstance(records, list):
270             records = [records]
271
272         self.fill_record_pl_info(records)
273         self.fill_record_hrns(records)
274         self.fill_record_sfa_info(records)
275         return records
276
277     def fill_record_pl_info(self, records):
278         """
279         Fill in the planetlab specific fields of a SFA record. This
280         involves calling the appropriate PLC method to retrieve the 
281         database record for the object.
282             
283         @param record: record to fill in field (in/out param)     
284         """
285         # get ids by type
286         node_ids, site_ids, slice_ids = [], [], [] 
287         person_ids, key_ids = [], []
288         type_map = {'node': node_ids, 'authority': site_ids,
289                     'slice': slice_ids, 'user': person_ids}
290                   
291         for record in records:
292             for type in type_map:
293                 if type == record['type']:
294                     type_map[type].append(record['pointer'])
295
296         # get pl records
297         nodes, sites, slices, persons, keys = {}, {}, {}, {}, {}
298         if node_ids:
299             node_list = self.shell.GetNodes(node_ids)
300             nodes = list_to_dict(node_list, 'node_id')
301         if site_ids:
302             site_list = self.shell.GetSites(site_ids)
303             sites = list_to_dict(site_list, 'site_id')
304         if slice_ids:
305             slice_list = self.shell.GetSlices(slice_ids)
306             slices = list_to_dict(slice_list, 'slice_id')
307         if person_ids:
308             person_list = self.shell.GetPersons(person_ids)
309             persons = list_to_dict(person_list, 'person_id')
310             for person in persons:
311                 key_ids.extend(persons[person]['key_ids'])
312
313         pl_records = {'node': nodes, 'authority': sites,
314                       'slice': slices, 'user': persons}
315
316         if key_ids:
317             key_list = self.shell.GetKeys(key_ids)
318             keys = list_to_dict(key_list, 'key_id')
319
320         # fill record info
321         for record in records:
322             # records with pointer==-1 do not have plc info.
323             # for example, the top level authority records which are
324             # authorities, but not PL "sites"
325             if record['pointer'] == -1:
326                 continue
327            
328             for type in pl_records:
329                 if record['type'] == type:
330                     if record['pointer'] in pl_records[type]:
331                         record.update(pl_records[type][record['pointer']])
332                         break
333             # fill in key info
334             if record['type'] == 'user':
335                 if 'key_ids' not in record:
336                     logger.info("user record has no 'key_ids' - need to import from myplc ?")
337                 else:
338                     pubkeys = [keys[key_id]['key'] for key_id in record['key_ids'] if key_id in keys] 
339                     record['keys'] = pubkeys
340
341         return records
342
343     def fill_record_hrns(self, records):
344         """
345         convert pl ids to hrns
346         """
347
348         # get ids
349         slice_ids, person_ids, site_ids, node_ids = [], [], [], []
350         for record in records:
351             if 'site_id' in record:
352                 site_ids.append(record['site_id'])
353             if 'site_ids' in record:
354                 site_ids.extend(record['site_ids'])
355             if 'person_ids' in record:
356                 person_ids.extend(record['person_ids'])
357             if 'slice_ids' in record:
358                 slice_ids.extend(record['slice_ids'])
359             if 'node_ids' in record:
360                 node_ids.extend(record['node_ids'])
361
362         # get pl records
363         slices, persons, sites, nodes = {}, {}, {}, {}
364         if site_ids:
365             site_list = self.shell.GetSites(site_ids, ['site_id', 'login_base'])
366             sites = list_to_dict(site_list, 'site_id')
367         if person_ids:
368             person_list = self.shell.GetPersons(person_ids, ['person_id', 'email'])
369             persons = list_to_dict(person_list, 'person_id')
370         if slice_ids:
371             slice_list = self.shell.GetSlices(slice_ids, ['slice_id', 'name'])
372             slices = list_to_dict(slice_list, 'slice_id')       
373         if node_ids:
374             node_list = self.shell.GetNodes(node_ids, ['node_id', 'hostname'])
375             nodes = list_to_dict(node_list, 'node_id')
376        
377         # convert ids to hrns
378         for record in records:
379             # get all relevant data
380             type = record['type']
381             pointer = record['pointer']
382             auth_hrn = self.hrn
383             login_base = ''
384             if pointer == -1:
385                 continue
386
387             if 'site_id' in record:
388                 site = sites[record['site_id']]
389                 login_base = site['login_base']
390                 record['site'] = ".".join([auth_hrn, login_base])
391             if 'person_ids' in record:
392                 emails = [persons[person_id]['email'] for person_id in record['person_ids'] \
393                           if person_id in  persons]
394                 usernames = [email.split('@')[0] for email in emails]
395                 person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
396                 record['persons'] = person_hrns 
397             if 'slice_ids' in record:
398                 slicenames = [slices[slice_id]['name'] for slice_id in record['slice_ids'] \
399                               if slice_id in slices]
400                 slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
401                 record['slices'] = slice_hrns
402             if 'node_ids' in record:
403                 hostnames = [nodes[node_id]['hostname'] for node_id in record['node_ids'] \
404                              if node_id in nodes]
405                 node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
406                 record['nodes'] = node_hrns
407             if 'site_ids' in record:
408                 login_bases = [sites[site_id]['login_base'] for site_id in record['site_ids'] \
409                                if site_id in sites]
410                 site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
411                 record['sites'] = site_hrns
412
413             if 'expires' in record:
414                 date = utcparse(record['expires'])
415                 datestring = datetime_to_string(date)
416                 record['expires'] = datestring 
417             
418         return records   
419
420     def fill_record_sfa_info(self, records):
421
422         def startswith(prefix, values):
423             return [value for value in values if value.startswith(prefix)]
424
425         # get person ids
426         person_ids = []
427         site_ids = []
428         for record in records:
429             person_ids.extend(record.get("person_ids", []))
430             site_ids.extend(record.get("site_ids", [])) 
431             if 'site_id' in record:
432                 site_ids.append(record['site_id']) 
433         
434         # get all pis from the sites we've encountered
435         # and store them in a dictionary keyed on site_id 
436         site_pis = {}
437         if site_ids:
438             pi_filter = {'|roles': ['pi'], '|site_ids': site_ids} 
439             pi_list = self.shell.GetPersons(pi_filter, ['person_id', 'site_ids'])
440             for pi in pi_list:
441                 # we will need the pi's hrns also
442                 person_ids.append(pi['person_id'])
443                 
444                 # we also need to keep track of the sites these pis
445                 # belong to
446                 for site_id in pi['site_ids']:
447                     if site_id in site_pis:
448                         site_pis[site_id].append(pi)
449                     else:
450                         site_pis[site_id] = [pi]
451                  
452         # get sfa records for all records associated with these records.   
453         # we'll replace pl ids (person_ids) with hrns from the sfa records
454         # we obtain
455         
456         # get the registry records
457         person_list, persons = [], {}
458         person_list = dbsession.query (RegRecord).filter(RegRecord.pointer.in_(person_ids))
459         # create a hrns keyed on the sfa record's pointer.
460         # Its possible for multiple records to have the same pointer so
461         # the dict's value will be a list of hrns.
462         persons = defaultdict(list)
463         for person in person_list:
464             persons[person.pointer].append(person)
465
466         # get the pl records
467         pl_person_list, pl_persons = [], {}
468         pl_person_list = self.shell.GetPersons(person_ids, ['person_id', 'roles'])
469         pl_persons = list_to_dict(pl_person_list, 'person_id')
470
471         # fill sfa info
472         for record in records:
473             # skip records with no pl info (top level authorities)
474             #if record['pointer'] == -1:
475             #    continue 
476             sfa_info = {}
477             type = record['type']
478             logger.info("fill_record_sfa_info - incoming record typed %s"%type)
479             if (type == "slice"):
480                 # all slice users are researchers
481                 record['geni_urn'] = hrn_to_urn(record['hrn'], 'slice')
482                 record['PI'] = []
483                 record['researcher'] = []
484                 for person_id in record.get('person_ids', []):
485                     hrns = [person.hrn for person in persons[person_id]]
486                     record['researcher'].extend(hrns)                
487
488                 # pis at the slice's site
489                 if 'site_id' in record and record['site_id'] in site_pis:
490                     pl_pis = site_pis[record['site_id']]
491                     pi_ids = [pi['person_id'] for pi in pl_pis]
492                     for person_id in pi_ids:
493                         hrns = [person.hrn for person in persons[person_id]]
494                         record['PI'].extend(hrns)
495                         record['geni_creator'] = record['PI'] 
496                 
497             elif (type.startswith("authority")):
498                 record['url'] = None
499                 logger.info("fill_record_sfa_info - authority xherex")
500                 if record['pointer'] != -1:
501                     record['PI'] = []
502                     record['operator'] = []
503                     record['owner'] = []
504                     for pointer in record.get('person_ids', []):
505                         if pointer not in persons or pointer not in pl_persons:
506                             # this means there is not sfa or pl record for this user
507                             continue   
508                         hrns = [person.hrn for person in persons[pointer]] 
509                         roles = pl_persons[pointer]['roles']   
510                         if 'pi' in roles:
511                             record['PI'].extend(hrns)
512                         if 'tech' in roles:
513                             record['operator'].extend(hrns)
514                         if 'admin' in roles:
515                             record['owner'].extend(hrns)
516                         # xxx TODO: OrganizationName
517             elif (type == "node"):
518                 sfa_info['dns'] = record.get("hostname", "")
519                 # xxx TODO: URI, LatLong, IP, DNS
520     
521             elif (type == "user"):
522                 logger.info('setting user.email')
523                 sfa_info['email'] = record.get("email", "")
524                 sfa_info['geni_urn'] = hrn_to_urn(record['hrn'], 'user')
525                 sfa_info['geni_certificate'] = record['gid'] 
526                 # xxx TODO: PostalAddress, Phone
527             record.update(sfa_info)
528
529
530     ####################
531     # plcapi works by changes, compute what needs to be added/deleted
532     def update_relation (self, subject_type, target_type, relation_name, subject_id, target_ids):
533         # hard-wire the code for slice/user for now, could be smarter if needed
534         if subject_type =='slice' and target_type == 'user' and relation_name == 'researcher':
535             subject=self.shell.GetSlices (subject_id)[0]
536             current_target_ids = subject['person_ids']
537             add_target_ids = list ( set (target_ids).difference(current_target_ids))
538             del_target_ids = list ( set (current_target_ids).difference(target_ids))
539             logger.debug ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
540             for target_id in add_target_ids:
541                 self.shell.AddPersonToSlice (target_id,subject_id)
542                 logger.debug ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
543             for target_id in del_target_ids:
544                 logger.debug ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
545                 self.shell.DeletePersonFromSlice (target_id, subject_id)
546         elif subject_type == 'authority' and target_type == 'user' and relation_name == 'pi':
547             # due to the plcapi limitations this means essentially adding pi role to all people in the list
548             # it's tricky to remove any pi role here, although it might be desirable
549             persons = self.shell.GetPersons (target_ids)
550             for person in persons: 
551                 if 'pi' not in person['roles']:
552                     self.shell.AddRoleToPerson('pi',person['person_id'])
553         else:
554             logger.info('unexpected relation %s to maintain, %s -> %s'%(relation_name,subject_type,target_type))
555
556         
557     ########################################
558     ########## aggregate oriented
559     ########################################
560
561     def testbed_name (self): return "myplc"
562
563     def aggregate_version (self):
564         return {}
565
566     # first 2 args are None in case of resource discovery
567     def list_resources (self, version=None, options={}):
568         aggregate = PlAggregate(self)
569         rspec =  aggregate.list_resources(version=version, options=options)
570         return rspec
571
572     def describe(self, urns, version, options={}, allocation_status=None):
573         aggregate = PlAggregate(self)
574         return aggregate.describe(urns, version=version, options=options)
575     
576     def status (self, urns, options={}):
577         aggregate = PlAggregate(self)
578         desc =  aggregate.describe(urns)
579         status = {'geni_urn': desc['geni_urn'],
580                   'geni_slivers': desc['geni_slivers']}
581         return status
582
583     def allocate (self, urn, rspec_string, options={}):
584         xrn = Xrn(urn)
585         aggregate = PlAggregate(self)
586         slices = PlSlices(self)
587         peer = slices.get_peer(xrn.get_hrn())
588         sfa_peer = slices.get_sfa_peer(xrn.get_hrn())
589         slice_record=None    
590         users = options.get('geni_users', [])
591         if users:
592             slice_record = users[0].get('slice_record', {})
593     
594         # parse rspec
595         rspec = RSpec(rspec_string)
596         requested_attributes = rspec.version.get_slice_attributes()
597         
598         # ensure site record exists
599         site = slices.verify_site(xrn.hrn, slice_record, peer, sfa_peer, options=options)
600         # ensure slice record exists
601         slice = slices.verify_slice(xrn.hrn, slice_record, peer, sfa_peer, options=options)
602         # ensure person records exists
603         persons = slices.verify_persons(xrn.hrn, slice, users, peer, sfa_peer, options=options)
604         # ensure slice attributes exists
605         slices.verify_slice_attributes(slice, requested_attributes, options=options)
606         
607         # add/remove slice from nodes
608         requested_slivers = []
609         for node in rspec.version.get_nodes_with_slivers():
610             hostname = None
611             if node.get('component_name'):
612                 hostname = node.get('component_name').strip()
613             elif node.get('component_id'):
614                 hostname = xrn_to_hostname(node.get('component_id').strip())
615             if hostname:
616                 requested_slivers.append(hostname)
617         nodes = slices.verify_slice_nodes(slice, requested_slivers, peer)
618
619         # update all sliver allocation states setting then to geni_allocated   
620         sliver_ids = []
621         for node in nodes:
622             sliver_hrn = '%s.%s-%s' % (self.hrn, slice['slice_id'], node['node_id'])
623             sliver_id = Xrn(sliver_hrn, type='sliver').urn
624             sliver_ids.append(sliver_id)
625         SliverAllocation.set_allocations(sliver_ids, 'geni_allocated')
626          
627         # add/remove links links 
628         slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes)
629
630         # add/remove leases
631         requested_leases = []
632         kept_leases = []
633         for lease in rspec.version.get_leases():
634             requested_lease = {}
635             if not lease.get('lease_id'):
636                requested_lease['hostname'] = xrn_to_hostname(lease.get('component_id').strip())
637                requested_lease['start_time'] = lease.get('start_time')
638                requested_lease['duration'] = lease.get('duration')
639             else:
640                kept_leases.append(int(lease['lease_id']))
641             if requested_lease.get('hostname'):
642                 requested_leases.append(requested_lease)
643
644         leases = slices.verify_slice_leases(slice, requested_leases, kept_leases, peer)
645         # handle MyPLC peer association.
646         # only used by plc and ple.
647         slices.handle_peer(site, slice, persons, peer)
648         
649         return aggregate.describe([xrn.get_urn()], version=rspec.version)
650
651     def provision(self, urns, options={}):
652         # update sliver allocation states and set them to geni_provisioned
653         aggregate = PlAggregate(self)
654         slivers = aggregate.get_slivers(urns)
655         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
656         SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned')
657      
658         return self.describe(urns, None, options=options)
659
660     def delete(self, urns, options={}):
661         # collect sliver ids so we can update sliver allocation states after
662         # we remove the slivers.
663         aggregate = PlAggregate(self)
664         slivers = aggregate.get_slivers(urns)
665         slice_id = slivers[0]['slice_id'] 
666         node_ids = []
667         sliver_ids = []
668         for sliver in slivers:
669             node_ids.append(sliver['node_id'])
670             sliver_ids.append(sliver['sliver_id']) 
671
672         # determine if this is a peer slice
673         # xxx I wonder if this would not need to use PlSlices.get_peer instead 
674         # in which case plc.peers could be deprecated as this here
675         # is the only/last call to this last method in plc.peers
676         slice_hrn = PlXrn(auth=self.hrn, slicename=slivers[0]['name']).get_hrn()     
677         peer = peers.get_peer(self, slice_hrn)
678         try:
679             if peer:
680                 self.shell.UnBindObjectFromPeer('slice', slice_id, peer)
681  
682             self.shell.DeleteSliceFromNodes(slice_id, node_ids)
683  
684             # delete sliver allocation states
685             SliverAllocation.delete_allocations(sliver_ids)
686         finally:
687             if peer:
688                 self.shell.BindObjectToPeer('slice', slice_id, peer, slice['peer_slice_id'])
689
690         # prepare return struct
691         geni_slivers = []
692         for node_id in node_ids:
693             sliver_hrn = '%s.%s-%s' % (self.hrn, slice_id, node_id)
694             geni_slivers.append(
695                 {'geni_sliver_urn': Xrn(sliver_hrn, type='sliver').urn,
696                  'geni_allocation_status': 'geni_unallocated',
697                  'geni_expires': datetime_to_string(utcparse(slivers[0]['expires']))})  
698         return geni_slivers
699     
700     def renew (self, urns, expiration_time, options={}):
701         # we can only renew slices, not individual slivers. ignore sliver
702         # ids in the urn 
703         names = []
704         for urn in urns:
705             xrn = PlXrn(xrn=urn, type='slice')
706             names.append(xrn.pl_slicename())
707         slices = self.shell.GetSlices(names, ['slice_id'])
708         if not slices:
709             raise SearchFailed(urns)
710         slice = slices[0]
711         requested_time = utcparse(expiration_time)
712         record = {'expires': int(datetime_to_epoch(requested_time))}
713         self.shell.UpdateSlice(slice['slice_id'], record)
714         description = self.describe(urns, None, options)
715         return description['geni_slivers']
716             
717
718     def perform_operational_action (self, urns, action, options={}):
719         # MyPLC doesn't support operational actions. Lets pretend like it
720         # supports start, but reject everything else.
721         action = action.lower()
722         if action not in ['geni_start']:
723             raise UnsupportedOperation(action)
724
725         # fault if sliver is not full allocated (operational status is geni_pending_allocation)
726         description = self.describe(urns, None, options)
727         for sliver in description['geni_slivers']:
728             if sliver['geni_operational_status'] == 'geni_pending_allocation':
729                 raise UnsupportedOperation(action, "Sliver must be fully allocated (operational status is not geni_pending_allocation)")
730         #
731         # Perform Operational Action Here
732         #
733
734         geni_slivers = self.describe(urns, None, options)['geni_slivers']
735         return geni_slivers
736
737     # set the 'enabled' tag to 0
738     def shutdown (self, xrn, options={}):
739         xrn = PlXrn(xrn=xrn, type='slice')
740         slicename = xrn.pl_slicename()
741         slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
742         if not slices:
743             raise RecordNotFound(slice_hrn)
744         slice_id = slices[0]['slice_id']
745         slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'})
746         if not slice_tags:
747             self.shell.AddSliceTag(slice_id, 'enabled', '0')
748         elif slice_tags[0]['value'] != "0":
749             tag_id = slice_tags[0]['slice_tag_id']
750             self.shell.UpdateSliceTag(tag_id, '0')
751         return 1