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