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