Fixing small mistake in GetLeases.
[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                       slice_hrn %s \r\n slices %s self.driver.hrn %s"
107                      % (slice_hrn, 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                 node_ids_list = []
121                 continue
122
123             for node in node_ids_list:
124                 sliver_xrn = Xrn(slice_urn, type='sliver', id=node)
125                 sliver_xrn.set_authority(self.driver.hrn)
126                 sliver = Sliver({'sliver_id': sliver_xrn.urn,
127                                 'name': sfa_slice['hrn'],
128                                 'type': 'iotlab-node',
129                                 'tags': []})
130
131                 slivers[node] = sliver
132
133         #Add default sliver attribute :
134         #connection information for iotlab
135         if get_authority(sfa_slice['hrn']) == self.driver.iotlab_api.root_auth:
136             tmp = sfa_slice['hrn'].split('.')
137             ldap_username = tmp[1].split('_')[0]
138             ssh_access = None
139             slivers['default_sliver'] = {'ssh': ssh_access,
140                                          'login': ldap_username}
141
142         #TODO get_slice_and_slivers Find the login of the external user
143
144         logger.debug("IOTLABAGGREGATE api get_slice_and_slivers  slivers %s "
145                      % (slivers))
146         return (slices, slivers)
147
148
149     def get_nodes(self, slices=None, slivers=[], options=None):
150         """Returns the nodes in the slice using the rspec format, with all the
151         nodes' properties.
152
153         Fetch the nodes ids in the slices dictionary and get all the nodes
154         properties from OAR. Makes a rspec dicitonary out of this and returns
155         it. If the slice does not have any job running or scheduled, that is
156         it has no reserved nodes, then returns an empty list.
157
158         :param slices: list of slices (record dictionaries)
159         :param slivers: the list of slivers in all the slices
160         :type slices: list of dicts
161         :type slivers: list of Sliver object (dictionaries)
162         :returns: An empty list if the slice has no reserved nodes, a rspec
163             list with all the nodes and their properties (a dict per node)
164             otherwise.
165         :rtype: list
166
167         .. seealso:: get_slice_and_slivers
168
169         """
170         # NT: the semantic of this function is not clear to me :
171         # if slice is not defined, then all the nodes should be returned
172         # if slice is defined, we should return only the nodes that
173         # are part of this slice
174         # but what is the role of the slivers parameter ?
175         # So i assume that slice['node_ids'] will be the same as slivers for us
176         slice_nodes_list = []
177         if slices is not None:
178             for one_slice in slices:
179                 try:
180                     slice_nodes_list = one_slice['node_ids']
181                      # if we are dealing with a slice that has no node just
182                      # return an empty list. In iotlab a slice can have multiple
183                      # jobs scheduled, so it either has at least one lease or
184                      # not at all.
185                 except KeyError:
186                     return []
187
188         # get the granularity in second for the reservation system
189         grain = self.driver.iotlab_api.GetLeaseGranularity()
190
191         nodes = self.driver.iotlab_api.GetNodes()
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         rspec_nodes = []
199
200         logger.debug("IOTLABAGGREGATE api get_nodes slices  %s "
201                      % (slices))
202
203         reserved_nodes = self.driver.iotlab_api.GetNodesCurrentlyInUse()
204         logger.debug("IOTLABAGGREGATE api get_nodes slice_nodes_list  %s "
205                      % (slice_nodes_list))
206         for node in nodes:
207             nodes_dict[node['node_id']] = node
208             if slice_nodes_list == [] or node['hostname'] in slice_nodes_list:
209
210                 rspec_node = IotlabNode()
211                 # xxx how to retrieve site['login_base']
212                 #site_id=node['site_id']
213                 #site=sites_dict[site_id]
214
215                 # rspec_node['mobile'] = node['mobile']
216                 rspec_node['archi'] = node['archi']
217                 rspec_node['radio'] = node['radio']
218
219                 iotlab_xrn = iotlab_xrn_object(self.driver.iotlab_api.root_auth,
220                                                node['hostname'])
221                 rspec_node['component_id'] = iotlab_xrn.urn
222                 rspec_node['component_name'] = node['hostname']
223                 rspec_node['component_manager_id'] = \
224                                 hrn_to_urn(self.driver.iotlab_api.root_auth,
225                                 'authority+sa')
226
227                 # Iotlab's nodes are federated : there is only one authority
228                 # for all Iotlab sites, registered in SFA.
229                 # Removing the part including the site
230                 # in authority_id SA 27/07/12
231                 rspec_node['authority_id'] = rspec_node['component_manager_id']
232
233                 # do not include boot state (<available> element)
234                 #in the manifest rspec
235
236                 rspec_node['boot_state'] = node['boot_state']
237                 if node['hostname'] in reserved_nodes:
238                     rspec_node['boot_state'] = "Reserved"
239                 rspec_node['exclusive'] = 'true'
240                 rspec_node['hardware_types'] = [HardwareType({'name':
241                                                'iotlab-node'})]
242
243
244                 location = IotlabLocation({'country':'France', 'site':
245                                             node['site']})
246                 rspec_node['location'] = location
247
248                 # Adding mobility of the node in the rspec
249                 mobility = IotlabMobility()
250                 for field in mobility:
251                     try:
252                         mobility[field] = node[field]
253                     except KeyError, error:
254                         logger.log_exc("IOTLABAGGREGATE\t get_nodes \
255                                          mobility %s " % (error))
256                 rspec_node['mobility'] = mobility
257
258                 position = IotlabPosition()
259                 for field in position:
260                     try:
261                         position[field] = node[field]
262                     except KeyError, error:
263                         logger.log_exc("IOTLABAGGREGATE\t get_nodes \
264                                                         position %s " % (error))
265
266                 rspec_node['position'] = position
267                 #rspec_node['interfaces'] = []
268
269                 # Granularity
270                 granularity = Granularity({'grain': grain})
271                 rspec_node['granularity'] = granularity
272                 rspec_node['tags'] = []
273                 if node['hostname'] in slivers:
274                     # add sliver info
275                     sliver = slivers[node['hostname']]
276                     rspec_node['sliver_id'] = sliver['sliver_id']
277                     rspec_node['client_id'] = node['hostname']
278                     rspec_node['slivers'] = [sliver]
279
280                     # slivers always provide the ssh service
281                     login = Login({'authentication': 'ssh-keys',
282                                    'hostname': node['hostname'], 'port': '22',
283                                    'username': sliver['name']})
284                     service = Services({'login': login})
285                     rspec_node['services'] = [service]
286                 rspec_nodes.append(rspec_node)
287
288         return (rspec_nodes)
289
290     def get_all_leases(self, ldap_username):
291         """
292
293         Get list of lease dictionaries which all have the mandatory keys
294         ('lease_id', 'hostname', 'site_id', 'name', 'start_time', 'duration').
295         All the leases running or scheduled are returned.
296
297         :param ldap_username: if ldap uid is not None, looks for the leases
298         belonging to this user.
299         :type ldap_username: string
300         :returns: rspec lease dictionary with keys lease_id, component_id,
301             slice_id, start_time, duration.
302         :rtype: dict
303
304         .. note::There is no filtering of leases within a given time frame.
305             All the running or scheduled leases are returned. options
306             removed SA 15/05/2013
307
308
309         """
310
311         #now = int(time.time())
312         #lease_filter = {'clip': now }
313
314         #if slice_record:
315             #lease_filter.update({'name': slice_record['name']})
316
317         #leases = self.driver.iotlab_api.GetLeases(lease_filter)
318
319         logger.debug("IOTLABAGGREGATE  get_all_leases ldap_username %s "
320                      % (ldap_username))
321         leases = self.driver.iotlab_api.GetLeases(login=ldap_username)
322         grain = self.driver.iotlab_api.GetLeaseGranularity()
323         # site_ids = []
324         rspec_leases = []
325         for lease in leases:
326             #as many leases as there are nodes in the job
327             for node in lease['reserved_nodes']:
328                 rspec_lease = Lease()
329                 rspec_lease['lease_id'] = lease['lease_id']
330                 #site = node['site_id']
331                 iotlab_xrn = iotlab_xrn_object(self.driver.iotlab_api.root_auth,
332                                                node)
333                 rspec_lease['component_id'] = iotlab_xrn.urn
334                 #rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn,\
335                                         #site, node['hostname'])
336                 try:
337                     rspec_lease['slice_id'] = lease['slice_id']
338                 except KeyError:
339                     #No info on the slice used in iotlab_xp table
340                     pass
341                 rspec_lease['start_time'] = lease['t_from']
342                 rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) \
343                      / grain
344                 rspec_leases.append(rspec_lease)
345         return rspec_leases
346
347     def get_rspec(self, slice_xrn=None, login=None, version=None,
348                   options=None):
349         """
350         Returns xml rspec:
351         - a full advertisement rspec with the testbed resources if slice_xrn is
352         not specified.If a lease option is given, also returns the leases
353         scheduled on the testbed.
354         - a manifest Rspec with the leases and nodes in slice's leases if
355         slice_xrn is not None.
356
357         :param slice_xrn: srn of the slice
358         :type slice_xrn: string
359         :param login: user'uid (ldap login) on iotlab
360         :type login: string
361         :param version: can be set to sfa or iotlab
362         :type version: RSpecVersion
363         :param options: used to specify if the leases should also be included in
364             the returned rspec.
365         :type options: dict
366
367         :returns: Xml Rspec.
368         :rtype: XML
369
370
371         """
372
373         ldap_username = None
374         rspec = None
375         version_manager = VersionManager()
376         version = version_manager.get_version(version)
377         logger.debug("IotlabAggregate \t get_rspec ***version %s \
378                     version.type %s  version.version %s options %s \r\n"
379                      % (version, version.type, version.version, options))
380
381         if slice_xrn is None:
382             rspec_version = version_manager._get_version(version.type,
383                                                          version.version, 'ad')
384
385         else:
386             rspec_version = version_manager._get_version(
387                 version.type, version.version, 'manifest')
388
389         slices, slivers = self.get_slice_and_slivers(slice_xrn, login)
390         if slice_xrn and slices is not None:
391             #Get user associated with this slice
392             #for one_slice in slices :
393             ldap_username = slices[0]['reg_researchers'][0].__dict__['hrn']
394              # ldap_username = slices[0]['user']
395             tmp = ldap_username.split('.')
396             ldap_username = tmp[1]
397             logger.debug("IotlabAggregate \tget_rspec **** \
398                     LDAP USERNAME %s \r\n" \
399                     % (ldap_username))
400         #at this point sliver may be empty if no iotlab job
401         #is running for this user/slice.
402         rspec = RSpec(version=rspec_version, user_options=options)
403
404         logger.debug("\r\n \r\n IotlabAggregate \tget_rspec *** \
405                       slice_xrn %s slices  %s\r\n \r\n"
406                      % (slice_xrn, slices))
407
408         if options is not None and 'list_leases' in options:
409             lease_option = options['list_leases']
410         else:
411             #If no options are specified, at least print the resources
412             lease_option = 'all'
413            #if slice_xrn :
414                #lease_option = 'all'
415
416         if lease_option in ['all', 'resources']:
417         #if not options.get('list_leases') or options.get('list_leases')
418         #and options['list_leases'] != 'leases':
419             nodes = self.get_nodes(slices, slivers)
420             if slice_xrn and slices is None:
421               nodes = []
422             logger.debug("\r\n")
423             logger.debug("IotlabAggregate \t lease_option %s \
424                           get rspec  ******* nodes %s"
425                          % (lease_option, nodes))
426
427             sites_set = set([node['location']['site'] for node in nodes])
428
429             #In case creating a job,  slice_xrn is not set to None
430             rspec.version.add_nodes(nodes)
431             if slice_xrn and slices is not None:
432             #     #Get user associated with this slice
433             #     #for one_slice in slices :
434             #     ldap_username = slices[0]['reg_researchers']
435             #      # ldap_username = slices[0]['user']
436             #     tmp = ldap_username.split('.')
437             #     ldap_username = tmp[1]
438             #      # ldap_username = tmp[1].split('_')[0]
439
440                 logger.debug("IotlabAggregate \tget_rspec **** \
441                         version type %s ldap_ user %s \r\n" \
442                         % (version.type, ldap_username))
443                 if version.type == "Iotlab":
444                     rspec.version.add_connection_information(
445                         ldap_username, sites_set)
446
447             default_sliver = slivers.get('default_sliver', [])
448             if default_sliver and len(nodes) is not 0:
449                 #default_sliver_attribs = default_sliver.get('tags', [])
450                 logger.debug("IotlabAggregate \tget_rspec **** \
451                         default_sliver%s \r\n" % (default_sliver))
452                 for attrib in default_sliver:
453                     rspec.version.add_default_sliver_attribute(
454                         attrib, default_sliver[attrib])
455
456         if lease_option in ['all','leases']:
457             leases = self.get_all_leases(ldap_username)
458             rspec.version.add_leases(leases)
459             logger.debug("IotlabAggregate \tget_rspec **** \
460                        FINAL RSPEC %s \r\n" % (rspec.toxml()))
461         return rspec.toxml()