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