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