474aadae040208367f935b6d8fbb7bdb42b8ab67
[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.xrn import hrn_to_urn, urn_to_hrn, get_authority
7
8 from sfa.rspecs.rspec import RSpec
9 #from sfa.rspecs.elements.location import Location
10 from sfa.rspecs.elements.hardware_type import HardwareType
11 from sfa.rspecs.elements.login import Login
12 from sfa.rspecs.elements.services import Services
13 from sfa.rspecs.elements.sliver import Sliver
14 from sfa.rspecs.elements.lease import Lease
15 from sfa.rspecs.elements.granularity import Granularity
16 from sfa.rspecs.version_manager import VersionManager
17
18 from sfa.rspecs.elements.versions.iotlabv1Node import IotlabPosition, \
19     IotlabNode, IotlabLocation, IotlabMobility
20
21 from sfa.util.sfalogging import logger
22 from sfa.util.xrn import Xrn
23
24
25 def iotlab_xrn_to_hostname(xrn):
26     """Returns a node's hostname from its xrn.
27     :param xrn: The nodes xrn identifier.
28     :type xrn: Xrn (from sfa.util.xrn)
29
30     :returns: node's hostname.
31     :rtype: string
32
33     """
34     return Xrn.unescape(Xrn(xrn=xrn, type='node').get_leaf())
35
36
37 def iotlab_xrn_object(root_auth, hostname):
38     """Creates a valid xrn object from the node's hostname and the authority
39     of the SFA server.
40
41     :param hostname: the node's hostname.
42     :param root_auth: the SFA root authority.
43     :type hostname: string
44     :type root_auth: string
45
46     :returns: the iotlab node's xrn
47     :rtype: Xrn
48
49     """
50     return Xrn('.'.join([root_auth, Xrn.escape(hostname)]), type='node')
51
52
53 class IotlabAggregate:
54     """Aggregate manager class for Iotlab. """
55
56     sites = {}
57     nodes = {}
58     api = None
59     interfaces = {}
60     links = {}
61     node_tags = {}
62
63     prepared = False
64
65     user_options = {}
66
67     def __init__(self, driver):
68         self.driver = driver
69
70     def get_slice_and_slivers(self, slice_xrn, login=None):
71         """
72         Get the slices and the associated leases if any from the iotlab
73             testbed. One slice can have mutliple leases.
74             For each slice, get the nodes in the  associated lease
75             and create a sliver with the necessary info and insert it into the
76             sliver dictionary, keyed on the node hostnames.
77             Returns a dict of slivers based on the sliver's node_id.
78             Called by get_rspec.
79
80
81         :param slice_xrn: xrn of the slice
82         :param login: user's login on iotlab ldap
83
84         :type slice_xrn: string
85         :type login: string
86         :returns: a list of slices dict and a list of Sliver object
87         :rtype: (list, list)
88
89         .. note: There is no real slivers in iotlab, only leases. The goal
90             is to be consistent with the SFA standard.
91
92         """
93         slivers = {}
94         sfa_slice = None
95         if slice_xrn is None:
96             return (sfa_slice, slivers)
97         slice_urn = hrn_to_urn(slice_xrn, 'slice')
98         slice_hrn, _ = urn_to_hrn(slice_xrn)
99         slice_name = slice_hrn
100
101         slices = self.driver.iotlab_api.GetSlices(slice_filter=str(slice_name),
102                                                   slice_filter_type='slice_hrn',
103                                                   login=login)
104
105         logger.debug("IotlabAggregate api \tget_slice_and_slivers \
106                       sfa_slice %s \r\n slices %s self.driver.hrn %s"
107                      % (sfa_slice, slices, self.driver.hrn))
108         if slices == []:
109             return (sfa_slice, slivers)
110
111         # sort slivers by node id , if there is a job
112         #and therefore, node allocated to this slice
113         for sfa_slice in slices:
114             try:
115                 node_ids_list = sfa_slice['node_ids']
116             except KeyError:
117                 logger.log_exc("IOTLABAGGREGATE \t \
118                             get_slice_and_slivers No nodes in the slice \
119                             - KeyError ")
120                 continue
121
122             for node in node_ids_list:
123                 sliver_xrn = Xrn(slice_urn, type='sliver', id=node)
124                 sliver_xrn.set_authority(self.driver.hrn)
125                 sliver = Sliver({'sliver_id': sliver_xrn.urn,
126                                 'name': sfa_slice['hrn'],
127                                 'type': 'iotlab-node',
128                                 'tags': []})
129
130                 slivers[node] = sliver
131
132         #Add default sliver attribute :
133         #connection information for iotlab
134         if get_authority(sfa_slice['hrn']) == self.driver.iotlab_api.root_auth:
135             tmp = sfa_slice['hrn'].split('.')
136             ldap_username = tmp[1].split('_')[0]
137             ssh_access = None
138             slivers['default_sliver'] = {'ssh': ssh_access,
139                                          'login': ldap_username}
140
141         #TODO get_slice_and_slivers Find the login of the external user
142
143         logger.debug("IOTLABAGGREGATE api get_slice_and_slivers  slivers %s "
144                      % (slivers))
145         return (slices, slivers)
146
147
148     def get_nodes(self, slices=None, slivers=[], options=None):
149         """Returns the nodes in the slice using the rspec format, with all the
150         nodes' properties.
151
152         Fetch the nodes ids in the slices dictionary and get all the nodes
153         properties from OAR. Makes a rspec dicitonary out of this and returns
154         it. If the slice does not have any job running or scheduled, that is
155         it has no reserved nodes, then returns an empty list.
156
157         :param slices: list of slices (record dictionaries)
158         :param slivers: the list of slivers in all the slices
159         :type slices: list of dicts
160         :type slivers: list of Sliver object (dictionaries)
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         # NT: the semantic of this function is not clear to me :
170         # if slice is not defined, then all the nodes should be returned
171         # if slice is defined, we should return only the nodes that
172         # are part of this slice
173         # but what is the role of the slivers parameter ?
174         # So i assume that slice['node_ids'] will be the same as slivers for us
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.iotlab_api.GetLeaseGranularity()
189
190         nodes = self.driver.iotlab_api.GetNodes()
191
192         nodes_dict = {}
193
194         #if slices, this means we got to list all the nodes given to this slice
195         # Make a list of all the nodes in the slice before getting their
196         #attributes
197         rspec_nodes = []
198
199         logger.debug("IOTLABAGGREGATE api get_nodes slices  %s "
200                      % (slices))
201
202         reserved_nodes = self.driver.iotlab_api.GetNodesCurrentlyInUse()
203         logger.debug("IOTLABAGGREGATE api get_nodes slice_nodes_list  %s "
204                      % (slice_nodes_list))
205         for node in nodes:
206             nodes_dict[node['node_id']] = node
207             if slice_nodes_list == [] or node['hostname'] in slice_nodes_list:
208
209                 rspec_node = IotlabNode()
210                 # xxx how to retrieve site['login_base']
211                 #site_id=node['site_id']
212                 #site=sites_dict[site_id]
213
214                 # rspec_node['mobile'] = node['mobile']
215                 rspec_node['archi'] = node['archi']
216                 rspec_node['radio'] = node['radio']
217
218                 iotlab_xrn = iotlab_xrn_object(self.driver.iotlab_api.root_auth,
219                                                node['hostname'])
220                 rspec_node['component_id'] = iotlab_xrn.urn
221                 rspec_node['component_name'] = node['hostname']
222                 rspec_node['component_manager_id'] = \
223                                 hrn_to_urn(self.driver.iotlab_api.root_auth,
224                                 'authority+sa')
225
226                 # Iotlab's nodes are federated : there is only one authority
227                 # for all Iotlab sites, registered in SFA.
228                 # Removing the part including the site
229                 # in authority_id SA 27/07/12
230                 rspec_node['authority_id'] = rspec_node['component_manager_id']
231
232                 # do not include boot state (<available> element)
233                 #in the manifest rspec
234
235                 rspec_node['boot_state'] = node['boot_state']
236                 if node['hostname'] in reserved_nodes:
237                     rspec_node['boot_state'] = "Reserved"
238                 rspec_node['exclusive'] = 'true'
239                 rspec_node['hardware_types'] = [HardwareType({'name':
240                                                'iotlab-node'})]
241
242
243                 location = IotlabLocation({'country':'France', 'site':
244                                             node['site']})
245                 rspec_node['location'] = location
246
247                 # Adding mobility of the node in the rspec
248                 mobility = IotlabMobility()
249                 for field in mobility:
250                     try:
251                         mobility[field] = node[field]
252                     except KeyError, error:
253                         logger.log_exc("IOTLABAGGREGATE\t get_nodes \
254                                          mobility %s " % (error))
255                 rspec_node['mobility'] = mobility
256
257                 position = IotlabPosition()
258                 for field in position:
259                     try:
260                         position[field] = node[field]
261                     except KeyError, error:
262                         logger.log_exc("IOTLABAGGREGATE\t get_nodes \
263                                                         position %s " % (error))
264
265                 rspec_node['position'] = position
266                 #rspec_node['interfaces'] = []
267
268                 # Granularity
269                 granularity = Granularity({'grain': grain})
270                 rspec_node['granularity'] = granularity
271                 rspec_node['tags'] = []
272                 if node['hostname'] in slivers:
273                     # add sliver info
274                     sliver = slivers[node['hostname']]
275                     rspec_node['sliver_id'] = sliver['sliver_id']
276                     rspec_node['client_id'] = node['hostname']
277                     rspec_node['slivers'] = [sliver]
278
279                     # slivers always provide the ssh service
280                     login = Login({'authentication': 'ssh-keys',
281                                    'hostname': node['hostname'], 'port': '22',
282                                    'username': sliver['name']})
283                     service = Services({'login': login})
284                     rspec_node['services'] = [service]
285                 rspec_nodes.append(rspec_node)
286
287         return (rspec_nodes)
288
289     def get_all_leases(self, ldap_username):
290         """
291
292         Get list of lease dictionaries which all have the mandatory keys
293         ('lease_id', 'hostname', 'site_id', 'name', 'start_time', 'duration').
294         All the leases running or scheduled are returned.
295
296         :param ldap_username: if ldap uid is not None, looks for the leases
297         belonging to this user.
298         :type ldap_username: string
299         :returns: rspec lease dictionary with keys lease_id, component_id,
300             slice_id, start_time, duration.
301         :rtype: dict
302
303         .. note::There is no filtering of leases within a given time frame.
304             All the running or scheduled leases are returned. options
305             removed SA 15/05/2013
306
307
308         """
309
310         #now = int(time.time())
311         #lease_filter = {'clip': now }
312
313         #if slice_record:
314             #lease_filter.update({'name': slice_record['name']})
315
316         #leases = self.driver.iotlab_api.GetLeases(lease_filter)
317
318         logger.debug("IOTLABAGGREGATE  get_all_leases ldap_username %s "
319                      % (ldap_username))
320         leases = self.driver.iotlab_api.GetLeases(login=ldap_username)
321         grain = self.driver.iotlab_api.GetLeaseGranularity()
322         # site_ids = []
323         rspec_leases = []
324         for lease in leases:
325             #as many leases as there are nodes in the job
326             for node in lease['reserved_nodes']:
327                 rspec_lease = Lease()
328                 rspec_lease['lease_id'] = lease['lease_id']
329                 #site = node['site_id']
330                 iotlab_xrn = iotlab_xrn_object(self.driver.iotlab_api.root_auth,
331                                                node)
332                 rspec_lease['component_id'] = iotlab_xrn.urn
333                 #rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn,\
334                                         #site, node['hostname'])
335                 try:
336                     rspec_lease['slice_id'] = lease['slice_id']
337                 except KeyError:
338                     #No info on the slice used in iotlab_xp table
339                     pass
340                 rspec_lease['start_time'] = lease['t_from']
341                 rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) \
342                     # * grain
343                 rspec_leases.append(rspec_lease)
344         return rspec_leases
345
346     def get_rspec(self, slice_xrn=None, login=None, version=None,
347                   options=None):
348         """
349         Returns xml rspec:
350         - a full advertisement rspec with the testbed resources if slice_xrn is
351         not specified.If a lease option is given, also returns the leases
352         scheduled on the testbed.
353         - a manifest Rspec with the leases and nodes in slice's leases if
354         slice_xrn is not None.
355
356         :param slice_xrn: srn of the slice
357         :type slice_xrn: string
358         :param login: user'uid (ldap login) on iotlab
359         :type login: string
360         :param version: can be set to sfa or iotlab
361         :type version: RSpecVersion
362         :param options: used to specify if the leases should also be included in
363             the returned rspec.
364         :type options: dict
365
366         :returns: Xml Rspec.
367         :rtype: XML
368
369
370         """
371
372         ldap_username = None
373         rspec = None
374         version_manager = VersionManager()
375         version = version_manager.get_version(version)
376         logger.debug("IotlabAggregate \t get_rspec ***version %s \
377                     version.type %s  version.version %s options %s \r\n"
378                      % (version, version.type, version.version, options))
379
380         if slice_xrn is None:
381             rspec_version = version_manager._get_version(version.type,
382                                                          version.version, 'ad')
383
384         else:
385             rspec_version = version_manager._get_version(
386                 version.type, version.version, 'manifest')
387
388         slices, slivers = self.get_slice_and_slivers(slice_xrn, login)
389         if slice_xrn and slices is not None:
390             #Get user associated with this slice
391             #for one_slice in slices :
392             ldap_username = slices[0]['reg_researchers'][0].__dict__['hrn']
393              # ldap_username = slices[0]['user']
394             tmp = ldap_username.split('.')
395             ldap_username = tmp[1]
396             logger.debug("IotlabAggregate \tget_rspec **** \
397                     LDAP USERNAME %s \r\n" \
398                     % (ldap_username))
399         #at this point sliver may be empty if no iotlab job
400         #is running for this user/slice.
401         rspec = RSpec(version=rspec_version, user_options=options)
402
403         logger.debug("\r\n \r\n IotlabAggregate \tget_rspec *** \
404                       slice_xrn %s slices  %s\r\n \r\n"
405                      % (slice_xrn, slices))
406
407         if options is not None and 'list_leases' in options:
408             lease_option = options['list_leases']
409         else:
410             #If no options are specified, at least print the resources
411             lease_option = 'all'
412            #if slice_xrn :
413                #lease_option = 'all'
414
415         if lease_option in ['all', 'resources']:
416         #if not options.get('list_leases') or options.get('list_leases')
417         #and options['list_leases'] != 'leases':
418             nodes = self.get_nodes(slices, slivers)
419             if slice_xrn and slices is None:
420               nodes = []
421             logger.debug("\r\n")
422             logger.debug("IotlabAggregate \t lease_option %s \
423                           get rspec  ******* nodes %s"
424                          % (lease_option, nodes))
425
426             sites_set = set([node['location']['site'] for node in nodes])
427
428             #In case creating a job,  slice_xrn is not set to None
429             rspec.version.add_nodes(nodes)
430             if slice_xrn and slices is not None:
431             #     #Get user associated with this slice
432             #     #for one_slice in slices :
433             #     ldap_username = slices[0]['reg_researchers']
434             #      # ldap_username = slices[0]['user']
435             #     tmp = ldap_username.split('.')
436             #     ldap_username = tmp[1]
437             #      # ldap_username = tmp[1].split('_')[0]
438
439                 logger.debug("IotlabAggregate \tget_rspec **** \
440                         version type %s ldap_ user %s \r\n" \
441                         % (version.type, ldap_username))
442                 if version.type == "Iotlab":
443                     rspec.version.add_connection_information(
444                         ldap_username, sites_set)
445
446             default_sliver = slivers.get('default_sliver', [])
447             if default_sliver and len(nodes) is not 0:
448                 #default_sliver_attribs = default_sliver.get('tags', [])
449                 logger.debug("IotlabAggregate \tget_rspec **** \
450                         default_sliver%s \r\n" % (default_sliver))
451                 for attrib in default_sliver:
452                     rspec.version.add_default_sliver_attribute(
453                         attrib, default_sliver[attrib])
454
455         if lease_option in ['all','leases']:
456             leases = self.get_all_leases(ldap_username)
457             rspec.version.add_leases(leases)
458             logger.debug("IotlabAggregate \tget_rspec **** \
459                        FINAL RSPEC %s \r\n" % (rspec.toxml()))
460         return rspec.toxml()