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