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