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