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