Merge branch 'geni-v3' of ssh://git.onelab.eu/git/sfa into geni-v3
[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         .. 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.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.GetSlices(slice_hrn,
620                 slice_filter_type)
621             leases = self.driver.GetLeases({'slice_hrn':slice_hrn})
622         logger.debug("IotlabAggregate \t get_slivers \
623                        slices %s leases %s\r\n" % (slices, leases ))
624         if not slices:
625             return []
626
627         single_slice = slices[0]
628         # get sliver users
629         user = single_slice['reg_researchers'][0].__dict__
630         logger.debug("IotlabAggregate \t get_slivers user %s \
631                        \r\n" % (user))
632
633         # construct user key info
634         person = self.driver.testbed_shell.ldap.LdapFindUser(record=user)
635         logger.debug("IotlabAggregate \t get_slivers person %s \
636                        \r\n" % (person))
637         # name = person['last_name']
638         user['login'] = person['uid']
639         user['user_urn'] = hrn_to_urn(user['hrn'], 'user')
640         user['keys'] = person['pkey']
641
642
643         try:
644             node_ids = single_slice['node_ids']
645             node_list = self.driver.testbed_shell.GetNodes(
646                     {'hostname':single_slice['node_ids']})
647             node_by_hostname = dict([(node['hostname'], node)
648                                         for node in node_list])
649         except KeyError:
650             logger.warning("\t get_slivers No slivers in slice")
651             # slice['node_ids'] = node_ids
652         # nodes_dict = self.get_slice_nodes(slice, options)
653
654         slivers = []
655         for current_lease in leases:
656             for hostname in current_lease['reserved_nodes']:
657                 node = {}
658                 node['slice_id'] = current_lease['slice_id']
659                 node['slice_hrn'] = current_lease['slice_hrn']
660                 slice_name = current_lease['slice_hrn'].split(".")[1]
661                 node['slice_name'] = slice_name
662                 index = current_lease['reserved_nodes'].index(hostname)
663                 node_id = current_lease['resource_ids'][index]
664                 # node['slice_name'] = user['login']
665                 # node.update(single_slice)
666                 more_info = node_by_hostname[hostname]
667                 node.update(more_info)
668                 # oar_job_id is the slice_id (lease_id)
669                 sliver_hrn = '%s.%s-%s' % (self.driver.hrn,
670                             current_lease['lease_id'], node_id)
671                 node['node_id'] = node_id
672                 node['expires'] = current_lease['t_until']
673                 node['sliver_id'] = Xrn(sliver_hrn, type='sliver').urn
674                 node['urn'] = node['sliver_id']
675                 node['services_user'] = [user]
676
677                 slivers.append(node)
678         return slivers
679
680     def list_resources(self, version = None, options={}):
681         """
682         Returns an advertisement Rspec of available resources at this
683         aggregate. This Rspec contains a resource listing along with their
684         description, providing sufficient information for clients to be able to
685         select among available resources.
686
687         :param options: various options. The valid options are: {boolean
688             geni_compressed <optional>; struct geni_rspec_version { string type;
689             #case insensitive , string version; # case insensitive}} . The only
690             mandatory options if options is specified is geni_rspec_version.
691         :type options: dictionary
692
693         :returns: On success, the value field of the return struct will contain
694             a geni.rspec advertisment RSpec
695         :rtype: Rspec advertisement in xml.
696
697         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#RSpecdatatype
698         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#ListResources
699         """
700
701         version_manager = VersionManager()
702         version = version_manager.get_version(version)
703         rspec_version = version_manager._get_version(version.type,
704                                                     version.version, 'ad')
705         rspec = RSpec(version=rspec_version, user_options=options)
706         # variable ldap_username to be compliant with  get_all_leases
707         # prototype. Now unused in geni-v3 since we are getting all the leases
708         # here
709         ldap_username = None
710         if not options.get('list_leases') or options['list_leases'] != 'leases':
711             # get nodes
712             nodes_dict  = self.get_nodes(options)
713
714             # no interfaces on iotlab nodes
715             # convert nodes to rspec nodes
716             rspec_nodes = []
717             for node_id in nodes_dict:
718                 node = nodes_dict[node_id]
719                 rspec_node = self.node_to_rspec_node(node)
720                 rspec_nodes.append(rspec_node)
721             rspec.version.add_nodes(rspec_nodes)
722
723             # add links
724             # links = self.get_links(sites, nodes_dict, interfaces)
725             # rspec.version.add_links(links)
726
727         if not options.get('list_leases') or options.get('list_leases') \
728             and options['list_leases'] != 'resources':
729             leases = self.get_all_leases(ldap_username)
730             rspec.version.add_leases(leases)
731
732         return rspec.toxml()
733
734
735     def describe(self, urns, version=None, options={}):
736         """
737         Retrieve a manifest RSpec describing the resources contained by the
738         named entities, e.g. a single slice or a set of the slivers in a slice.
739         This listing and description should be sufficiently descriptive to allow
740         experimenters to use the resources.
741
742         :param urns: If a slice urn is supplied and there are no slivers in the
743             given slice at this aggregate, then geni_rspec shall be a valid
744             manifest RSpec, containing no node elements - no resources.
745         :type urns: list  or strings
746         :param options: various options. the valid options are: {boolean
747             geni_compressed <optional>; struct geni_rspec_version { string type;
748             #case insensitive , string version; # case insensitive}}
749         :type options: dictionary
750
751         :returns: On success returns the following dictionary {geni_rspec:
752             <geni.rspec, a Manifest RSpec>, geni_urn: <string slice urn of the
753             containing slice>, geni_slivers:{ geni_sliver_urn:
754             <string sliver urn>, geni_expires:  <dateTime.rfc3339 allocation
755             expiration string, as in geni_expires from SliversStatus>,
756             geni_allocation_status: <string sliver state - e.g. geni_allocated
757             or geni_provisioned >, geni_operational_status:
758             <string sliver operational state>, geni_error: <optional string.
759             The field may be omitted entirely but may not be null/None,
760             explaining any failure for a sliver.>}
761
762         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#Describe
763         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
764         """
765         version_manager = VersionManager()
766         version = version_manager.get_version(version)
767         rspec_version = version_manager._get_version(
768                                     version.type, version.version, 'manifest')
769         rspec = RSpec(version=rspec_version, user_options=options)
770
771         # get slivers
772         geni_slivers = []
773         slivers = self.get_slivers(urns, options)
774         if slivers:
775             rspec_expires = datetime_to_string(utcparse(slivers[0]['expires']))
776         else:
777             rspec_expires = datetime_to_string(utcparse(time.time()))
778         rspec.xml.set('expires',  rspec_expires)
779
780         # lookup the sliver allocations
781         geni_urn = urns[0]
782         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
783         logger.debug(" IOTLAB_API.PY \tDescribe  sliver_ids %s "
784                      % (sliver_ids))
785         constraint = SliverAllocation.sliver_id.in_(sliver_ids)
786         query = self.driver.api.dbsession().query(SliverAllocation)
787         sliver_allocations = query.filter((constraint)).all()
788         logger.debug(" IOTLAB_API.PY \tDescribe  sliver_allocations %s "
789                      % (sliver_allocations))
790         sliver_allocation_dict = {}
791         for sliver_allocation in sliver_allocations:
792             geni_urn = sliver_allocation.slice_urn
793             sliver_allocation_dict[sliver_allocation.sliver_id] = \
794                                                             sliver_allocation
795
796         # add slivers
797         nodes_dict = {}
798         for sliver in slivers:
799             nodes_dict[sliver['node_id']] = sliver
800         rspec_nodes = []
801         for sliver in slivers:
802             rspec_node = self.sliver_to_rspec_node(sliver,
803                                                     sliver_allocation_dict)
804             rspec_nodes.append(rspec_node)
805             logger.debug(" IOTLAB_API.PY \tDescribe  sliver_allocation_dict %s "
806                      % (sliver_allocation_dict))
807             geni_sliver = self.rspec_node_to_geni_sliver(rspec_node,
808                             sliver_allocation_dict)
809             geni_slivers.append(geni_sliver)
810
811         logger.debug(" IOTLAB_API.PY \tDescribe rspec_nodes %s\
812                         rspec %s "
813                      % (rspec_nodes, rspec))
814         rspec.version.add_nodes(rspec_nodes)
815
816         return {'geni_urn': geni_urn,
817                 'geni_rspec': rspec.toxml(),
818                 'geni_slivers': geni_slivers}