25b1445ad942527c3a90b446a88b0386ce2125c2
[sfa.git] / sfa / iotlab / iotlabaggregate.py
1 """
2 File providing methods to generate valid RSpecs for the Iotlab testbed.
3 Contains methods to get information on slice, slivers, nodes and leases,
4 formatting them and turn it into a RSpec.
5 """
6 from sfa.util.sfatime import utcparse, datetime_to_string
7 from sfa.util.xrn import Xrn, hrn_to_urn, urn_to_hrn
8 from sfa.iotlab.iotlabxrn import IotlabXrn
9 from sfa.rspecs.rspec import RSpec
10 #from sfa.rspecs.elements.location import Location
11 from sfa.rspecs.elements.hardware_type import HardwareType
12 from sfa.rspecs.elements.login import Login
13 # from sfa.rspecs.elements.services import ServicesElement
14 from sfa.rspecs.elements.sliver import Sliver
15 from sfa.rspecs.elements.lease import Lease
16 from sfa.rspecs.elements.granularity import Granularity
17 from sfa.rspecs.version_manager import VersionManager
18 from sfa.storage.model import SliverAllocation
19 from sfa.rspecs.elements.versions.iotlabv1Node import IotlabPosition, \
20     IotlabNode, IotlabLocation
21 from sfa.iotlab.iotlabxrn import xrn_object
22 from sfa.util.sfalogging import logger
23 import time
24
25 class IotlabAggregate:
26     """Aggregate manager class for Iotlab. """
27
28     sites = {}
29     nodes = {}
30     api = None
31     interfaces = {}
32     links = {}
33     node_tags = {}
34
35     prepared = False
36
37     user_options = {}
38
39     def __init__(self, driver):
40         self.driver = driver
41
42     def get_slice_and_slivers(self, slice_xrn, login=None):
43         """
44         Get the slices and the associated leases if any from the iotlab
45             testbed. One slice can have mutliple leases.
46             For each slice, get the nodes in the  associated lease
47             and create a sliver with the necessary info and insert it into the
48             sliver dictionary, keyed on the node hostnames.
49             Returns a dict of slivers based on the sliver's node_id.
50             Called by get_rspec.
51
52
53         :param slice_xrn: xrn of the slice
54         :param login: user's login on iotlab ldap
55
56         :type slice_xrn: string
57         :type login: string
58         :returns: a list of slices dict and a list of Sliver object
59         :rtype: (list, list)
60
61         .. note: There is no real slivers in iotlab, only leases. The goal
62             is to be consistent with the SFA standard.
63
64         """
65         slivers = {}
66         sfa_slice = None
67         if slice_xrn is None:
68             return (sfa_slice, slivers)
69         slice_urn = hrn_to_urn(slice_xrn, 'slice')
70         slice_hrn, _ = urn_to_hrn(slice_xrn)
71
72         # GetSlices always returns a list, even if there is only one element
73         slices = self.driver.GetSlices(
74                                         slice_filter=str(slice_hrn),
75                                         slice_filter_type='slice_hrn',
76                                         login=login)
77
78         logger.debug("IotlabAggregate api \tget_slice_and_slivers \
79                       slice_hrn %s \r\n slices %s self.driver.hrn %s"
80                      % (slice_hrn, slices, self.driver.hrn))
81         if slices == []:
82             return (sfa_slice, slivers)
83
84         # sort slivers by node id , if there is a job
85         #and therefore, node allocated to this slice
86         # for sfa_slice in slices:
87         sfa_slice = slices[0]
88         try:
89             node_ids_list = sfa_slice['node_ids']
90         except KeyError:
91             logger.log_exc("IOTLABAGGREGATE \t \
92                         get_slice_and_slivers No nodes in the slice \
93                         - KeyError ")
94             node_ids_list = []
95             # continue
96
97         for node in node_ids_list:
98             sliver_xrn = Xrn(slice_urn, type='sliver', id=node)
99             sliver_xrn.set_authority(self.driver.hrn)
100             sliver = Sliver({'sliver_id': sliver_xrn.urn,
101                             'name': sfa_slice['hrn'],
102                             'type': 'iotlab-node',
103                             'tags': []})
104
105             slivers[node] = sliver
106
107         #Add default sliver attribute :
108         #connection information for iotlab
109         # if get_authority(sfa_slice['hrn']) == \
110             # self.driver.testbed_shell.root_auth:
111         #     tmp = sfa_slice['hrn'].split('.')
112         #     ldap_username = tmp[1].split('_')[0]
113         #     ssh_access = None
114         #     slivers['default_sliver'] = {'ssh': ssh_access,
115         #                                  'login': ldap_username}
116         # look in ldap:
117         ldap_username = self.find_ldap_username_from_slice(sfa_slice)
118
119         if ldap_username is not None:
120             ssh_access = None
121             slivers['default_sliver'] = {'ssh': ssh_access,
122                                              'login': ldap_username}
123
124
125         logger.debug("IOTLABAGGREGATE api get_slice_and_slivers  slivers %s "
126                      % (slivers))
127         return (slices, slivers)
128
129     def find_ldap_username_from_slice(self, sfa_slice):
130         """
131         Gets the ldap username of the user based on the information contained
132         in ist sfa_slice record.
133
134         :param sfa_slice: the user's slice record. Must contain the
135             reg_researchers key.
136         :type sfa_slice: dictionary
137         :returns: ldap_username, the ldap user's login.
138         :rtype: string
139
140         """
141         researchers = [sfa_slice['reg_researchers'][0].__dict__]
142         # look in ldap:
143         ldap_username = None
144         ret =  self.driver.testbed_shell.GetPersons(researchers)
145         if len(ret) != 0:
146             ldap_username = ret[0]['uid']
147
148         return ldap_username
149
150
151
152     def get_nodes(self, options=None):
153     # def node_to_rspec_node(self, node, sites, node_tags,
154     #     grain=None, options={}):
155         """Returns the nodes in the slice using the rspec format, with all the
156         nodes' properties.
157
158         Fetch the nodes ids in the slices dictionary and get all the nodes
159         properties from OAR. Makes a rspec dicitonary out of this and returns
160         it. If the slice does not have any job running or scheduled, that is
161         it has no reserved nodes, then returns an empty list.
162
163         :returns: An empty list if the slice has no reserved nodes, a rspec
164             list with all the nodes and their properties (a dict per node)
165             otherwise.
166         :rtype: list
167
168         .. seealso:: get_slice_and_slivers
169
170         """
171
172         # NT: the semantic of this function is not clear to me :
173         # if slice is not defined, then all the nodes should be returned
174         # if slice is defined, we should return only the nodes that
175         # are part of this slice
176         # but what is the role of the slivers parameter ?
177         # So i assume that slice['node_ids'] will be the same as slivers for us
178         filter_nodes = None
179         if options:
180             geni_available = options.get('geni_available')
181             if geni_available == True:
182                 filter_nodes['boot_state'] = ['Alive']
183
184         # slice_nodes_list = []
185         # if slices is not None:
186         #     for one_slice in slices:
187         #         try:
188         #             slice_nodes_list = one_slice['node_ids']
189     #              # if we are dealing with a slice that has no node just
190     #              # return an empty list. In iotlab a slice can have multiple
191     #              # jobs scheduled, so it either has at least one lease or
192     #              # not at all.
193         #         except KeyError:
194         #             return []
195
196         # get the granularity in second for the reservation system
197         # grain = self.driver.testbed_shell.GetLeaseGranularity()
198
199         nodes = self.driver.testbed_shell.GetNodes(node_filter_dict =
200                                                     filter_nodes)
201
202         nodes_dict = {}
203
204         #if slices, this means we got to list all the nodes given to this slice
205         # Make a list of all the nodes in the slice before getting their
206         #attributes
207         # rspec_nodes = []
208
209         # logger.debug("IOTLABAGGREGATE api get_nodes slices  %s "
210                      # % (slices))
211
212         # reserved_nodes = self.driver.testbed_shell.GetNodesCurrentlyInUse()
213         # logger.debug("IOTLABAGGREGATE api get_nodes slice_nodes_list  %s "
214                      # % (slice_nodes_list))
215         for node in nodes:
216             nodes_dict[node['node_id']] = node
217
218         return nodes_dict
219
220     def node_to_rspec_node(self, node):
221         """ Creates a rspec node structure with the appropriate information
222         based on the node information that can be found in the node dictionary.
223
224         :param node: node data. this dict contains information about the node
225             and must have the following keys : mobile, radio, archi, hostname,
226             boot_state, site, x, y ,z (position).
227         :type node: dictionary.
228
229         :returns: node dictionary containing the following keys : mobile, archi,
230             radio, component_id, component_name, component_manager_id,
231             authority_id, boot_state, exclusive, hardware_types, location,
232             position, granularity, tags.
233         :rtype: dict
234
235         """
236
237         grain = self.driver.testbed_shell.GetLeaseGranularity()
238
239         rspec_node = IotlabNode()
240         # xxx how to retrieve site['login_base']
241         #site_id=node['site_id']
242         #site=sites_dict[site_id]
243
244         rspec_node['mobile'] = node['mobile']
245         rspec_node['archi'] = node['archi']
246         rspec_node['radio'] = node['radio']
247
248         iotlab_xrn = xrn_object(self.driver.testbed_shell.root_auth,
249                                        node['hostname'])
250         rspec_node['component_id'] = iotlab_xrn.urn
251         rspec_node['component_name'] = node['hostname']
252         rspec_node['component_manager_id'] = \
253                         hrn_to_urn(self.driver.testbed_shell.root_auth,
254                         'authority+sa')
255
256         # Iotlab's nodes are federated : there is only one authority
257         # for all Iotlab sites, registered in SFA.
258         # Removing the part including the site
259         # in authority_id SA 27/07/12
260         rspec_node['authority_id'] = rspec_node['component_manager_id']
261
262         # do not include boot state (<available> element)
263         #in the manifest rspec
264
265
266         rspec_node['boot_state'] = node['boot_state']
267         # if node['hostname'] in reserved_nodes:
268         #     rspec_node['boot_state'] = "Reserved"
269         rspec_node['exclusive'] = 'true'
270         rspec_node['hardware_types'] = [HardwareType({'name': \
271                                         'iotlab-node'})]
272
273         location = IotlabLocation({'country':'France', 'site': \
274                                     node['site']})
275         rspec_node['location'] = location
276
277
278         position = IotlabPosition()
279         for field in position :
280             try:
281                 position[field] = node[field]
282             except KeyError, error :
283                 logger.log_exc("IOTLABAGGREGATE\t get_nodes \
284                                                 position %s "% (error))
285
286         rspec_node['position'] = position
287
288
289         # Granularity
290         granularity = Granularity({'grain': grain})
291         rspec_node['granularity'] = granularity
292         rspec_node['tags'] = []
293         # if node['hostname'] in slivers:
294         #     # add sliver info
295         #     sliver = slivers[node['hostname']]
296         #     rspec_node['sliver_id'] = sliver['sliver_id']
297         #     rspec_node['client_id'] = node['hostname']
298         #     rspec_node['slivers'] = [sliver]
299
300         #     # slivers always provide the ssh service
301         #     login = Login({'authentication': 'ssh-keys', \
302         #             'hostname': node['hostname'], 'port':'22', \
303         #             'username': sliver['name']})
304         #     service = Services({'login': login})
305         #     rspec_node['services'] = [service]
306
307         return rspec_node
308
309
310     def rspec_node_to_geni_sliver(self, rspec_node, sliver_allocations = {}):
311         """Makes a geni sliver structure from all the nodes allocated
312         to slivers in the sliver_allocations dictionary. Returns the states
313         of the sliver.
314
315         :param rspec_node: Node information contained in a rspec data structure
316             fashion.
317         :type rspec_node: dictionary
318         :param sliver_allocations:
319         :type sliver_allocations: dictionary
320
321         :returns: Dictionary with the following keys: geni_sliver_urn,
322             geni_expires, geni_allocation_status, geni_operational_status,
323             geni_error.
324
325         :rtype: dictionary
326
327         .. seealso:: node_to_rspec_node
328
329         """
330         if rspec_node['sliver_id'] in sliver_allocations:
331             # set sliver allocation and operational status
332             sliver_allocation = sliver_allocations[rspec_node['sliver_id']]
333             if sliver_allocation:
334                 allocation_status = sliver_allocation.allocation_state
335                 if allocation_status == 'geni_allocated':
336                     op_status =  'geni_pending_allocation'
337                 elif allocation_status == 'geni_provisioned':
338                     op_status = 'geni_ready'
339                 else:
340                     op_status = 'geni_unknown'
341             else:
342                 allocation_status = 'geni_unallocated'
343         else:
344             allocation_status = 'geni_unallocated'
345             op_status = 'geni_failed'
346         # required fields
347         geni_sliver = {'geni_sliver_urn': rspec_node['sliver_id'],
348                        'geni_expires': rspec_node['expires'],
349                        'geni_allocation_status' : allocation_status,
350                        'geni_operational_status': op_status,
351                        'geni_error': '',
352                        }
353         return geni_sliver
354
355
356     def sliver_to_rspec_node(self, sliver, sliver_allocations):
357         """Used by describe to format node information into a rspec compliant
358         structure.
359
360         Creates a node rspec compliant structure by calling node_to_rspec_node.
361         Adds slivers, if any, to rspec node structure. Returns the updated
362         rspec node struct.
363
364         :param sliver: sliver dictionary. Contains keys: urn, slice_id, hostname
365             and slice_name.
366         :type sliver: dictionary
367         :param sliver_allocations: dictionary of slivers
368         :type sliver_allocations: dict
369
370         :returns: Node dictionary with all necessary data.
371
372         .. seealso:: node_to_rspec_node
373         """
374         rspec_node = self.node_to_rspec_node(sliver)
375         rspec_node['expires'] = datetime_to_string(utcparse(sliver['expires']))
376         # add sliver info
377         logger.debug("IOTLABAGGREGATE api \t  sliver_to_rspec_node sliver \
378                         %s \r\nsliver_allocations %s" % (sliver,
379                             sliver_allocations))
380         rspec_sliver = Sliver({'sliver_id': sliver['urn'],
381                          'name': sliver['slice_id'],
382                          'type': 'iotlab-exclusive',
383                          'tags': []})
384         rspec_node['sliver_id'] = rspec_sliver['sliver_id']
385
386         if sliver['urn'] in sliver_allocations:
387             rspec_node['client_id'] = sliver_allocations[
388                                                     sliver['urn']].client_id
389             if sliver_allocations[sliver['urn']].component_id:
390                 rspec_node['component_id'] = sliver_allocations[
391                                                     sliver['urn']].component_id
392         rspec_node['slivers'] = [rspec_sliver]
393
394         # slivers always provide the ssh service
395         login = Login({'authentication': 'ssh-keys',
396                        'hostname': sliver['hostname'],
397                        'port':'22',
398                        'username': sliver['slice_name'],
399                        'login': sliver['slice_name']
400                       })
401         return rspec_node
402
403
404     def get_all_leases(self, ldap_username):
405         """
406         Get list of lease dictionaries which all have the mandatory keys
407         ('lease_id', 'hostname', 'site_id', 'name', 'start_time', 'duration').
408         All the leases running or scheduled are returned.
409
410         :param ldap_username: if ldap uid is not None, looks for the leases
411             belonging to this user.
412         :type ldap_username: string
413         :returns: rspec lease dictionary with keys lease_id, component_id,
414             slice_id, start_time, duration where the lease_id is the oar job id,
415             component_id is the node's urn, slice_id is the slice urn,
416             start_time is the timestamp starting time and duration is expressed
417             in terms of the testbed's granularity.
418         :rtype: dict
419
420         .. note::There is no filtering of leases within a given time frame.
421             All the running or scheduled leases are returned. options
422             removed SA 15/05/2013
423
424
425         """
426
427         logger.debug("IOTLABAGGREGATE  get_all_leases ldap_username %s "
428                      % (ldap_username))
429         leases = self.driver.GetLeases(login=ldap_username)
430         grain = self.driver.testbed_shell.GetLeaseGranularity()
431
432         rspec_leases = []
433         for lease in leases:
434             #as many leases as there are nodes in the job
435             for node in lease['reserved_nodes']:
436                 rspec_lease = Lease()
437                 rspec_lease['lease_id'] = lease['lease_id']
438                 #site = node['site_id']
439                 iotlab_xrn = xrn_object(self.driver.testbed_shell.root_auth,
440                                                node)
441                 rspec_lease['component_id'] = iotlab_xrn.urn
442                 #rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn,\
443                                         #site, node['hostname'])
444                 try:
445                     rspec_lease['slice_id'] = lease['slice_id']
446                 except KeyError:
447                     #No info on the slice used in testbed_xp table
448                     pass
449                 rspec_lease['start_time'] = lease['t_from']
450                 rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) \
451                      / grain
452                 rspec_leases.append(rspec_lease)
453         return rspec_leases
454
455     def get_rspec(self, slice_xrn=None, login=None, version=None,
456                   options=None):
457         """
458         Returns xml rspec:
459         - a full advertisement rspec with the testbed resources if slice_xrn is
460         not specified.If a lease option is given, also returns the leases
461         scheduled on the testbed.
462         - a manifest Rspec with the leases and nodes in slice's leases if
463         slice_xrn is not None.
464
465         :param slice_xrn: srn of the slice
466         :type slice_xrn: string
467         :param login: user'uid (ldap login) on iotlab
468         :type login: string
469         :param version: can be set to sfa or iotlab
470         :type version: RSpecVersion
471         :param options: used to specify if the leases should also be included in
472             the returned rspec.
473         :type options: dict
474
475         :returns: Xml Rspec.
476         :rtype: XML
477
478
479         """
480
481         ldap_username = None
482         rspec = None
483         version_manager = VersionManager()
484         version = version_manager.get_version(version)
485         logger.debug("IotlabAggregate \t get_rspec ***version %s \
486                     version.type %s  version.version %s options %s \r\n"
487                      % (version, version.type, version.version, options))
488
489         if slice_xrn is None:
490             rspec_version = version_manager._get_version(version.type,
491                                                          version.version, 'ad')
492
493         else:
494             rspec_version = version_manager._get_version(
495                 version.type, version.version, 'manifest')
496
497         slices, slivers = self.get_slice_and_slivers(slice_xrn, login)
498         if slice_xrn and slices is not None:
499             #Get user associated with this slice
500             #for one_slice in slices :
501             ldap_username = self.find_ldap_username_from_slice(slices[0])
502             # ldap_username = slices[0]['reg_researchers'][0].__dict__['hrn']
503             #  # ldap_username = slices[0]['user']
504             # tmp = ldap_username.split('.')
505             # ldap_username = tmp[1]
506             logger.debug("IotlabAggregate \tget_rspec **** \
507                     LDAP USERNAME %s \r\n" \
508                     % (ldap_username))
509         #at this point sliver may be empty if no iotlab job
510         #is running for this user/slice.
511         rspec = RSpec(version=rspec_version, user_options=options)
512
513         logger.debug("\r\n \r\n IotlabAggregate \tget_rspec *** \
514                       slice_xrn %s slices  %s\r\n \r\n"
515                      % (slice_xrn, slices))
516
517         if options is not None:
518             lease_option = options['list_leases']
519         else:
520             #If no options are specified, at least print the resources
521             lease_option = 'all'
522            #if slice_xrn :
523                #lease_option = 'all'
524
525         if lease_option in ['all', 'resources']:
526         #if not options.get('list_leases') or options.get('list_leases')
527         #and options['list_leases'] != 'leases':
528             nodes = self.get_nodes()
529             logger.debug("\r\n")
530             logger.debug("IotlabAggregate \t lease_option %s \
531                           get rspec  ******* nodes %s"
532                          % (lease_option, nodes))
533
534             sites_set = set([node['location']['site'] for node in nodes])
535
536             #In case creating a job,  slice_xrn is not set to None
537             rspec.version.add_nodes(nodes)
538             if slice_xrn and slices is not None:
539             #     #Get user associated with this slice
540             #     #for one_slice in slices :
541             #     ldap_username = slices[0]['reg_researchers']
542             #      # ldap_username = slices[0]['user']
543             #     tmp = ldap_username.split('.')
544             #     ldap_username = tmp[1]
545             #      # ldap_username = tmp[1].split('_')[0]
546
547                 logger.debug("IotlabAggregate \tget_rspec **** \
548                         version type %s ldap_ user %s \r\n" \
549                         % (version.type, ldap_username))
550                 if version.type == "Iotlab":
551                     rspec.version.add_connection_information(
552                         ldap_username, sites_set)
553
554             default_sliver = slivers.get('default_sliver', [])
555             if default_sliver and len(nodes) is not 0:
556                 #default_sliver_attribs = default_sliver.get('tags', [])
557                 logger.debug("IotlabAggregate \tget_rspec **** \
558                         default_sliver%s \r\n" % (default_sliver))
559                 for attrib in default_sliver:
560                     rspec.version.add_default_sliver_attribute(
561                         attrib, default_sliver[attrib])
562
563         if lease_option in ['all','leases']:
564             leases = self.get_all_leases(ldap_username)
565             rspec.version.add_leases(leases)
566             logger.debug("IotlabAggregate \tget_rspec **** \
567                        FINAL RSPEC %s \r\n" % (rspec.toxml()))
568         return rspec.toxml()
569
570     def get_slivers(self, urns, options={}):
571         """Get slivers of the given slice urns. Slivers contains slice, node and
572         user information.
573
574         For Iotlab, returns the leases with sliver ids and their allocation
575         status.
576
577         :param urns: list of  slice urns.
578         :type urns: list of strings
579         :param options: unused
580         :type options: unused
581
582         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
583         """
584
585
586         slice_ids = set()
587         node_ids = []
588         for urn in urns:
589             xrn = IotlabXrn(xrn=urn)
590             if xrn.type == 'sliver':
591                  # id: slice_id-node_id
592                 try:
593                     sliver_id_parts = xrn.get_sliver_id_parts()
594                     slice_id = int(sliver_id_parts[0])
595                     node_id = int(sliver_id_parts[1])
596                     slice_ids.add(slice_id)
597                     node_ids.append(node_id)
598                 except ValueError:
599                     pass
600             else:
601                 slice_names = set()
602                 slice_names.add(xrn.hrn)
603
604
605         logger.debug("IotlabAggregate \t get_slivers urns %s slice_ids %s \
606                        node_ids %s\r\n" % (urns, slice_ids, node_ids))
607         logger.debug("IotlabAggregate \t get_slivers xrn %s slice_names %s \
608                        \r\n" % (xrn, slice_names))
609         filter_sliver = {}
610         if slice_names:
611             filter_sliver['slice_hrn'] = list(slice_names)
612             slice_hrn = filter_sliver['slice_hrn'][0]
613
614             slice_filter_type = 'slice_hrn'
615
616         # if slice_ids:
617         #     filter['slice_id'] = list(slice_ids)
618         # # get slices
619         if slice_hrn:
620             slices = self.driver.GetSlices(slice_hrn,
621                 slice_filter_type)
622             leases = self.driver.GetLeases({'slice_hrn':slice_hrn})
623         logger.debug("IotlabAggregate \t get_slivers \
624                        slices %s leases %s\r\n" % (slices, leases ))
625         if not slices:
626             return []
627
628         single_slice = slices[0]
629         # get sliver users
630         user = single_slice['reg_researchers'][0].__dict__
631         logger.debug("IotlabAggregate \t get_slivers user %s \
632                        \r\n" % (user))
633
634         # construct user key info
635         person = self.driver.testbed_shell.ldap.LdapFindUser(record=user)
636         logger.debug("IotlabAggregate \t get_slivers person %s \
637                        \r\n" % (person))
638         # name = person['last_name']
639         user['login'] = person['uid']
640         user['user_urn'] = hrn_to_urn(user['hrn'], 'user')
641         user['keys'] = person['pkey']
642
643
644         try:
645             node_ids = single_slice['node_ids']
646             node_list = self.driver.testbed_shell.GetNodes(
647                     {'hostname':single_slice['node_ids']})
648             node_by_hostname = dict([(node['hostname'], node)
649                                         for node in node_list])
650         except KeyError:
651             logger.warning("\t get_slivers No slivers in slice")
652             # slice['node_ids'] = node_ids
653         # nodes_dict = self.get_slice_nodes(slice, options)
654
655         slivers = []
656         for current_lease in leases:
657             for hostname in current_lease['reserved_nodes']:
658                 node = {}
659                 node['slice_id'] = current_lease['slice_id']
660                 node['slice_hrn'] = current_lease['slice_hrn']
661                 slice_name = current_lease['slice_hrn'].split(".")[1]
662                 node['slice_name'] = slice_name
663                 index = current_lease['reserved_nodes'].index(hostname)
664                 node_id = current_lease['resource_ids'][index]
665                 # node['slice_name'] = user['login']
666                 # node.update(single_slice)
667                 more_info = node_by_hostname[hostname]
668                 node.update(more_info)
669                 # oar_job_id is the slice_id (lease_id)
670                 sliver_hrn = '%s.%s-%s' % (self.driver.hrn,
671                             current_lease['lease_id'], node_id)
672                 node['node_id'] = node_id
673                 node['expires'] = current_lease['t_until']
674                 node['sliver_id'] = Xrn(sliver_hrn, type='sliver').urn
675                 node['urn'] = node['sliver_id']
676                 node['services_user'] = [user]
677
678                 slivers.append(node)
679         return slivers
680
681     def list_resources(self, version = None, options={}):
682         """
683         Returns an advertisement Rspec of available resources at this
684         aggregate. This Rspec contains a resource listing along with their
685         description, providing sufficient information for clients to be able to
686         select among available resources.
687
688         :param options: various options. The valid options are: {boolean
689             geni_compressed <optional>; struct geni_rspec_version { string type;
690             #case insensitive , string version; # case insensitive}} . The only
691             mandatory options if options is specified is geni_rspec_version.
692         :type options: dictionary
693
694         :returns: On success, the value field of the return struct will contain
695             a geni.rspec advertisment RSpec
696         :rtype: Rspec advertisement in xml.
697
698         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#RSpecdatatype
699         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#ListResources
700         """
701
702         version_manager = VersionManager()
703         version = version_manager.get_version(version)
704         rspec_version = version_manager._get_version(version.type,
705                                                     version.version, 'ad')
706         rspec = RSpec(version=rspec_version, user_options=options)
707         # variable ldap_username to be compliant with  get_all_leases
708         # prototype. Now unused in geni-v3 since we are getting all the leases
709         # here
710         ldap_username = None
711         if not options.get('list_leases') or options['list_leases'] != 'leases':
712             # get nodes
713             nodes_dict  = self.get_nodes(options)
714
715             # no interfaces on iotlab nodes
716             # convert nodes to rspec nodes
717             rspec_nodes = []
718             for node_id in nodes_dict:
719                 node = nodes_dict[node_id]
720                 rspec_node = self.node_to_rspec_node(node)
721                 rspec_nodes.append(rspec_node)
722             rspec.version.add_nodes(rspec_nodes)
723
724             # add links
725             # links = self.get_links(sites, nodes_dict, interfaces)
726             # rspec.version.add_links(links)
727
728         if not options.get('list_leases') or options.get('list_leases') \
729             and options['list_leases'] != 'resources':
730             leases = self.get_all_leases(ldap_username)
731             rspec.version.add_leases(leases)
732
733         return rspec.toxml()
734
735
736     def describe(self, urns, version=None, options={}):
737         """
738         Retrieve a manifest RSpec describing the resources contained by the
739         named entities, e.g. a single slice or a set of the slivers in a slice.
740         This listing and description should be sufficiently descriptive to allow
741         experimenters to use the resources.
742
743         :param urns: If a slice urn is supplied and there are no slivers in the
744             given slice at this aggregate, then geni_rspec shall be a valid
745             manifest RSpec, containing no node elements - no resources.
746         :type urns: list  or strings
747         :param options: various options. the valid options are: {boolean
748             geni_compressed <optional>; struct geni_rspec_version { string type;
749             #case insensitive , string version; # case insensitive}}
750         :type options: dictionary
751
752         :returns: On success returns the following dictionary {geni_rspec:
753             <geni.rspec, a Manifest RSpec>, geni_urn: <string slice urn of the
754             containing slice>, geni_slivers:{ geni_sliver_urn:
755             <string sliver urn>, geni_expires:  <dateTime.rfc3339 allocation
756             expiration string, as in geni_expires from SliversStatus>,
757             geni_allocation_status: <string sliver state - e.g. geni_allocated
758             or geni_provisioned >, geni_operational_status:
759             <string sliver operational state>, geni_error: <optional string.
760             The field may be omitted entirely but may not be null/None,
761             explaining any failure for a sliver.>}
762
763         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#Describe
764         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
765         """
766         version_manager = VersionManager()
767         version = version_manager.get_version(version)
768         rspec_version = version_manager._get_version(
769                                     version.type, version.version, 'manifest')
770         rspec = RSpec(version=rspec_version, user_options=options)
771
772         # get slivers
773         geni_slivers = []
774         slivers = self.get_slivers(urns, options)
775         if slivers:
776             rspec_expires = datetime_to_string(utcparse(slivers[0]['expires']))
777         else:
778             rspec_expires = datetime_to_string(utcparse(time.time()))
779         rspec.xml.set('expires',  rspec_expires)
780
781         # lookup the sliver allocations
782         geni_urn = urns[0]
783         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
784         logger.debug(" IOTLAB_API.PY \tDescribe  sliver_ids %s "
785                      % (sliver_ids))
786         constraint = SliverAllocation.sliver_id.in_(sliver_ids)
787         query = self.driver.api.dbsession().query(SliverAllocation)
788         sliver_allocations = query.filter((constraint)).all()
789         logger.debug(" IOTLAB_API.PY \tDescribe  sliver_allocations %s "
790                      % (sliver_allocations))
791         sliver_allocation_dict = {}
792         for sliver_allocation in sliver_allocations:
793             geni_urn = sliver_allocation.slice_urn
794             sliver_allocation_dict[sliver_allocation.sliver_id] = \
795                                                             sliver_allocation
796
797         # add slivers
798         nodes_dict = {}
799         for sliver in slivers:
800             nodes_dict[sliver['node_id']] = sliver
801         rspec_nodes = []
802         for sliver in slivers:
803             rspec_node = self.sliver_to_rspec_node(sliver,
804                                                     sliver_allocation_dict)
805             rspec_nodes.append(rspec_node)
806             logger.debug(" IOTLAB_API.PY \tDescribe  sliver_allocation_dict %s "
807                      % (sliver_allocation_dict))
808             geni_sliver = self.rspec_node_to_geni_sliver(rspec_node,
809                             sliver_allocation_dict)
810             geni_slivers.append(geni_sliver)
811
812         logger.debug(" IOTLAB_API.PY \tDescribe rspec_nodes %s\
813                         rspec %s "
814                      % (rspec_nodes, rspec))
815         rspec.version.add_nodes(rspec_nodes)
816
817         return {'geni_urn': geni_urn,
818                 'geni_rspec': rspec.toxml(),
819                 'geni_slivers': geni_slivers}