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