d9cddf3bb1280123fbc8f80ef7e11d84b2b68ac4
[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 = {}):
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 rspec_node['sliver_id'] in sliver_allocations:
316             # set sliver allocation and operational status
317             sliver_allocation = sliver_allocations[rspec_node['sliver_id']]
318             if sliver_allocation:
319                 allocation_status = sliver_allocation.allocation_state
320                 if allocation_status == 'geni_allocated':
321                     op_status =  'geni_pending_allocation'
322                 elif allocation_status == 'geni_provisioned':
323                     op_status = 'geni_ready'
324                 else:
325                     op_status = 'geni_unknown'
326             else:
327                 allocation_status = 'geni_unallocated'
328         else:
329             allocation_status = 'geni_unallocated'
330             op_status = 'geni_failed'
331         # required fields
332         geni_sliver = {'geni_sliver_urn': rspec_node['sliver_id'],
333                        'geni_expires': rspec_node['expires'],
334                        'geni_allocation_status' : allocation_status,
335                        'geni_operational_status': op_status,
336                        'geni_error': '',
337                        }
338         return geni_sliver
339
340     def sliver_to_rspec_node(self, sliver, sliver_allocations):
341         """Used by describe to format node information into a rspec compliant
342         structure.
343
344         Creates a node rspec compliant structure by calling node_to_rspec_node.
345         Adds slivers, if any, to rspec node structure. Returns the updated
346         rspec node struct.
347
348         :param sliver: sliver dictionary. Contains keys: urn, slice_id, hostname
349             and slice_name.
350         :type sliver: dictionary
351         :param sliver_allocations: dictionary of slivers
352         :type sliver_allocations: dict
353
354         :returns: Node dictionary with all necessary data.
355
356         .. seealso:: node_to_rspec_node
357         """
358         rspec_node = self.node_to_rspec_node(sliver)
359         rspec_node['expires'] = datetime_to_string(utcparse(sliver['expires']))
360         # add sliver info
361         logger.debug("CORTEXLABAGGREGATE api \t  sliver_to_rspec_node sliver \
362                         %s \r\nsliver_allocations %s" % (sliver,
363                             sliver_allocations))
364         rspec_sliver = Sliver({'sliver_id': sliver['urn'],
365                          'name': sliver['slice_id'],
366                          'type': 'iotlab-exclusive',
367                          'tags': []})
368         rspec_node['sliver_id'] = rspec_sliver['sliver_id']
369
370         if sliver['urn'] in sliver_allocations:
371             rspec_node['client_id'] = sliver_allocations[
372                                                     sliver['urn']].client_id
373             if sliver_allocations[sliver['urn']].component_id:
374                 rspec_node['component_id'] = sliver_allocations[
375                                                     sliver['urn']].component_id
376         rspec_node['slivers'] = [rspec_sliver]
377
378         # slivers always provide the ssh service
379         login = Login({'authentication': 'ssh-keys',
380                        'hostname': sliver['hostname'],
381                        'port':'22',
382                        'username': sliver['slice_name'],
383                        'login': sliver['slice_name']
384                       })
385         return rspec_node
386
387
388     def get_all_leases(self, ldap_username):
389         """
390
391         Get list of lease dictionaries which all have the mandatory keys
392         ('lease_id', 'hostname', 'site_id', 'name', 'start_time', 'duration').
393         All the leases running or scheduled are returned.
394
395         :param ldap_username: if ldap uid is not None, looks for the leases
396             belonging to this user.
397         :type ldap_username: string
398         :returns: rspec lease dictionary with keys lease_id, component_id,
399             slice_id, start_time, duration where the lease_id is the oar job id,
400             component_id is the node's urn, slice_id is the slice urn,
401             start_time is the timestamp starting time and duration is expressed
402             in terms of the testbed's granularity.
403         :rtype: dict
404
405         .. note::There is no filtering of leases within a given time frame.
406             All the running or scheduled leases are returned. options
407             removed SA 15/05/2013
408
409
410         """
411
412         logger.debug("CortexlabAggregate  get_all_leases ldap_username %s "
413                      % (ldap_username))
414         leases = self.driver.driver.GetLeases(login=ldap_username)
415         grain = self.driver.testbed_shell.GetLeaseGranularity()
416         # site_ids = []
417         rspec_leases = []
418         for lease in leases:
419             #as many leases as there are nodes in the job
420             for node in lease['reserved_nodes']:
421                 rspec_lease = Lease()
422                 rspec_lease['lease_id'] = lease['lease_id']
423
424                 cortexlab_xrn = xrn_object(
425                     self.driver.testbed_shell.root_auth, node)
426                 rspec_lease['component_id'] = cortexlab_xrn.urn
427                 #rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn,\
428                                         #site, node['hostname'])
429                 try:
430                     rspec_lease['slice_id'] = lease['slice_id']
431                 except KeyError:
432                     #No info on the slice used in cortexlab_xp table
433                     pass
434                 rspec_lease['start_time'] = lease['t_from']
435                 rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) \
436                      / grain
437                 rspec_leases.append(rspec_lease)
438         return rspec_leases
439
440     def get_rspec(self, slice_xrn=None, login=None, version=None,
441                   options=None):
442         """
443         Returns xml rspec:
444         - a full advertisement rspec with the testbed resources if slice_xrn is
445         not specified.If a lease option is given, also returns the leases
446         scheduled on the testbed.
447         - a manifest Rspec with the leases and nodes in slice's leases if
448         slice_xrn is not None.
449
450         :param slice_xrn: srn of the slice
451         :type slice_xrn: string
452         :param login: user'uid (ldap login) on cortexlab
453         :type login: string
454         :param version: can be set to sfa or cortexlab
455         :type version: RSpecVersion
456         :param options: used to specify if the leases should also be included in
457             the returned rspec.
458         :type options: dict
459
460         :returns: Xml Rspec.
461         :rtype: XML
462
463
464         """
465
466         ldap_username = None
467         rspec = None
468         version_manager = VersionManager()
469         version = version_manager.get_version(version)
470         logger.debug("CortexlabAggregate \t get_rspec ***version %s \
471                     version.type %s  version.version %s options %s \r\n"
472                      % (version, version.type, version.version, options))
473
474         if slice_xrn is None:
475             rspec_version = version_manager._get_version(version.type,
476                                                          version.version, 'ad')
477
478         else:
479             rspec_version = version_manager._get_version(
480                 version.type, version.version, 'manifest')
481
482         slices, slivers = self.get_slice_and_slivers(slice_xrn, login)
483         if slice_xrn and slices is not None:
484             #Get user associated with this slice
485             #for one_slice in slices :
486             ldap_username = self.find_ldap_username_from_slice(slices[0])
487             # ldap_username = slices[0]['reg_researchers'][0].__dict__['hrn']
488             #  # ldap_username = slices[0]['user']
489             # tmp = ldap_username.split('.')
490             # ldap_username = tmp[1]
491             logger.debug("CortexlabAggregate \tget_rspec **** \
492                     LDAP USERNAME %s \r\n" \
493                     % (ldap_username))
494         #at this point sliver may be empty if no cortexlab job
495         #is running for this user/slice.
496         rspec = RSpec(version=rspec_version, user_options=options)
497
498         logger.debug("\r\n \r\n CortexlabAggregate \tget_rspec *** \
499                       slice_xrn %s slices  %s\r\n \r\n"
500                      % (slice_xrn, slices))
501
502         if options is not None :
503             lease_option = options['list_leases']
504         else:
505             #If no options are specified, at least print the resources
506             lease_option = 'all'
507            #if slice_xrn :
508                #lease_option = 'all'
509
510         if lease_option in ['all', 'resources']:
511         #if not options.get('list_leases') or options.get('list_leases')
512         #and options['list_leases'] != 'leases':
513             nodes = self.get_nodes()
514             logger.debug("\r\n")
515             logger.debug("CortexlabAggregate \t lease_option %s \
516                           get rspec  ******* nodes %s"
517                          % (lease_option, nodes))
518
519             sites_set = set([node['location']['site'] for node in nodes])
520
521             #In case creating a job,  slice_xrn is not set to None
522             rspec.version.add_nodes(nodes)
523             if slice_xrn and slices is not None:
524             #     #Get user associated with this slice
525             #     #for one_slice in slices :
526             #     ldap_username = slices[0]['reg_researchers']
527             #      # ldap_username = slices[0]['user']
528             #     tmp = ldap_username.split('.')
529             #     ldap_username = tmp[1]
530             #      # ldap_username = tmp[1].split('_')[0]
531
532                 logger.debug("CortexlabAggregate \tget_rspec **** \
533                         version type %s ldap_ user %s \r\n" \
534                         % (version.type, ldap_username))
535                 #TODO : Change the version of Rspec here in case of pbm -SA 09/01/14
536                 if version.type in ["Cortexlab", "Iotlab"]:
537                     rspec.version.add_connection_information(
538                         ldap_username, sites_set)
539
540             default_sliver = slivers.get('default_sliver', [])
541             if default_sliver and len(nodes) is not 0:
542                 #default_sliver_attribs = default_sliver.get('tags', [])
543                 logger.debug("CortexlabAggregate \tget_rspec **** \
544                         default_sliver%s \r\n" % (default_sliver))
545                 for attrib in default_sliver:
546                     rspec.version.add_default_sliver_attribute(
547                         attrib, default_sliver[attrib])
548
549         if lease_option in ['all','leases']:
550             leases = self.get_all_leases(ldap_username)
551             rspec.version.add_leases(leases)
552             logger.debug("CortexlabAggregate \tget_rspec **** \
553                        FINAL RSPEC %s \r\n" % (rspec.toxml()))
554         return rspec.toxml()
555
556
557
558     def get_slivers(self, urns, options={}):
559         """Get slivers of the given slice urns. Slivers contains slice, node and
560         user information.
561
562         For Iotlab, returns the leases with sliver ids and their allocation
563         status.
564
565         :param urns: list of  slice urns.
566         :type urns: list of strings
567         :param options: unused
568         :type options: unused
569
570         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
571         """
572
573
574         slice_ids = set()
575         node_ids = []
576         for urn in urns:
577             xrn = IotlabXrn(xrn=urn)
578             if xrn.type == 'sliver':
579                  # id: slice_id-node_id
580                 try:
581                     sliver_id_parts = xrn.get_sliver_id_parts()
582                     slice_id = int(sliver_id_parts[0])
583                     node_id = int(sliver_id_parts[1])
584                     slice_ids.add(slice_id)
585                     node_ids.append(node_id)
586                 except ValueError:
587                     pass
588             else:
589                 slice_names = set()
590                 slice_names.add(xrn.hrn)
591
592
593         logger.debug("CortexlabAggregate \t get_slivers urns %s slice_ids %s \
594                        node_ids %s\r\n" % (urns, slice_ids, node_ids))
595         logger.debug("CortexlabAggregate \t get_slivers xrn %s slice_names %s \
596                        \r\n" % (xrn, slice_names))
597         filter_sliver = {}
598         if slice_names:
599             filter_sliver['slice_hrn'] = list(slice_names)
600             slice_hrn = filter_sliver['slice_hrn'][0]
601
602             slice_filter_type = 'slice_hrn'
603
604         # if slice_ids:
605         #     filter['slice_id'] = list(slice_ids)
606         # # get slices
607         if slice_hrn:
608             slices = self.driver.GetSlices(slice_hrn,
609                 slice_filter_type)
610             leases = self.driver.GetLeases({'slice_hrn':slice_hrn})
611         logger.debug("CortexlabAggregate \t get_slivers \
612                        slices %s leases %s\r\n" % (slices, leases ))
613         if not slices:
614             return []
615
616         single_slice = slices[0]
617         # get sliver users
618         user = single_slice['reg_researchers'][0].__dict__
619         logger.debug("CortexlabAggregate \t get_slivers user %s \
620                        \r\n" % (user))
621
622         # construct user key info
623         person = self.driver.testbed_shell.ldap.LdapFindUser(record=user)
624         logger.debug("CortexlabAggregate \t get_slivers person %s \
625                        \r\n" % (person))
626         # name = person['last_name']
627         user['login'] = person['uid']
628         user['user_urn'] = hrn_to_urn(user['hrn'], 'user')
629         user['keys'] = person['pkey']
630
631
632         try:
633             node_ids = single_slice['node_ids']
634             node_list = self.driver.testbed_shell.GetNodes(
635                     {'hostname':single_slice['node_ids']})
636             node_by_hostname = dict([(node['hostname'], node)
637                                         for node in node_list])
638         except KeyError:
639             logger.warning("\t get_slivers No slivers in slice")
640             # slice['node_ids'] = node_ids
641         # nodes_dict = self.get_slice_nodes(slice, options)
642
643         slivers = []
644         for current_lease in leases:
645             for hostname in current_lease['reserved_nodes']:
646                 node = {}
647                 node['slice_id'] = current_lease['slice_id']
648                 node['slice_hrn'] = current_lease['slice_hrn']
649                 slice_name = current_lease['slice_hrn'].split(".")[1]
650                 node['slice_name'] = slice_name
651                 index = current_lease['reserved_nodes'].index(hostname)
652                 node_id = current_lease['resource_ids'][index]
653                 # node['slice_name'] = user['login']
654                 # node.update(single_slice)
655                 more_info = node_by_hostname[hostname]
656                 node.update(more_info)
657                 # oar_job_id is the slice_id (lease_id)
658                 sliver_hrn = '%s.%s-%s' % (self.driver.hrn,
659                             current_lease['lease_id'], node_id)
660                 node['node_id'] = node_id
661                 node['expires'] = current_lease['t_until']
662                 node['sliver_id'] = Xrn(sliver_hrn, type='sliver').urn
663                 node['urn'] = node['sliver_id']
664                 node['services_user'] = [user]
665
666                 slivers.append(node)
667         return slivers
668
669
670     def list_resources(self, version = None, options={}):
671         """
672         Returns an advertisement Rspec of available resources at this
673         aggregate. This Rspec contains a resource listing along with their
674         description, providing sufficient information for clients to be able to
675         select among available resources.
676
677         :param options: various options. The valid options are: {boolean
678             geni_compressed <optional>; struct geni_rspec_version { string type;
679             #case insensitive , string version; # case insensitive}} . The only
680             mandatory options if options is specified is geni_rspec_version.
681         :type options: dictionary
682
683         :returns: On success, the value field of the return struct will contain
684             a geni.rspec advertisment RSpec
685         :rtype: Rspec advertisement in xml.
686
687         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#RSpecdatatype
688         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#ListResources
689         """
690
691         version_manager = VersionManager()
692         version = version_manager.get_version(version)
693         rspec_version = version_manager._get_version(version.type,
694                                                     version.version, 'ad')
695         rspec = RSpec(version=rspec_version, user_options=options)
696         # variable ldap_username to be compliant with  get_all_leases
697         # prototype. Now unused in geni-v3 since we are getting all the leases
698         # here
699         ldap_username = None
700         if not options.get('list_leases') or options['list_leases'] != 'leases':
701             # get nodes
702             nodes_dict  = self.get_nodes(options)
703
704             # no interfaces on iotlab nodes
705             # convert nodes to rspec nodes
706             rspec_nodes = []
707             for node_id in nodes_dict:
708                 node = nodes_dict[node_id]
709                 rspec_node = self.node_to_rspec_node(node)
710                 rspec_nodes.append(rspec_node)
711             rspec.version.add_nodes(rspec_nodes)
712
713             # add links
714             # links = self.get_links(sites, nodes_dict, interfaces)
715             # rspec.version.add_links(links)
716
717         if not options.get('list_leases') or options.get('list_leases') \
718             and options['list_leases'] != 'resources':
719             leases = self.get_all_leases(ldap_username)
720             rspec.version.add_leases(leases)
721
722         return rspec.toxml()
723
724
725     def describe(self, urns, version=None, options={}):
726         """
727         Retrieve a manifest RSpec describing the resources contained by the
728         named entities, e.g. a single slice or a set of the slivers in a slice.
729         This listing and description should be sufficiently descriptive to allow
730         experimenters to use the resources.
731
732         :param urns: If a slice urn is supplied and there are no slivers in the
733             given slice at this aggregate, then geni_rspec shall be a valid
734             manifest RSpec, containing no node elements - no resources.
735         :type urns: list  or strings
736         :param options: various options. the valid options are: {boolean
737             geni_compressed <optional>; struct geni_rspec_version { string type;
738             #case insensitive , string version; # case insensitive}}
739         :type options: dictionary
740
741         :returns: On success returns the following dictionary {geni_rspec:
742             <geni.rspec, a Manifest RSpec>, geni_urn: <string slice urn of the
743             containing slice>, geni_slivers:{ geni_sliver_urn:
744             <string sliver urn>, geni_expires:  <dateTime.rfc3339 allocation
745             expiration string, as in geni_expires from SliversStatus>,
746             geni_allocation_status: <string sliver state - e.g. geni_allocated
747             or geni_provisioned >, geni_operational_status:
748             <string sliver operational state>, geni_error: <optional string.
749             The field may be omitted entirely but may not be null/None,
750             explaining any failure for a sliver.>}
751
752         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3#Describe
753         .. seealso:: http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns
754         """
755         version_manager = VersionManager()
756         version = version_manager.get_version(version)
757         rspec_version = version_manager._get_version(
758                                     version.type, version.version, 'manifest')
759         rspec = RSpec(version=rspec_version, user_options=options)
760
761         # get slivers
762         geni_slivers = []
763         slivers = self.get_slivers(urns, options)
764         if slivers:
765             rspec_expires = datetime_to_string(utcparse(slivers[0]['expires']))
766         else:
767             rspec_expires = datetime_to_string(utcparse(time.time()))
768         rspec.xml.set('expires',  rspec_expires)
769
770         # lookup the sliver allocations
771         geni_urn = urns[0]
772         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
773         logger.debug(" Cortexlabaggregate.PY \tDescribe  sliver_ids %s "
774                      % (sliver_ids))
775         constraint = SliverAllocation.sliver_id.in_(sliver_ids)
776         query = self.driver.api.dbsession().query(SliverAllocation)
777         sliver_allocations = query.filter((constraint)).all()
778         logger.debug(" Cortexlabaggregate.PY \tDescribe  sliver_allocations %s "
779                      % (sliver_allocations))
780         sliver_allocation_dict = {}
781         for sliver_allocation in sliver_allocations:
782             geni_urn = sliver_allocation.slice_urn
783             sliver_allocation_dict[sliver_allocation.sliver_id] = \
784                                                             sliver_allocation
785
786         # add slivers
787         nodes_dict = {}
788         for sliver in slivers:
789             nodes_dict[sliver['node_id']] = sliver
790         rspec_nodes = []
791         for sliver in slivers:
792             rspec_node = self.sliver_to_rspec_node(sliver,
793                                                     sliver_allocation_dict)
794             rspec_nodes.append(rspec_node)
795             logger.debug(" Cortexlabaggregate.PY \tDescribe  sliver_allocation_dict %s "
796                      % (sliver_allocation_dict))
797             geni_sliver = self.rspec_node_to_geni_sliver(rspec_node,
798                             sliver_allocation_dict)
799             geni_slivers.append(geni_sliver)
800
801         logger.debug(" Cortexlabaggregate.PY \tDescribe rspec_nodes %s\
802                         rspec %s "
803                      % (rspec_nodes, rspec))
804         rspec.version.add_nodes(rspec_nodes)
805
806         return {'geni_urn': geni_urn,
807                 'geni_rspec': rspec.toxml(),
808                 'geni_slivers': geni_slivers}