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