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