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