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