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