FIT IoT-Lab aggregate: fixed archi and radio format
[sfa.git] / sfa / iotlab / iotlabaggregate.py
1 # -*- coding:utf-8 -*-
2 """ aggregate class management """
3
4 from sfa.util.xrn import Xrn, hrn_to_urn
5 from sfa.util.sfatime import utcparse, datetime_to_string
6 from sfa.util.sfalogging import logger
7 from sfa.rspecs.rspec import RSpec
8 from sfa.rspecs.elements.hardware_type import HardwareType
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 from sfa.rspecs.elements.services import ServicesElement
13 from sfa.rspecs.elements.login import Login
14 from sfa.rspecs.elements.sliver import Sliver
15 from sfa.rspecs.elements.versions.iotlabv1Node import IotlabPosition
16 from sfa.rspecs.elements.versions.iotlabv1Node import IotlabNode
17 from sfa.rspecs.elements.versions.iotlabv1Node import IotlabLocation
18 from sfa.iotlab.iotlablease import LeaseTable
19 import time
20 import datetime
21
22
23 class IotLABAggregate(object):
24     """
25     SFA aggregate for Iot-LAB testbed
26     """
27
28     def __init__(self, driver):
29         self.driver = driver
30
31     def leases_to_rspec_leases(self, leases):
32         """ Get leases attributes list"""
33         rspec_leases = []
34         for lease in leases:
35             for node in lease['resources']:
36                 rspec_lease = Lease()
37                 rspec_lease['lease_id'] = lease['id']
38                 iotlab_xrn = Xrn('.'.join([self.driver.root_auth,
39                                            Xrn.escape(node)]),
40                                  type='node')
41                 rspec_lease['component_id'] = iotlab_xrn.urn
42                 rspec_lease['start_time'] = str(lease['date'])
43                 # duration in minutes
44                 duration = int(lease['duration']) / 60
45                 rspec_lease['duration'] = duration
46                 rspec_lease['slice_id'] = lease['slice_id']
47                 rspec_leases.append(rspec_lease)
48         return rspec_leases
49
50     def node_to_rspec_node(self, node):
51         """ Get node attributes """
52         rspec_node = IotlabNode()
53         rspec_node['mobile'] = node['mobile']
54         rspec_node['archi'] = node['archi']
55         if ':' in node['archi']:
56             rspec_node['radio'] = (node['archi'].split(':'))[1]
57         else:
58             rspec_node['radio'] = node['archi']
59         iotlab_xrn = Xrn('.'.join([self.driver.root_auth,
60                                    Xrn.escape(node['network_address'])]),
61                          type='node')
62         # rspec_node['boot_state'] = 'true'
63         if node['state'] == 'Absent' or \
64            node['state'] == 'Suspected' or \
65            node['state'] == 'Dead' or \
66            node['state'] == 'Busy':
67             rspec_node['available'] = 'false'
68         else:
69             rspec_node['available'] = 'true'
70         rspec_node['component_id'] = iotlab_xrn.urn
71         rspec_node['component_name'] = node['network_address']
72         rspec_node['component_manager_id'] = hrn_to_urn(self.driver.root_auth,
73                                                         'authority+sa')
74         rspec_node['authority_id'] = rspec_node['component_manager_id']
75         rspec_node['exclusive'] = 'true'
76         rspec_node['hardware_types'] = \
77             [HardwareType({'name': node['archi']})]
78         location = IotlabLocation({'country': 'France',
79                                    'site': node['site']})
80         rspec_node['location'] = location
81         position = IotlabPosition()
82         for field in position:
83             position[field] = node[field]
84         granularity = Granularity({'grain': 30})
85         rspec_node['granularity'] = granularity
86         rspec_node['tags'] = []
87         return rspec_node
88
89     def sliver_to_rspec_node(self, sliver):
90         """ Get node and sliver attributes """
91         rspec_node = self.node_to_rspec_node(sliver)
92         rspec_node['expires'] = datetime_to_string(utcparse(sliver['expires']))
93         rspec_node['sliver_id'] = sliver['sliver_id']
94         rspec_sliver = Sliver({'sliver_id': sliver['sliver_id'],
95                                'name': sliver['name'],
96                                'type': sliver['archi'],
97                                'tags': []})
98         rspec_node['slivers'] = [rspec_sliver]
99         # slivers always provide the ssh service
100         login = Login({'authentication': 'ssh-keys',
101                        'hostname': sliver['hostname'],
102                        'port': '22',
103                        'username': sliver['name'],
104                        'login': sliver['name']
105                        })
106         service = ServicesElement({'login': login})
107         rspec_node['services'] = [service]
108         return rspec_node
109
110     @classmethod
111     def rspec_node_to_geni_sliver(cls, rspec_node):
112         """ Get sliver status """
113         geni_sliver = {'geni_sliver_urn': rspec_node['sliver_id'],
114                        'geni_expires': rspec_node['expires'],
115                        'geni_allocation_status': 'geni_allocated',
116                        'geni_operational_status': 'geni_pending_allocation',
117                        'geni_error': '',
118                        }
119         return geni_sliver
120
121     def list_resources(self, version=None, options=None):
122         """
123         list_resources method sends a RSpec with all Iot-LAB testbed nodes
124         and leases (OAR job submission). For leases we get all OAR jobs with
125         state Waiting or Running. If we have an entry in SFA database
126         (lease table) with OAR job id this submission was launched by SFA
127         driver, otherwise it was launched by Iot-LAB Webportal or CLI-tools
128
129         :Example:
130         <rspec>
131         ...
132         <node component_manager_id="urn:publicid:IDN+iotlab+authority+sa"
133               component_id=
134                   "urn:publicid:IDN+iotlab+node+m3-10.devgrenoble.iot-lab.info"
135               exclusive="true" component_name="m3-10.devgrenoble.iot-lab.info">
136             <hardware_type name="iotlab-node"/>
137             <location country="France"/>
138             <granularity grain="60"/>
139             ...
140         </node>
141         ...
142         <lease slice_id="urn:publicid:IDN+onelab:inria+slice+test_iotlab"
143             start_time="1427792400" duration="30">
144             <node component_id=
145                 "urn:publicid:IDN+iotlab+node+m3-10.grenoble.iot-lab.info"/>
146         </lease>
147         ...
148         </rspec>
149         """
150         # pylint:disable=R0914,W0212
151         logger.warning("iotlabaggregate list_resources")
152         logger.warning("iotlabaggregate list_resources options %s" % options)
153         if not options:
154             options = {}
155
156         version_manager = VersionManager()
157         version = version_manager.get_version(version)
158         rspec_version = version_manager._get_version(version.type,
159                                                      version.version,
160                                                      'ad')
161         rspec = RSpec(version=rspec_version, user_options=options)
162
163         nodes = self.driver.shell.get_nodes()
164         reserved_nodes = self.driver.shell.get_reserved_nodes()
165         if 'error' not in nodes and 'error' not in reserved_nodes:
166             # convert nodes to rspec nodes
167             rspec_nodes = []
168             for node in nodes:
169                 rspec_node = self.node_to_rspec_node(nodes[node])
170                 rspec_nodes.append(rspec_node)
171             rspec.version.add_nodes(rspec_nodes)
172
173             leases = []
174             db_leases = {}
175             # find OAR jobs id for all slices in SFA database
176             for lease in self.driver.api.dbsession().query(LeaseTable).all():
177                 db_leases[lease.job_id] = lease.slice_hrn
178
179             for lease_id in reserved_nodes:
180                 # onelab slice = job submission from OneLAB
181                 if lease_id in db_leases:
182                     reserved_nodes[lease_id]['slice_id'] = \
183                         hrn_to_urn(db_leases[lease_id],
184                                    'slice')
185                 # iotlab slice = job submission from Iot-LAB
186                 else:
187                     reserved_nodes[lease_id]['slice_id'] = \
188                         hrn_to_urn(self.driver.root_auth + '.' +
189                                    reserved_nodes[lease_id][
190                                        'owner'] + "_slice",
191                                    'slice')
192                 leases.append(reserved_nodes[lease_id])
193
194             rspec_leases = self.leases_to_rspec_leases(leases)
195             rspec.version.add_leases(rspec_leases)
196         return rspec.toxml()
197
198     def get_slivers(self, urns, leases, nodes):
199         """ Get slivers attributes list """
200         logger.warning("iotlabaggregate get_slivers")
201         logger.warning("iotlabaggregate get_slivers urns %s" % urns)
202         slivers = []
203         for lease in leases:
204             for node in lease['resources']:
205                 network_address = node.split(".")
206                 sliver_node = nodes[node]
207                 sliver_hrn = '%s.%s-%s' % (self.driver.hrn,
208                                            lease['id'],
209                                            network_address[0])
210                 start_time = datetime.datetime.fromtimestamp(lease['date'])
211                 duration = datetime.timedelta(seconds=int(lease['duration']))
212                 sliver_node['expires'] = start_time + duration
213                 sliver_node['sliver_id'] = Xrn(sliver_hrn,
214                                                type='sliver').urn
215                 # frontend SSH hostname
216                 sliver_node['hostname'] = '.'.join(network_address[1:])
217                 # user login
218                 sliver_node['name'] = lease['owner']
219                 slivers.append(sliver_node)
220         return slivers
221
222     def _delete_db_lease(self, job_id):
223         """ Delete lease table row in SFA database """
224         logger.warning("iotlabdriver _delete_db_lease lease job_id : %s"
225                        % job_id)
226         self.driver.api.dbsession().query(LeaseTable).filter(
227             LeaseTable.job_id == job_id).delete()
228         self.driver.api.dbsession().commit()
229
230     def describe(self, urns, version=None, options=None):
231         """
232         describe method returns slice slivers (allocated resources) and leases
233         (OAR job submission). We search in lease table of SFA database all OAR
234         jobs id for this slice and match OAR jobs with state Waiting or
235         Running. If OAR job id doesn't exist the experiment is terminated and
236         we delete the database table entry. Otherwise we add slivers and leases
237         in the response
238
239         :returns:
240             geni_slivers : a list of allocated slivers with information about
241                            their allocation and operational state
242             geni_urn : the URN of the slice in which the sliver has been
243                        allocated
244             geni_rspec:  a RSpec describing the allocated slivers and leases
245         :rtype: dict
246
247         :Example:
248         <rspec>
249         ...
250         <node component_manager_id="urn:publicid:IDN+iotlab+authority+sa"
251               component_id=
252                   "urn:publicid:IDN+iotlab+node+m3-10.grenoble.iot-lab.info"
253               client_id="m3-10.grenoble.iot-lab.info"
254               sliver_id="urn:publicid:IDN+iotlab+sliver+9953-m3-10"
255               exclusive="true" component_name="m3-10.grenoble.iot-lab.info">
256             <hardware_type name="iotlab-node"/>
257             <location country="France"/>
258             <granularity grain="30"/>
259             <sliver_type name="iotlab-exclusive"/>
260         </node>
261         <lease slice_id="urn:publicid:IDN+onelab:inria+slice+test_iotlab"
262                start_time="1427792428" duration="29">
263             <node component_id=
264                 "urn:publicid:IDN+iotlab+node+m3-10.grenoble.iot-lab.info"/>
265         </lease>
266         ...
267         </rspec>
268
269         """
270         # pylint:disable=R0914,W0212
271         logger.warning("iotlabaggregate describe")
272         logger.warning("iotlabaggregate describe urns : %s" % urns)
273         if not options:
274             options = {}
275         version_manager = VersionManager()
276         version = version_manager.get_version(version)
277         rspec_version = version_manager._get_version(version.type,
278                                                      version.version,
279                                                      'manifest')
280         rspec = RSpec(version=rspec_version, user_options=options)
281         xrn = Xrn(urns[0])
282         geni_slivers = []
283
284         nodes = self.driver.shell.get_nodes()
285         reserved_nodes = self.driver.shell.get_reserved_nodes()
286         if 'error' not in nodes and 'error' not in reserved_nodes:
287             # find OAR jobs id for one slice in SFA database
288             db_leases = [(lease.job_id, lease.slice_hrn)
289                          for lease in self.driver.api.dbsession()
290                          .query(LeaseTable)
291                          .filter(LeaseTable.slice_hrn == xrn.hrn).all()]
292
293             leases = []
294             for job_id, slice_hrn in db_leases:
295                 # OAR job terminated, we delete entry in database
296                 if job_id not in reserved_nodes:
297                     self._delete_db_lease(job_id)
298                 else:
299                     # onelab slice = job submission from OneLAB
300                     lease = reserved_nodes[job_id]
301                     lease['slice_id'] = hrn_to_urn(slice_hrn, 'slice')
302                     leases.append(lease)
303
304             # get slivers
305             slivers = self.get_slivers(urns, leases, nodes)
306             if slivers:
307                 date = utcparse(slivers[0]['expires'])
308                 rspec_expires = datetime_to_string(date)
309             else:
310                 rspec_expires = datetime_to_string(utcparse(time.time()))
311             rspec.xml.set('expires', rspec_expires)
312
313             rspec_nodes = []
314
315             for sliver in slivers:
316                 rspec_node = self.sliver_to_rspec_node(sliver)
317                 rspec_nodes.append(rspec_node)
318                 geni_sliver = self.rspec_node_to_geni_sliver(rspec_node)
319                 geni_slivers.append(geni_sliver)
320             logger.warning("iotlabaggregate describe geni_slivers %s" %
321                            geni_slivers)
322             rspec.version.add_nodes(rspec_nodes)
323
324             rspec_leases = self.leases_to_rspec_leases(leases)
325             logger.warning("iotlabaggregate describe rspec_leases %s" %
326                            rspec_leases)
327             rspec.version.add_leases(rspec_leases)
328
329         return {'geni_urn': urns[0],
330                 'geni_rspec': rspec.toxml(),
331                 'geni_slivers': geni_slivers}