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