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