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