Implement feature 5949
[osm/RO.git] / 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 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
51 from ..wan_link_actions import WanLinkCreate, WanLinkDelete
52 from ..wimconn import WimConnectorError
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__sdn(self):
122 db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
123
124 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
125 port_mappings = [
126 eg.wim_port_mapping(0, 0),
127 eg.wim_port_mapping(0, 1)
128 ]
129 instance_action = eg.instance_action(action_id='ACTION-000')
130 for i, net in enumerate(instance_nets):
131 net['status'] = 'ACTIVE'
132 net['sdn_net_id'] = uuid('sdn-net%d' % i)
133
134 db_state += [{'instance_nets': instance_nets},
135 {'instance_wim_nets': eg.instance_wim_nets()},
136 {'wim_port_mappings': port_mappings},
137 {'instance_actions': instance_action}]
138
139 action = WanLinkCreate(
140 eg.wim_actions('CREATE', action_id='ACTION-000')[0])
141 # --> ensure it is in the database for updates --> #
142 action_record = action.as_record()
143 action_record['extra'] = json.dumps(action_record['extra'])
144 self.populate([{'vim_wim_actions': action_record}])
145
146 return db_state, action
147
148 @disable_foreign_keys
149 def test_process__sdn(self):
150 # Given we want 1 WAN link between 2 datacenters
151 # and the local network in each datacenter is already created
152 db_state, action = self.prepare_create__sdn()
153 self.populate(db_state)
154
155 instance_action = self.persist.get_by_uuid(
156 'instance_actions', action.instance_action_id)
157 number_done = instance_action['number_done']
158 number_failed = instance_action['number_failed']
159
160 connector_patch = patch.object(
161 self.connector, 'create_connectivity_service',
162 lambda *_, **__: (uuid('random-id'), None))
163
164 ovim_patch = patch.object(
165 self.ovim, 'get_ports', MagicMock(return_value=[{
166 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
167 'switch_port': 1,
168 }]))
169
170 # If the connector works fine
171 with connector_patch, ovim_patch:
172 # When we try to process a CREATE action that refers to the same
173 # instance_scenario_id and sce_net_id
174 action.process(self.connector, self.persist, self.ovim)
175
176 # Then the action should be succeeded
177 db_action = self.persist.query_one('vim_wim_actions', WHERE={
178 'instance_action_id': action.instance_action_id,
179 'task_index': action.task_index})
180 self.assertEqual(db_action['status'], 'DONE')
181
182 instance_action = self.persist.get_by_uuid(
183 'instance_actions', action.instance_action_id)
184 self.assertEqual(instance_action['number_done'], number_done + 1)
185 self.assertEqual(instance_action['number_failed'], number_failed)
186
187 @disable_foreign_keys
188 def test_process__sdn_fail(self):
189 # Given we want 1 WAN link between 2 datacenters
190 # and the local network in each datacenter is already created
191 db_state, action = self.prepare_create__sdn()
192 self.populate(db_state)
193
194 instance_action = self.persist.get_by_uuid(
195 'instance_actions', action.instance_action_id)
196 number_done = instance_action['number_done']
197 number_failed = instance_action['number_failed']
198
199 connector_patch = patch.object(
200 self.connector, 'create_connectivity_service',
201 MagicMock(side_effect=WimConnectorError('foobar')))
202
203 ovim_patch = patch.object(
204 self.ovim, 'get_ports', MagicMock(return_value=[{
205 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
206 'switch_port': 1,
207 }]))
208
209 # If the connector throws an error
210 with connector_patch, ovim_patch:
211 # When we try to process a CREATE action that refers to the same
212 # instance_scenario_id and sce_net_id
213 action.process(self.connector, self.persist, self.ovim)
214
215 # Then the action should be fail
216 db_action = self.persist.query_one('vim_wim_actions', WHERE={
217 'instance_action_id': action.instance_action_id,
218 'task_index': action.task_index})
219 self.assertEqual(db_action['status'], 'FAILED')
220
221 instance_action = self.persist.get_by_uuid(
222 'instance_actions', action.instance_action_id)
223 self.assertEqual(instance_action['number_done'], number_done)
224 self.assertEqual(instance_action['number_failed'], number_failed + 1)
225
226
227 class TestDelete(TestActionsWithDb):
228 @disable_foreign_keys
229 def test_process__no_internal_id(self):
230 # Given no WAN link was created yet,
231 # when we try to process a DELETE action, with no wim_internal_id
232 action = WanLinkDelete(eg.wim_actions('DELETE')[0])
233 action.wim_internal_id = None
234 # -- ensure it is in the database for updates --> #
235 action_record = action.as_record()
236 action_record['extra'] = json.dumps(action_record['extra'])
237 self.populate([{'vim_wim_actions': action_record,
238 'instance_wim_nets': eg.instance_wim_nets()}])
239 # <-- #
240 action.process(self.connector, self.persist, self.ovim)
241
242 # Then the action should succeed
243 assert action.is_done
244
245 def prepare_delete(self):
246 db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
247
248 instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
249 port_mappings = [
250 eg.wim_port_mapping(0, 0),
251 eg.wim_port_mapping(0, 1)
252 ]
253 instance_action = eg.instance_action(action_id='ACTION-000')
254 for i, net in enumerate(instance_nets):
255 net['status'] = 'ACTIVE'
256 net['sdn_net_id'] = uuid('sdn-net%d' % i)
257
258 db_state += [{'instance_nets': instance_nets},
259 {'instance_wim_nets': eg.instance_wim_nets()},
260 {'wim_port_mappings': port_mappings},
261 {'instance_actions': instance_action}]
262
263 action = WanLinkDelete(
264 eg.wim_actions('DELETE', action_id='ACTION-000')[0])
265 # --> ensure it is in the database for updates --> #
266 action_record = action.as_record()
267 action_record['extra'] = json.dumps(action_record['extra'])
268 self.populate([{'vim_wim_actions': action_record}])
269
270 return db_state, action
271
272 @disable_foreign_keys
273 def test_process(self):
274 # Given we want to delete 1 WAN link between 2 datacenters
275 db_state, action = self.prepare_delete()
276 self.populate(db_state)
277
278 instance_action = self.persist.get_by_uuid(
279 'instance_actions', action.instance_action_id)
280 number_done = instance_action['number_done']
281 number_failed = instance_action['number_failed']
282
283 connector_patch = patch.object(
284 self.connector, 'delete_connectivity_service')
285
286 # If the connector works fine
287 with connector_patch:
288 # When we try to process a DELETE action that refers to the same
289 # instance_scenario_id and sce_net_id
290 action.process(self.connector, self.persist, self.ovim)
291
292 # Then the action should be succeeded
293 db_action = self.persist.query_one('vim_wim_actions', WHERE={
294 'instance_action_id': action.instance_action_id,
295 'task_index': action.task_index})
296 self.assertEqual(db_action['status'], 'DONE')
297
298 instance_action = self.persist.get_by_uuid(
299 'instance_actions', action.instance_action_id)
300 self.assertEqual(instance_action['number_done'], number_done + 1)
301 self.assertEqual(instance_action['number_failed'], number_failed)
302
303 @disable_foreign_keys
304 def test_process__wan_link_error(self):
305 # Given we have a delete action that targets a wan link with an error
306 db_state, action = self.prepare_delete()
307 wan_link = [tables for tables in db_state
308 if tables.get('instance_wim_nets')][0]['instance_wim_nets']
309 from pprint import pprint
310 pprint(wan_link)
311 wan_link[0]['status'] = 'ERROR'
312 self.populate(db_state)
313
314 # When we try to process it
315 action.process(self.connector, self.persist, self.ovim)
316
317 # Then it should fail
318 assert action.is_failed
319
320 def create_action(self):
321 action = WanLinkCreate(
322 eg.wim_actions('CREATE', action_id='ACTION-000')[0])
323 # --> ensure it is in the database for updates --> #
324 action_record = action.as_record()
325 action_record['extra'] = json.dumps(action_record['extra'])
326 self.populate([{'vim_wim_actions': action_record}])
327
328 return action
329
330 @disable_foreign_keys
331 def test_create_and_delete(self):
332 # Given a CREATE action was well succeeded
333 db_state, delete_action = self.prepare_delete()
334 delete_action.save(self.persist, task_index=1)
335 self.populate(db_state)
336 create_action = self.create_action()
337
338 connector_patch = patch.multiple(
339 self.connector,
340 delete_connectivity_service=MagicMock(),
341 create_connectivity_service=(
342 lambda *_, **__: (uuid('random-id'), None)))
343
344 ovim_patch = patch.object(
345 self.ovim, 'get_ports', MagicMock(return_value=[{
346 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
347 'switch_port': 1,
348 }]))
349
350 with connector_patch, ovim_patch:
351 create_action.process(self.connector, self.persist, self.ovim)
352
353 # When we try to process a CREATE action that refers to the same
354 # instance_scenario_id and sce_net_id
355 with connector_patch:
356 delete_action.process(self.connector, self.persist, self.ovim)
357
358 # Then the DELETE action should be successful
359 db_action = self.persist.query_one('vim_wim_actions', WHERE={
360 'instance_action_id': delete_action.instance_action_id,
361 'task_index': delete_action.task_index})
362 self.assertEqual(db_action['status'], 'DONE')
363
364
365 if __name__ == '__main__':
366 unittest.main()