Adding cover to tox.ini default envs
[osm/RO.git] / RO / osm_ro / wim / tests / test_actions.py
1 # -*- coding: utf-8 -*-
2 ##
3 # Copyright 2018 University of Bristol - High Performance Networks Research
4 # Group
5 # All Rights Reserved.
6 #
7 # Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
8 # Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
9 #
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
13 #
14 # http://www.apache.org/licenses/LICENSE-2.0
15 #
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
20 # under the License.
21 #
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: <highperformance-networks@bristol.ac.uk>
24 #
25 # Neither the name of the University of Bristol nor the names of its
26 # contributors may be used to endorse or promote products derived from
27 # this software without specific prior written permission.
28 #
29 # This work has been performed in the context of DCMS UK 5G Testbeds
30 # & Trials Programme and in the framework of the Metro-Haul project -
31 # funded by the European Commission under Grant number 761727 through the
32 # Horizon 2020 and 5G-PPP programmes.
33 ##
34 # pylint: disable=E1101
35
36 # from __future__ import unicode_literals, print_function
37
38 import json
39 import unittest
40 from time import time
41
42 from unittest.mock import MagicMock, patch
43
44 from . import fixtures as eg
45 from ...tests.db_helpers import (
46 TestCaseWithDatabasePerTest,
47 disable_foreign_keys,
48 uuid,
49 )
50 from ..persistence import WimPersistence, preprocess_record
51 from ..wan_link_actions import WanLinkCreate, WanLinkDelete
52 from ..sdnconn import SdnConnectorError
53
54
55 class TestActionsWithDb(TestCaseWithDatabasePerTest):
56 def setUp(self):
57 super(TestActionsWithDb, self).setUp()
58 self.persist = WimPersistence(self.db)
59 self.connector = MagicMock()
60 self.ovim = MagicMock()
61
62
63 class TestCreate(TestActionsWithDb):
64 @disable_foreign_keys
65 def test_process__instance_nets_on_build(self):
66 # Given we want 1 WAN link between 2 datacenters
67 # and the local network in each datacenter is still being built
68 wan_link = eg.instance_wim_nets()
69 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
70 for net in instance_nets:
71 net['status'] = 'BUILD'
72 self.populate([{'instance_nets': instance_nets,
73 'instance_wim_nets': wan_link}])
74
75 # When we try to process a CREATE action that refers to the same
76 # instance_scenario_id and sce_net_id
77 now = time()
78 action = WanLinkCreate(eg.wim_actions('CREATE')[0])
79 action.instance_scenario_id = instance_nets[0]['instance_scenario_id']
80 action.sce_net_id = instance_nets[0]['sce_net_id']
81 # -- ensure it is in the database for updates --> #
82 action_record = action.as_record()
83 action_record['extra'] = json.dumps(action_record['extra'])
84 self.populate([{'vim_wim_actions': action_record}])
85 # <-- #
86 action.process(self.connector, self.persist, self.ovim)
87
88 # Then the action should be defered
89 assert action.is_scheduled
90 self.assertEqual(action.extra['attempts'], 1)
91 self.assertGreater(action.extra['last_attempted_at'], now)
92
93 @disable_foreign_keys
94 def test_process__instance_nets_on_error(self):
95 # Given we want 1 WAN link between 2 datacenters
96 # and at least one local network is in a not good state (error, or
97 # being deleted)
98 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
99 instance_nets[1]['status'] = 'SCHEDULED_DELETION'
100 wan_link = eg.instance_wim_nets()
101 self.populate([{'instance_nets': instance_nets,
102 'instance_wim_nets': wan_link}])
103
104 # When we try to process a CREATE action that refers to the same
105 # instance_scenario_id and sce_net_id
106 action = WanLinkCreate(eg.wim_actions('CREATE')[0])
107 action.instance_scenario_id = instance_nets[0]['instance_scenario_id']
108 action.sce_net_id = instance_nets[0]['sce_net_id']
109 # -- ensure it is in the database for updates --> #
110 action_record = action.as_record()
111 action_record['extra'] = json.dumps(action_record['extra'])
112 self.populate([{'vim_wim_actions': action_record}])
113 # <-- #
114 action.process(self.connector, self.persist, self.ovim)
115
116 # Then the action should fail
117 assert action.is_failed
118 self.assertIn('issue with the local networks', action.error_msg)
119 self.assertIn('SCHEDULED_DELETION', action.error_msg)
120
121 def prepare_create__rules(self):
122 db_state = eg.consistent_set(num_wims=1, num_tenants=1,
123 num_datacenters=2,
124 external_ports_config=True)
125
126 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
127 status='ACTIVE')
128 for i, net in enumerate(instance_nets):
129 net['vim_info'] = {}
130 net['vim_info']['provider:physical_network'] = 'provider'
131 net['vim_info']['encapsulation_type'] = 'vlan'
132 net['vim_info']['encapsulation_id'] = i
133 net['sdn_net_id'] = uuid('sdn-net%d' % i)
134
135 instance_action = eg.instance_action(action_id='ACTION-000')
136
137 db_state += [
138 {'instance_wim_nets': eg.instance_wim_nets()},
139 {'instance_nets': [preprocess_record(r) for r in instance_nets]},
140 {'instance_actions': instance_action}]
141
142 action = WanLinkCreate(
143 eg.wim_actions('CREATE', action_id='ACTION-000')[0])
144 # --> ensure it is in the database for updates --> #
145 action_record = action.as_record()
146 action_record['extra'] = json.dumps(action_record['extra'])
147 db_state += [{'vim_wim_actions': action_record}]
148
149 return db_state, action
150
151 @disable_foreign_keys
152 def test_process__rules(self):
153 # Given we want 1 WAN link between 2 datacenters
154 # and the local network in each datacenter is already created
155 db_state, action = self.prepare_create__rules()
156 self.populate(db_state)
157
158 instance_action = self.persist.get_by_uuid(
159 'instance_actions', action.instance_action_id)
160 number_done = instance_action['number_done']
161 number_failed = instance_action['number_failed']
162
163 # If the connector works fine
164 with patch.object(self.connector, 'create_connectivity_service',
165 lambda *_, **__: (uuid('random-id'), None)):
166 # When we try to process a CREATE action that refers to the same
167 # instance_scenario_id and sce_net_id
168 action.process(self.connector, self.persist, self.ovim)
169
170 # Then the action should be succeeded
171 db_action = self.persist.query_one('vim_wim_actions', WHERE={
172 'instance_action_id': action.instance_action_id,
173 'task_index': action.task_index})
174 self.assertEqual(db_action['status'], 'DONE')
175
176 instance_action = self.persist.get_by_uuid(
177 'instance_actions', action.instance_action_id)
178 self.assertEqual(instance_action['number_done'], number_done + 1)
179 self.assertEqual(instance_action['number_failed'], number_failed)
180
181 @disable_foreign_keys
182 def test_process__rules_fail(self):
183 # Given we want 1 WAN link between 2 datacenters
184 # and the local network in each datacenter is already created
185 db_state, action = self.prepare_create__rules()
186 self.populate(db_state)
187
188 instance_action = self.persist.get_by_uuid(
189 'instance_actions', action.instance_action_id)
190 number_done = instance_action['number_done']
191 number_failed = instance_action['number_failed']
192
193 # If the connector raises an error
194 with patch.object(self.connector, 'create_connectivity_service',
195 MagicMock(side_effect=SdnConnectorError('foobar'))):
196 # When we try to process a CREATE action that refers to the same
197 # instance_scenario_id and sce_net_id
198 action.process(self.connector, self.persist, self.ovim)
199
200 # Then the action should be fail
201 db_action = self.persist.query_one('vim_wim_actions', WHERE={
202 'instance_action_id': action.instance_action_id,
203 'task_index': action.task_index})
204 self.assertEqual(db_action['status'], 'FAILED')
205
206 instance_action = self.persist.get_by_uuid(
207 'instance_actions', action.instance_action_id)
208 self.assertEqual(instance_action['number_done'], number_done)
209 self.assertEqual(instance_action['number_failed'], number_failed + 1)
210
211 def prepare_create__sdn(self):
212 db_state = eg.consistent_set(num_wims=1, num_tenants=1,
213 num_datacenters=2,
214 external_ports_config=False)
215
216 # Make sure all port_mappings are predictable
217 switch = 'AA:AA:AA:AA:AA:AA:AA:AA'
218 port = 1
219 port_mappings = next(r['wim_port_mappings']
220 for r in db_state if 'wim_port_mappings' in r)
221 for mapping in port_mappings:
222 mapping['device_id'] = switch
223 mapping['device_interface_id'] = port
224
225 instance_action = eg.instance_action(action_id='ACTION-000')
226 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
227 status='ACTIVE')
228 for i, net in enumerate(instance_nets):
229 net['sdn_net_id'] = uuid('sdn-net%d' % i)
230
231 db_state += [{'instance_nets': instance_nets},
232 {'instance_wim_nets': eg.instance_wim_nets()},
233 {'instance_actions': instance_action}]
234
235 action = WanLinkCreate(
236 eg.wim_actions('CREATE', action_id='ACTION-000')[0])
237 # --> ensure it is in the database for updates --> #
238 action_record = action.as_record()
239 action_record['extra'] = json.dumps(action_record['extra'])
240 db_state += [{'vim_wim_actions': action_record}]
241
242 ovim_patch = patch.object(
243 self.ovim, 'get_ports', MagicMock(return_value=[{
244 'switch_dpid': switch,
245 'switch_port': port,
246 }]))
247
248 return db_state, action, ovim_patch
249
250 @disable_foreign_keys
251 def test_process__sdn(self):
252 # Given we want 1 WAN link between 2 datacenters
253 # and the local network in each datacenter is already created
254 db_state, action, ovim_patch = self.prepare_create__sdn()
255 self.populate(db_state)
256
257 instance_action = self.persist.get_by_uuid(
258 'instance_actions', action.instance_action_id)
259 number_done = instance_action['number_done']
260 number_failed = instance_action['number_failed']
261
262 connector_patch = patch.object(
263 self.connector, 'create_connectivity_service',
264 lambda *_, **__: (uuid('random-id'), None))
265
266 # If the connector works fine
267 with connector_patch, ovim_patch:
268 # When we try to process a CREATE action that refers to the same
269 # instance_scenario_id and sce_net_id
270 action.process(self.connector, self.persist, self.ovim)
271
272 # Then the action should be succeeded
273 db_action = self.persist.query_one('vim_wim_actions', WHERE={
274 'instance_action_id': action.instance_action_id,
275 'task_index': action.task_index})
276 self.assertEqual(db_action['status'], 'DONE')
277
278 instance_action = self.persist.get_by_uuid(
279 'instance_actions', action.instance_action_id)
280 self.assertEqual(instance_action['number_done'], number_done + 1)
281 self.assertEqual(instance_action['number_failed'], number_failed)
282
283 @disable_foreign_keys
284 def test_process__sdn_fail(self):
285 # Given we want 1 WAN link between 2 datacenters
286 # and the local network in each datacenter is already created
287 db_state, action, ovim_patch = self.prepare_create__sdn()
288 self.populate(db_state)
289
290 instance_action = self.persist.get_by_uuid(
291 'instance_actions', action.instance_action_id)
292 number_done = instance_action['number_done']
293 number_failed = instance_action['number_failed']
294
295 connector_patch = patch.object(
296 self.connector, 'create_connectivity_service',
297 MagicMock(side_effect=SdnConnectorError('foobar')))
298
299 # If the connector throws an error
300 with connector_patch, ovim_patch:
301 # When we try to process a CREATE action that refers to the same
302 # instance_scenario_id and sce_net_id
303 action.process(self.connector, self.persist, self.ovim)
304
305 # Then the action should be fail
306 db_action = self.persist.query_one('vim_wim_actions', WHERE={
307 'instance_action_id': action.instance_action_id,
308 'task_index': action.task_index})
309 self.assertEqual(db_action['status'], 'FAILED')
310
311 instance_action = self.persist.get_by_uuid(
312 'instance_actions', action.instance_action_id)
313 self.assertEqual(instance_action['number_done'], number_done)
314 self.assertEqual(instance_action['number_failed'], number_failed + 1)
315
316
317 class TestDelete(TestActionsWithDb):
318 @disable_foreign_keys
319 def test_process__no_internal_id(self):
320 # Given no WAN link was created yet,
321 # when we try to process a DELETE action, with no wim_internal_id
322 action = WanLinkDelete(eg.wim_actions('DELETE')[0])
323 action.wim_internal_id = None
324 # -- ensure it is in the database for updates --> #
325 action_record = action.as_record()
326 action_record['extra'] = json.dumps(action_record['extra'])
327 self.populate([{'vim_wim_actions': action_record,
328 'instance_wim_nets': eg.instance_wim_nets()}])
329 # <-- #
330 action.process(self.connector, self.persist, self.ovim)
331
332 # Then the action should succeed
333 assert action.is_done
334
335 def prepare_delete(self):
336 db_state = eg.consistent_set(num_wims=1, num_tenants=1,
337 num_datacenters=2,
338 external_ports_config=True)
339
340 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
341 status='ACTIVE')
342 for i, net in enumerate(instance_nets):
343 net['vim_info'] = {}
344 net['vim_info']['provider:physical_network'] = 'provider'
345 net['vim_info']['encapsulation_type'] = 'vlan'
346 net['vim_info']['encapsulation_id'] = i
347 net['sdn_net_id'] = uuid('sdn-net%d' % i)
348
349 instance_action = eg.instance_action(action_id='ACTION-000')
350
351 db_state += [
352 {'instance_wim_nets': eg.instance_wim_nets()},
353 {'instance_nets': [preprocess_record(r) for r in instance_nets]},
354 {'instance_actions': instance_action}]
355
356 action = WanLinkDelete(
357 eg.wim_actions('DELETE', action_id='ACTION-000')[0])
358 # --> ensure it is in the database for updates --> #
359 action_record = action.as_record()
360 action_record['extra'] = json.dumps(action_record['extra'])
361 db_state += [{'vim_wim_actions': action_record}]
362
363 return db_state, action
364
365 @disable_foreign_keys
366 def test_process(self):
367 # Given we want to delete 1 WAN link between 2 datacenters
368 db_state, action = self.prepare_delete()
369 self.populate(db_state)
370
371 instance_action = self.persist.get_by_uuid(
372 'instance_actions', action.instance_action_id)
373 number_done = instance_action['number_done']
374 number_failed = instance_action['number_failed']
375
376 connector_patch = patch.object(
377 self.connector, 'delete_connectivity_service')
378
379 # If the connector works fine
380 with connector_patch:
381 # When we try to process a DELETE action that refers to the same
382 # instance_scenario_id and sce_net_id
383 action.process(self.connector, self.persist, self.ovim)
384
385 # Then the action should be succeeded
386 db_action = self.persist.query_one('vim_wim_actions', WHERE={
387 'instance_action_id': action.instance_action_id,
388 'task_index': action.task_index})
389 self.assertEqual(db_action['status'], 'DONE')
390
391 instance_action = self.persist.get_by_uuid(
392 'instance_actions', action.instance_action_id)
393 self.assertEqual(instance_action['number_done'], number_done + 1)
394 self.assertEqual(instance_action['number_failed'], number_failed)
395
396 @disable_foreign_keys
397 def test_process__wan_link_error(self):
398 # Given we have a delete action that targets a wan link with an error
399 db_state, action = self.prepare_delete()
400 wan_link = [tables for tables in db_state
401 if tables.get('instance_wim_nets')][0]['instance_wim_nets']
402 from pprint import pprint
403 pprint(wan_link)
404 wan_link[0]['status'] = 'ERROR'
405 self.populate(db_state)
406
407 # When we try to process it
408 action.process(self.connector, self.persist, self.ovim)
409
410 # Then it should fail
411 assert action.is_failed
412
413 def create_action(self):
414 action = WanLinkCreate(
415 eg.wim_actions('CREATE', action_id='ACTION-000')[0])
416 # --> ensure it is in the database for updates --> #
417 action_record = action.as_record()
418 action_record['extra'] = json.dumps(action_record['extra'])
419 self.populate([{'vim_wim_actions': action_record}])
420
421 return action
422
423 @disable_foreign_keys
424 def test_create_and_delete(self):
425 # Given a CREATE action was well succeeded
426 db_state, delete_action = self.prepare_delete()
427 self.populate(db_state)
428
429 delete_action.save(self.persist, task_index=1)
430 create_action = self.create_action()
431
432 connector_patch = patch.multiple(
433 self.connector,
434 delete_connectivity_service=MagicMock(),
435 create_connectivity_service=(
436 lambda *_, **__: (uuid('random-id'), None)))
437
438 with connector_patch: # , ovim_patch:
439 create_action.process(self.connector, self.persist, self.ovim)
440
441 # When we try to process a CREATE action that refers to the same
442 # instance_scenario_id and sce_net_id
443 with connector_patch:
444 delete_action.process(self.connector, self.persist, self.ovim)
445
446 # Then the DELETE action should be successful
447 db_action = self.persist.query_one('vim_wim_actions', WHERE={
448 'instance_action_id': delete_action.instance_action_id,
449 'task_index': delete_action.task_index})
450 self.assertEqual(db_action['status'], 'DONE')
451
452
453 if __name__ == '__main__':
454 unittest.main()