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