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