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