Functional v3 version of Iotlab.
[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.testbed_shell.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         .. seealso:: node_to_rspec_node
372         """
373         rspec_node = self.node_to_rspec_node(sliver)
374         rspec_node['expires'] = datetime_to_string(utcparse(sliver['expires']))
375         # add sliver info
376         logger.debug("IOTLABAGGREGATE api \t  sliver_to_rspec_node sliver \
377                         %s \r\nsliver_allocations %s" % (sliver,
378                             sliver_allocations))
379         rspec_sliver = Sliver({'sliver_id': sliver['urn'],
380                          'name': sliver['slice_id'],
381                          'type': 'iotlab-exclusive',
382                          'tags': []})
383         rspec_node['sliver_id'] = rspec_sliver['sliver_id']
384
385         if sliver['urn'] in sliver_allocations:
386             rspec_node['client_id'] = sliver_allocations[
387                                                     sliver['urn']].client_id
388             if sliver_allocations[sliver['urn']].component_id:
389                 rspec_node['component_id'] = sliver_allocations[
390                                                     sliver['urn']].component_id
391         rspec_node['slivers'] = [rspec_sliver]
392
393         # slivers always provide the ssh service
394         login = Login({'authentication': 'ssh-keys',
395                        'hostname': sliver['hostname'],
396                        'port':'22',
397                        'username': sliver['slice_name'],
398                        'login': sliver['slice_name']
399                       })
400         return rspec_node
401
402
403     def get_all_leases(self, ldap_username):
404         """
405         Get list of lease dictionaries which all have the mandatory keys
406         ('lease_id', 'hostname', 'site_id', 'name', 'start_time', 'duration').
407         All the leases running or scheduled are returned.
408
409         :param ldap_username: if ldap uid is not None, looks for the leases
410             belonging to this user.
411         :type ldap_username: string
412         :returns: rspec lease dictionary with keys lease_id, component_id,
413             slice_id, start_time, duration where the lease_id is the oar job id,
414             component_id is the node's urn, slice_id is the slice urn,
415             start_time is the timestamp starting time and duration is expressed
416             in terms of the testbed's granularity.
417         :rtype: dict
418
419         .. note::There is no filtering of leases within a given time frame.
420             All the running or scheduled leases are returned. options
421             removed SA 15/05/2013
422
423
424         """
425
426         logger.debug("IOTLABAGGREGATE  get_all_leases ldap_username %s "
427                      % (ldap_username))
428         leases = self.driver.testbed_shell.GetLeases(login=ldap_username)
429         grain = self.driver.testbed_shell.GetLeaseGranularity()
430
431         rspec_leases = []
432         for lease in leases:
433             #as many leases as there are nodes in the job
434             for node in lease['reserved_nodes']:
435                 rspec_lease = Lease()
436                 rspec_lease['lease_id'] = lease['lease_id']
437                 #site = node['site_id']
438                 iotlab_xrn = xrn_object(self.driver.testbed_shell.root_auth,
439                                                node)
440                 rspec_lease['component_id'] = iotlab_xrn.urn
441                 #rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn,\
442                                         #site, node['hostname'])
443                 try:
444                     rspec_lease['slice_id'] = lease['slice_id']
445                 except KeyError:
446                     #No info on the slice used in testbed_xp table
447                     pass
448                 rspec_lease['start_time'] = lease['t_from']
449                 rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) \
450                      / grain
451                 rspec_leases.append(rspec_lease)
452         return rspec_leases
453
454     def get_rspec(self, slice_xrn=None, login=None, version=None,
455                   options=None):
456         """
457         Returns xml rspec:
458         - a full advertisement rspec with the testbed resources if slice_xrn is
459         not specified.If a lease option is given, also returns the leases
460         scheduled on the testbed.
461         - a manifest Rspec with the leases and nodes in slice's leases if
462         slice_xrn is not None.
463
464         :param slice_xrn: srn of the slice
465         :type slice_xrn: string
466         :param login: user'uid (ldap login) on iotlab
467         :type login: string
468         :param version: can be set to sfa or iotlab
469         :type version: RSpecVersion
470         :param options: used to specify if the leases should also be included in
471             the returned rspec.
472         :type options: dict
473
474         :returns: Xml Rspec.
475         :rtype: XML
476
477
478         """
479
480         ldap_username = None
481         rspec = None
482         version_manager = VersionManager()
483         version = version_manager.get_version(version)
484         logger.debug("IotlabAggregate \t get_rspec ***version %s \
485                     version.type %s  version.version %s options %s \r\n"
486                      % (version, version.type, version.version, options))
487
488         if slice_xrn is None:
489             rspec_version = version_manager._get_version(version.type,
490                                                          version.version, 'ad')
491
492         else:
493             rspec_version = version_manager._get_version(
494                 version.type, version.version, 'manifest')
495
496         slices, slivers = self.get_slice_and_slivers(slice_xrn, login)
497         if slice_xrn and slices is not None:
498             #Get user associated with this slice
499             #for one_slice in slices :
500             ldap_username = self.find_ldap_username_from_slice(slices[0])
501             # ldap_username = slices[0]['reg_researchers'][0].__dict__['hrn']
502             #  # ldap_username = slices[0]['user']
503             # tmp = ldap_username.split('.')
504             # ldap_username = tmp[1]
505             logger.debug("IotlabAggregate \tget_rspec **** \
506                     LDAP USERNAME %s \r\n" \
507                     % (ldap_username))
508         #at this point sliver may be empty if no iotlab job
509         #is running for this user/slice.
510         rspec = RSpec(version=rspec_version, user_options=options)
511
512         logger.debug("\r\n \r\n IotlabAggregate \tget_rspec *** \
513                       slice_xrn %s slices  %s\r\n \r\n"
514                      % (slice_xrn, slices))
515
516         if options is not None:
517             lease_option = options['list_leases']
518         else:
519             #If no options are specified, at least print the resources
520             lease_option = 'all'
521            #if slice_xrn :
522                #lease_option = 'all'
523
524         if lease_option in ['all', 'resources']:
525         #if not options.get('list_leases') or options.get('list_leases')
526         #and options['list_leases'] != 'leases':
527             nodes = self.get_nodes()
528             logger.debug("\r\n")
529             logger.debug("IotlabAggregate \t lease_option %s \
530                           get rspec  ******* nodes %s"
531                          % (lease_option, nodes))
532
533             sites_set = set([node['location']['site'] for node in nodes])
534
535             #In case creating a job,  slice_xrn is not set to None
536             rspec.version.add_nodes(nodes)
537             if slice_xrn and slices is not None:
538             #     #Get user associated with this slice
539             #     #for one_slice in slices :
540             #     ldap_username = slices[0]['reg_researchers']
541             #      # ldap_username = slices[0]['user']
542             #     tmp = ldap_username.split('.')
543             #     ldap_username = tmp[1]
544             #      # ldap_username = tmp[1].split('_')[0]
545
546                 logger.debug("IotlabAggregate \tget_rspec **** \
547                         version type %s ldap_ user %s \r\n" \
548                         % (version.type, ldap_username))
549                 if version.type == "Iotlab":
550                     rspec.version.add_connection_information(
551                         ldap_username, sites_set)
552
553             default_sliver = slivers.get('default_sliver', [])
554             if default_sliver and len(nodes) is not 0:
555                 #default_sliver_attribs = default_sliver.get('tags', [])
556                 logger.debug("IotlabAggregate \tget_rspec **** \
557                         default_sliver%s \r\n" % (default_sliver))
558                 for attrib in default_sliver:
559                     rspec.version.add_default_sliver_attribute(
560                         attrib, default_sliver[attrib])
561
562         if lease_option in ['all','leases']:
563             leases = self.get_all_leases(ldap_username)
564             rspec.version.add_leases(leases)
565             logger.debug("IotlabAggregate \tget_rspec **** \
566                        FINAL RSPEC %s \r\n" % (rspec.toxml()))
567         return rspec.toxml()
568
569     def get_slivers(self, urns, options={}):
570         """Get slivers of the given slice urns. Slivers contains slice, node and
571         user information.
572
573         For Iotlab, returns the leases with sliver ids and their allocation
574         status.
575
576         :param urns: list of  slice urns.
577         :type urns: list of strings
578         :param options: unused
579         :type options: unused
580
581         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
582         """
583
584
585         slice_ids = set()
586         node_ids = []
587         for urn in urns:
588             xrn = IotlabXrn(xrn=urn)
589             if xrn.type == 'sliver':
590                  # id: slice_id-node_id
591                 try:
592                     sliver_id_parts = xrn.get_sliver_id_parts()
593                     slice_id = int(sliver_id_parts[0])
594                     node_id = int(sliver_id_parts[1])
595                     slice_ids.add(slice_id)
596                     node_ids.append(node_id)
597                 except ValueError:
598                     pass
599             else:
600                 slice_names = set()
601                 slice_names.add(xrn.hrn)
602
603
604         logger.debug("IotlabAggregate \t get_slivers urns %s slice_ids %s \
605                        node_ids %s\r\n" % (urns, slice_ids, node_ids))
606         logger.debug("IotlabAggregate \t get_slivers xrn %s slice_names %s \
607                        \r\n" % (xrn, slice_names))
608         filter_sliver = {}
609         if slice_names:
610             filter_sliver['slice_hrn'] = list(slice_names)
611             slice_hrn = filter_sliver['slice_hrn'][0]
612
613             slice_filter_type = 'slice_hrn'
614
615         # if slice_ids:
616         #     filter['slice_id'] = list(slice_ids)
617         # # get slices
618         if slice_hrn:
619             slices = self.driver.testbed_shell.GetSlices(slice_hrn,
620                 slice_filter_type)
621             leases = self.driver.testbed_shell.GetLeases(
622                                                 {'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}