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