Test Failed
Pull Request — master (#621)
by Vinicius
04:02
created

TestPath.test_choose_vlans()   A

Complexity

Conditions 1

Size

Total Lines 16
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 15
nop 1
dl 0
loc 16
ccs 5
cts 5
cp 1
crap 1
rs 9.65
c 0
b 0
f 0
1
"""Module to test the Path class."""
2 1
import sys
3 1
from unittest.mock import call, patch, Mock, MagicMock
4 1
import pytest
5 1
from napps.kytos.mef_eline import settings
6 1
from httpx import TimeoutException, ConnectError
7 1
from kytos.core.common import EntityStatus
8 1
from kytos.core.exceptions import KytosNoTagAvailableError
9 1
from kytos.core.link import Link
10
from kytos.core.switch import Switch
11
12
# pylint: disable=wrong-import-position,ungrouped-imports,no-member
13 1
14
sys.path.insert(0, "/var/lib/kytos/napps/..")
15 1
# pylint: enable=wrong-import-position
16 1
from napps.kytos.mef_eline.exceptions import InvalidPath  # NOQA pycodestyle
17
from napps.kytos.mef_eline.models import (  # NOQA pycodestyle
18 1
    DynamicPathManager, Path)
19
from napps.kytos.mef_eline.tests.helpers import (  # NOQA pycodestyle
20
    MockResponse, get_link_mocked, id_to_interface_mock)
21
22 1
23
class TestPath():
24
    """Class to test path methods."""
25 1
26
    def test_is_affected_by_link_1(self):
27 1
        """Test method is affected by link."""
28 1
        path = Path()
29
        assert path.is_affected_by_link() is False
30 1
31
    def test_link_affected_by_interface_1(self):
32 1
        """Test method to get the link using an interface."""
33 1
        link1 = Mock()
34 1
        link1.endpoint_a = "a"
35 1
        link1.endpoint_b = "b"
36 1
        link2 = Mock()
37 1
        link2.endpoint_a = "c"
38 1
        link2.endpoint_b = "d"
39 1
        path = Path([link1, link2])
40
        assert path.link_affected_by_interface() is None
41 1
42
    def test_link_affected_by_interface_2(self):
43 1
        """Test method to get the link using an interface."""
44 1
        link1 = Mock()
45 1
        link1.endpoint_a = "a"
46 1
        link1.endpoint_b = "b"
47 1
        link2 = Mock()
48 1
        link2.endpoint_a = "c"
49 1
        link2.endpoint_b = "d"
50 1
        path = Path([link1, link2])
51
        assert path.link_affected_by_interface("a") == link1
52 1
53
    def test_status_case_1(self):
54 1
        """Test if empty link is DISABLED."""
55 1
        current_path = Path()
56
        assert current_path.status == EntityStatus.DISABLED
57 1
58
    def test_status_case_2(self):
59 1
        """Test if link status is DOWN."""
60 1
        link1 = get_link_mocked()
61 1
        link2 = get_link_mocked()
62 1
        link1.id = "def"
63 1
        link2.id = "abc"
64 1
        links = [link1, link2]
65 1
        current_path = Path(links)
66
        assert current_path.status == EntityStatus.DOWN
67 1
68
    def test_status_case_3(self):
69 1
        """Test if link status is DISABLED."""
70 1
        links = []
71 1
        current_path = Path(links)
72
        assert current_path.status == EntityStatus.DISABLED
73
74 1
    # This method will be used by the mock to replace httpx.get
75
    def _mocked_httpx_get_status_case_4(self):
76
        return MockResponse(
77
            {
78
                "links": {
79
                    "abc": {"active": True, "enabled": True},
80
                    "def": {"active": True, "enabled": True},
81
                }
82
            },
83
            200,
84
        )
85 1
86
    def test_status_case_4(self):
87 1
        """Test if link status is UP."""
88 1
        link1 = get_link_mocked(status=EntityStatus.UP)
89 1
        link2 = get_link_mocked(status=EntityStatus.UP)
90 1
        link1.id = "def"
91 1
        link2.id = "abc"
92 1
        links = [link1, link2]
93 1
        current_path = Path(links)
94
        assert current_path.status == EntityStatus.UP
95 1
96
    def test_status_case_5(self):
97 1
        """Test if link status is UP."""
98 1
        link1 = get_link_mocked(status=EntityStatus.UP)
99 1
        link2 = get_link_mocked(status=EntityStatus.DISABLED)
100 1
        link1.id = "def"
101 1
        link2.id = "abc"
102 1
        links = [link1, link2]
103 1
        current_path = Path(links)
104
        assert current_path.status == EntityStatus.DISABLED
105 1
106
    def test_status_case_6(self):
107 1
        """Test if link status is UP."""
108 1
        link1 = get_link_mocked(status=EntityStatus.DISABLED)
109 1
        link2 = get_link_mocked(status=EntityStatus.UP)
110 1
        link1.id = "def"
111 1
        link2.id = "abc"
112 1
        links = [link1, link2]
113 1
        current_path = Path(links)
114
        assert current_path.status == EntityStatus.DISABLED
115 1
116
    def test_status_case_7(self):
117 1
        """Test if link status is DOWN if has one link down."""
118 1
        link1 = get_link_mocked(status=EntityStatus.UP)
119 1
        link2 = get_link_mocked(status=EntityStatus.DOWN)
120 1
        link1.id = "def"
121 1
        link2.id = "abc"
122 1
        links = [link1, link2]
123 1
        current_path = Path(links)
124
        assert current_path.status == EntityStatus.DOWN
125 1
126
    def test_status_case_8(self):
127 1
        """Test if status is DOWN if has one endpoint link is mismatched."""
128 1
        link1 = get_link_mocked(status=EntityStatus.UP)
129 1
        link1.endpoint_a.link = None
130 1
        link2 = get_link_mocked(status=EntityStatus.UP)
131 1
        link1.id = "def"
132 1
        link2.id = "abc"
133 1
        links = [link1, link2]
134 1
        current_path = Path(links)
135
        assert current_path.status == EntityStatus.DOWN
136 1
137
    def test_choose_vlans(self) -> None:
138 1
        """Test choose vlans."""
139
        controller = MagicMock()
140
        link1 = get_link_mocked(status=EntityStatus.UP)
141
        link2 = get_link_mocked(status=EntityStatus.UP)
142
        link1.id = "def"
143
        link2.id = "abc"
144
        links = [link1, link2]
145
        path = Path(links)
146
        path.make_vlans_available = MagicMock()
147 1
        path.choose_vlans(controller)
148 1
        assert link1.get_next_available_tag.call_count == 1
149 1
        assert link1.add_metadata.call_count == 1
150
        assert link2.get_next_available_tag.call_count == 1
151 1
        assert link2.add_metadata.call_count == 1
152
        assert not path.make_vlans_available.call_count
153 1
154
    def test_choose_vlans_tags_not_available(self) -> None:
155
        """Test choose vlans rollback if tags not available."""
156
        controller = MagicMock()
157
        link1 = get_link_mocked(status=EntityStatus.UP)
158
        link2 = get_link_mocked(status=EntityStatus.UP)
159
        link1.id = "def"
160
        link2.id = "abc"
161 1
        links = [link1, link2]
162
        path = Path(links)
163
        path.make_vlans_available = MagicMock()
164
        exc = KytosNoTagAvailableError(link2)
165
        link2.get_next_available_tag.side_effect = exc
166
        with pytest.raises(KytosNoTagAvailableError):
167
            path.choose_vlans(controller)
168
        assert link1.get_next_available_tag.call_count == 1
169
        assert link1.add_metadata.call_count == 1
170 1
        assert link2.get_next_available_tag.call_count == 1
171 1
        assert not link2.add_metadata.call_count
172 1
        assert path.make_vlans_available.call_count == 1
173
174 1
    def test_compare_same_paths(self):
175
        """Test compare paths with same links."""
176 1
        links = [
177
            get_link_mocked(
178
                endpoint_a_port=9, endpoint_b_port=10, metadata={"s_vlan": 5}
179
            ),
180
            get_link_mocked(
181 1
                endpoint_a_port=11, endpoint_b_port=12, metadata={"s_vlan": 6}
182 1
            ),
183 1
        ]
184
185 1
        path_1 = Path(links)
186
        path_2 = Path(links)
187 1
        assert path_1 == path_2
188 1
189
    def test_compare_different_paths(self):
190 1
        """Test compare paths with different links."""
191
        links_1 = [
192 1
            get_link_mocked(
193 1
                endpoint_a_port=9, endpoint_b_port=10, metadata={"s_vlan": 5}
194 1
            ),
195 1
            get_link_mocked(
196 1
                endpoint_a_port=11, endpoint_b_port=12, metadata={"s_vlan": 6}
197 1
            ),
198
        ]
199 1
        links_2 = [
200
            get_link_mocked(
201
                endpoint_a_port=12, endpoint_b_port=11, metadata={"s_vlan": 5}
202
            ),
203
            get_link_mocked(
204
                endpoint_a_port=14, endpoint_b_port=16, metadata={"s_vlan": 11}
205
            ),
206 1
        ]
207 1
208
        path_1 = Path(links_1)
209 1
        path_2 = Path(links_2)
210
        assert path_1 != path_2
211 1
212 1
    def test_as_dict(self):
213 1
        """Test path as dict."""
214 1
        links = [
215 1
            get_link_mocked(link_dict={"id": 3}),
216
            get_link_mocked(link_dict={"id": 2}),
217
        ]
218
219
        current_path = Path(links)
220 1
        expected_dict = [{"id": 3}, {"id": 2}]
221 1
        assert expected_dict == current_path.as_dict()
222 1
223
    def test_empty_is_valid(self) -> None:
224 1
        """Test empty path is valid."""
225
        path = Path([])
226 1
        assert path.is_valid(MagicMock(), MagicMock(), False)
227 1
228 1 View Code Duplication
    def test_is_valid(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
229 1
        """Test is_valid method."""
230 1
        switch1 = Switch("00:00:00:00:00:00:00:01")
231 1
        switch2 = Switch("00:00:00:00:00:00:00:02")
232
        switch3 = Switch("00:00:00:00:00:00:00:03")
233
        switch4 = Switch("00:00:00:00:00:00:00:04")
234
        switch5 = Switch("00:00:00:00:00:00:00:05")
235
        switch6 = Switch("00:00:00:00:00:00:00:06")
236
        # Links connected
237
        links = [
238 1
            get_link_mocked(switch_a=switch5, switch_b=switch6),
239 1
            get_link_mocked(switch_a=switch4, switch_b=switch5),
240 1
            get_link_mocked(switch_a=switch3, switch_b=switch4),
241
            get_link_mocked(switch_a=switch2, switch_b=switch3),
242 1
            get_link_mocked(switch_a=switch1, switch_b=switch2),
243
        ]
244
        path = Path(links)
245 1
        assert path.is_valid(switch6, switch1) is True
246 1
247 1
    def test_is_valid_diconnected(self):
248 1
        """Test is_valid with disconnected path"""
249 1
        switch1 = Switch("00:00:00:00:00:00:00:01")
250 1
        switch2 = Switch("00:00:00:00:00:00:00:02")
251 1
        switch3 = Switch("00:00:00:00:00:00:00:03")
252
        switch4 = Switch("00:00:00:00:00:00:00:04")
253
        links = [
254
            get_link_mocked(switch_a=switch1, switch_b=switch2),
255
            get_link_mocked(switch_a=switch3, switch_b=switch4),
256
            get_link_mocked(switch_a=switch2, switch_b=switch4),
257
        ]
258 1
        path = Path(links)
259 1
        with pytest.raises(InvalidPath):
260 1
            path.is_valid(switch1, switch4)
261
262
    def test_is_valid_with_loop(self):
263 1
        """Test is_valid with a loop"""
264
        switch1 = Switch("00:00:00:00:00:00:00:01")
265
        switch2 = Switch("00:00:00:00:00:00:00:02")
266 1
        switch3 = Switch("00:00:00:00:00:00:00:03")
267
        switch4 = Switch("00:00:00:00:00:00:00:04")
268 1
        switch5 = Switch("00:00:00:00:00:00:00:05")
269
        links = [
270
            get_link_mocked(switch_a=switch1, switch_b=switch2),
271
            get_link_mocked(switch_a=switch2, switch_b=switch3),
272
            get_link_mocked(switch_a=switch3, switch_b=switch4),
273
            get_link_mocked(switch_a=switch2, switch_b=switch4),
274
            get_link_mocked(switch_a=switch2, switch_b=switch5),
275
        ]
276
        path = Path(links)
277
        with pytest.raises(InvalidPath):
278 1
            path.is_valid(switch1, switch5)
279
280 View Code Duplication
    def test_is_valid_invalid(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
281
        """Test is_valid when path is invalid
282
        UNI_Z is not connected"""
283
        switch1 = Switch("00:00:00:00:00:00:00:01")
284
        switch2 = Switch("00:00:00:00:00:00:00:02")
285
        switch3 = Switch("00:00:00:00:00:00:00:03")
286
        switch4 = Switch("00:00:00:00:00:00:00:04")
287 1
        switch5 = Switch("00:00:00:00:00:00:00:05")
288
        switch6 = Switch("00:00:00:00:00:00:00:06")
289 1
        links = [
290
            get_link_mocked(switch_a=switch5, switch_b=switch6),
291 1
            get_link_mocked(switch_a=switch4, switch_b=switch5),
292
            get_link_mocked(switch_a=switch3, switch_b=switch4),
293
            get_link_mocked(switch_a=switch2, switch_b=switch3),
294
            get_link_mocked(switch_a=switch1, switch_b=switch2),
295
        ]
296
        path = Path(links)
297
        with pytest.raises(InvalidPath):
298
            path.is_valid(switch3, switch6)
299
300 1
301
class TestDynamicPathManager():
302 1
    """Tests for the DynamicPathManager class"""
303 1
304
    def test_clear_path(self):
305 1
        """Test _clear_path method"""
306 1
        path = [
307 1
            '00:00:00:00:00:00:00:01:1',
308
            '00:00:00:00:00:00:00:02:3',
309 1
            '00:00:00:00:00:00:00:02',
310
            '00:00:00:00:00:00:00:02:4',
311
            '00:00:00:00:00:00:00:03:2',
312
            '00:00:00:00:00:00:00:03',
313
            '00:00:00:00:00:00:00:03:1',
314
            '00:00:00:00:00:00:00:04:1'
315
        ]
316
        expected_path = [
317
            '00:00:00:00:00:00:00:01:1',
318
            '00:00:00:00:00:00:00:02:3',
319
            '00:00:00:00:00:00:00:02:4',
320
            '00:00:00:00:00:00:00:03:2',
321
            '00:00:00:00:00:00:00:03:1',
322
            '00:00:00:00:00:00:00:04:1'
323
        ]
324
        # pylint: disable=protected-access
325
        assert DynamicPathManager._clear_path(path) == expected_path
326
327
    def test_create_path_invalid(self):
328
        """Test create_path method"""
329
        path = [
330
            '00:00:00:00:00:00:00:01:1',
331
            '00:00:00:00:00:00:00:02:3',
332
            '00:00:00:00:00:00:00:02',
333
            '00:00:00:00:00:00:00:02:4',
334
            '00:00:00:00:00:00:00:03:2',
335
            '00:00:00:00:00:00:00:03',
336
            '00:00:00:00:00:00:00:03:1',
337
        ]
338
        assert DynamicPathManager.create_path(path) is None
339
340
    @patch("httpx.post")
341
    def test_get_best_paths(self, mock_httpx_post):
342 1
        """Test get_best_paths method."""
343
        controller = MagicMock()
344
        controller.get_interface_by_id.side_effect = id_to_interface_mock
345
        DynamicPathManager.set_controller(controller)
346
347
        paths1 = {
348
            "paths": [
349 1
                {
350
                    "cost": 5,
351
                    "hops": [
352
                        "00:00:00:00:00:00:00:01:1",
353
                        "00:00:00:00:00:00:00:01",
354
                        "00:00:00:00:00:00:00:01:2",
355
                        "00:00:00:00:00:00:00:02:2",
356
                        "00:00:00:00:00:00:00:02",
357
                        "00:00:00:00:00:00:00:02:1"
358
                        ]
359
                },
360
                {
361
                    "cost": 11,
362
                    "hops": [
363
                        "00:00:00:00:00:00:00:01:1",
364 1
                        "00:00:00:00:00:00:00:01",
365 1
                        "00:00:00:00:00:00:00:01:2",
366 1
                        "00:00:00:00:00:00:00:02:2",
367 1
                        "00:00:00:00:00:00:00:02",
368 1
                        "00:00:00:00:00:00:00:02:3",
369
                        "00:00:00:00:00:00:00:03:3",
370
                        "00:00:00:00:00:00:00:03",
371
                        "00:00:00:00:00:00:00:03:4",
372
                        "00:00:00:00:00:00:00:04:4",
373
                        "00:00:00:00:00:00:00:04",
374
                        "00:00:00:00:00:00:00:04:1"
375 1
                        ]
376 1
                },
377 1
            ]
378 1
        }
379 1
380
        expected_paths_0 = [
381 1
            Link(
382
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
383
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
384
            ),
385 1
        ]
386
387
        expected_paths_1 = [
388
            Link(
389 1
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
390
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
391
            ),
392
            Link(
393
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
394
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
395
            ),
396
            Link(
397
                id_to_interface_mock("00:00:00:00:00:00:00:03:4"),
398
                id_to_interface_mock("00:00:00:00:00:00:00:04:4")
399
            ),
400
        ]
401 1
402
        mock_response = MagicMock()
403 1
        mock_response.status_code = 200
404 1
        mock_response.json.return_value = paths1
405 1
        mock_httpx_post.return_value = mock_response
406 1
        kwargs = {
407
            "spf_attribute": settings.SPF_ATTRIBUTE,
408 1
            "spf_max_path_cost": 8,
409 1
            "mandatory_metrics": {
410 1
                "ownership": "red"
411 1
            }
412 1
        }
413 1
        circuit = MagicMock()
414 1
        circuit.uni_a.interface.id = "1"
415
        circuit.uni_z.interface.id = "2"
416 1
        max_paths = 2
417 1
        res_paths = list(DynamicPathManager.get_best_paths(circuit,
418 1
                         max_paths=max_paths, **kwargs))
419 1
        assert (
420
            [link.id for link in res_paths[0]] ==
421 1
            [link.id for link in expected_paths_0]
422 1
        )
423 1
        assert (
424 1
            [link.id for link in res_paths[1]] ==
425
            [link.id for link in expected_paths_1]
426
        )
427 1
        expected_call = call(
428
            "http://localhost:8181/api/kytos/pathfinder/v3/",
429
            json={
430
                **{
431
                    "source": circuit.uni_a.interface.id,
432 1
                    "destination": circuit.uni_z.interface.id,
433 1
                    "spf_max_paths": max_paths,
434
                },
435
                **kwargs
436 1
            },
437 1
            timeout=10,
438 1
        )
439
        mock_httpx_post.assert_has_calls([expected_call])
440 1
441 1
    @patch('time.sleep')
442
    @patch("napps.kytos.mef_eline.models.path.log")
443
    @patch("httpx.post")
444
    def test_get_best_paths_error(self, mock_httpx_post, mock_log, _):
445
        """Test get_best_paths method."""
446
        controller = MagicMock()
447
        DynamicPathManager.set_controller(controller)
448 1
        circuit = MagicMock()
449 1
        circuit.id = "id"
450 1
        circuit.uni_a.interface.id = "1"
451 1
        circuit.uni_z.interface.id = "2"
452
        circuit.name = "some_name"
453
454 1
        mock_response = MagicMock()
455
        mock_response.status_code = 400
456
        mock_response.json.return_value = {}
457
        mock_httpx_post.return_value = mock_response
458
459
        res_paths = list(DynamicPathManager.get_best_paths(circuit))
460
        assert not res_paths
461
        assert isinstance(res_paths, list)
462
        assert mock_log.error.call_count == 1
463
464
    # pylint: disable=too-many-statements, too-many-locals
465
    @patch.object(
466
        DynamicPathManager,
467
        "get_shared_components",
468
        side_effect=DynamicPathManager.get_shared_components
469
    )
470
    @patch("httpx.post")
471
    def test_get_disjoint_paths(self, mock_httpx_post, mock_shared):
472
        """Test get_disjoint_paths method."""
473
474
        controller = MagicMock()
475
        controller.get_interface_by_id.side_effect = id_to_interface_mock
476
        DynamicPathManager.set_controller(controller)
477
478
        evc = MagicMock()
479
        evc.secondary_constraints = {
480
            "spf_attribute": "hop",
481
            "spf_max_path_cost": 20,
482
            "mandatory_metrics": {
483
                "ownership": "red"
484
            }
485
        }
486
        evc.uni_a.interface.id = "1"
487
        evc.uni_z.interface.id = "2"
488
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
489
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
490
491
        # Topo0
492
        paths1 = {
493
            "paths": [
494
                {
495
                    "cost": 11,
496
                    "hops": [
497
                        "00:00:00:00:00:00:00:01:1",
498
                        "00:00:00:00:00:00:00:01",
499
                        "00:00:00:00:00:00:00:01:2",
500
                        "00:00:00:00:00:00:00:02:2",
501
                        "00:00:00:00:00:00:00:02",
502
                        "00:00:00:00:00:00:00:02:3",
503
                        "00:00:00:00:00:00:00:04:2",
504
                        "00:00:00:00:00:00:00:04",
505
                        "00:00:00:00:00:00:00:04:3",
506
                        "00:00:00:00:00:00:00:05:2",
507
                        "00:00:00:00:00:00:00:05",
508
                        "00:00:00:00:00:00:00:05:1"
509
                        ]
510
                },
511
                {
512
                    "cost": 11,
513
                    "hops": [
514
                        "00:00:00:00:00:00:00:01:1",
515
                        "00:00:00:00:00:00:00:01",
516
                        "00:00:00:00:00:00:00:01:3",
517
                        "00:00:00:00:00:00:00:03:2",
518
                        "00:00:00:00:00:00:00:03",
519
                        "00:00:00:00:00:00:00:03:3",
520
                        "00:00:00:00:00:00:00:04:4",
521
                        "00:00:00:00:00:00:00:04",
522
                        "00:00:00:00:00:00:00:04:3",
523
                        "00:00:00:00:00:00:00:05:2",
524
                        "00:00:00:00:00:00:00:05",
525
                        "00:00:00:00:00:00:00:05:1"
526
                        ]
527
                },
528
                {
529
                    "cost": 14,
530
                    "hops": [
531
                        "00:00:00:00:00:00:00:01:1",
532
                        "00:00:00:00:00:00:00:01",
533
                        "00:00:00:00:00:00:00:01:2",
534
                        "00:00:00:00:00:00:00:02:2",
535
                        "00:00:00:00:00:00:00:02",
536
                        "00:00:00:00:00:00:00:02:3",
537
                        "00:00:00:00:00:00:00:04:2",
538
                        "00:00:00:00:00:00:00:04",
539
                        "00:00:00:00:00:00:00:04:5",
540
                        "00:00:00:00:00:00:00:06:2",
541
                        "00:00:00:00:00:00:00:06",
542
                        "00:00:00:00:00:00:00:06:3",
543
                        "00:00:00:00:00:00:00:05:3",
544
                        "00:00:00:00:00:00:00:05",
545
                        "00:00:00:00:00:00:00:05:1"
546
                    ]
547
                },
548
                {
549
                    "cost": 14,
550
                    "hops": [
551
                        "00:00:00:00:00:00:00:01:1",
552
                        "00:00:00:00:00:00:00:01",
553
                        "00:00:00:00:00:00:00:01:3",
554
                        "00:00:00:00:00:00:00:03:2",
555
                        "00:00:00:00:00:00:00:03",
556 1
                        "00:00:00:00:00:00:00:03:3",
557 1
                        "00:00:00:00:00:00:00:04:4",
558 1
                        "00:00:00:00:00:00:00:04",
559
                        "00:00:00:00:00:00:00:04:5",
560
                        "00:00:00:00:00:00:00:06:2",
561 1
                        "00:00:00:00:00:00:00:06",
562 1
                        "00:00:00:00:00:00:00:06:3",
563 1
                        "00:00:00:00:00:00:00:05:3",
564
                        "00:00:00:00:00:00:00:05",
565 1
                        "00:00:00:00:00:00:00:05:1"
566
                    ]
567
                },
568
                {
569
                    "cost": 17,
570
                    "hops": [
571
                        "00:00:00:00:00:00:00:01:1",
572
                        "00:00:00:00:00:00:00:01",
573
                        "00:00:00:00:00:00:00:01:3",
574
                        "00:00:00:00:00:00:00:03:2",
575
                        "00:00:00:00:00:00:00:03",
576
                        "00:00:00:00:00:00:00:03:3",
577
                        "00:00:00:00:00:00:00:04:4",
578
                        "00:00:00:00:00:00:00:04",
579 1
                        "00:00:00:00:00:00:00:04:5",
580
                        "00:00:00:00:00:00:00:06:2",
581
                        "00:00:00:00:00:00:00:06",
582
                        "00:00:00:00:00:00:00:06:4",
583
                        "00:00:00:00:00:00:00:07:2",
584 1
                        "00:00:00:00:00:00:00:07",
585
                        "00:00:00:00:00:00:00:07:3",
586
                        "00:00:00:00:00:00:00:05:4",
587
                        "00:00:00:00:00:00:00:05",
588
                        "00:00:00:00:00:00:00:05:1"
589
                    ]
590
                },
591 1
            ]
592 1
        }
593 1
594 1
        mock_response = MagicMock()
595 1
        mock_response.status_code = 200
596 1
        mock_response.json.return_value = paths1
597 1
598 1
        # when we dont have the current_path
599
        mock_httpx_post.return_value = mock_response
600 1
        disjoint_paths = list(DynamicPathManager.get_disjoint_paths(evc, []))
601
        assert not disjoint_paths
602
603
        current_path = [
604
            Link(
605
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
606
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
607
            ),
608
            Link(
609
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
610
                id_to_interface_mock("00:00:00:00:00:00:00:04:2")
611
            ),
612
            Link(
613
                id_to_interface_mock("00:00:00:00:00:00:00:04:3"),
614
                id_to_interface_mock("00:00:00:00:00:00:00:05:2")
615
            ),
616
        ]
617
        path_links = [
618
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:2"),
619
            ("00:00:00:00:00:00:00:02:3", "00:00:00:00:00:00:00:04:2"),
620 1
            ("00:00:00:00:00:00:00:04:3", "00:00:00:00:00:00:00:05:2")
621 1
        ]
622 1
        path_switches = {
623 1
            "00:00:00:00:00:00:00:04",
624 1
            "00:00:00:00:00:00:00:02"
625 1
        }
626 1
627
        # only one path available from pathfinder (precesilly the
628
        # current_path), so the maximum disjoint path will be empty
629
        mock_response.json.return_value = {"paths": paths1["paths"][0:1]}
630 1
        mock_httpx_post.return_value = mock_response
631
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
632 1
        args = mock_shared.call_args[0]
633 1
        assert args[0] == paths1["paths"][0]
634 1
        assert args[1] == path_links
635 1
        assert args[2] == path_switches
636 1
        assert len(paths) == 0
637
638
        expected_disjoint_path = [
639
            Link(
640
                id_to_interface_mock("00:00:00:00:00:00:00:01:3"),
641 1
                id_to_interface_mock("00:00:00:00:00:00:00:03:2")
642 1
            ),
643
            Link(
644
                id_to_interface_mock("00:00:00:00:00:00:00:03:3"),
645
                id_to_interface_mock("00:00:00:00:00:00:00:04:4")
646
            ),
647
            Link(
648
                id_to_interface_mock("00:00:00:00:00:00:00:04:5"),
649
                id_to_interface_mock("00:00:00:00:00:00:00:06:2")
650
            ),
651
            Link(
652
                id_to_interface_mock("00:00:00:00:00:00:00:06:3"),
653
                id_to_interface_mock("00:00:00:00:00:00:00:05:3")
654 1
            ),
655
        ]
656 1
657
        # there are one alternative path
658
        mock_response.json.return_value = paths1
659 1
        mock_httpx_post.return_value = mock_response
660 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
661 1
        args = mock_shared.call_args[0]
662
        assert args[0] == paths1["paths"][-1]
663
        assert args[1] == path_links
664
        assert args[2] == {
665
            "00:00:00:00:00:00:00:04",
666
            "00:00:00:00:00:00:00:02"
667
        }
668
        assert len(paths) == 4
669
        # for more information on the paths please refer to EP029
670
        assert len(paths[0]) == 4  # path S-Z-W-I-D
671
        assert len(paths[1]) == 5  # path S-Z-W-I-J-D
672
        assert len(paths[2]) == 3  # path S-Z-W-D
673
        assert len(paths[3]) == 4  # path S-X-W-I-D
674
        assert (
675
            [link.id for link in paths[0]] ==
676
            [link.id for link in expected_disjoint_path]
677
        )
678
679
        max_paths = 10
680
        expected_call = call(
681
            "http://localhost:8181/api/kytos/pathfinder/v3/",
682
            json={
683
                **{
684
                    "source": evc.uni_a.interface.id,
685
                    "destination": evc.uni_z.interface.id,
686
                    "spf_max_paths": max_paths,
687
                },
688
                **evc.secondary_constraints
689
            },
690
            timeout=10,
691
        )
692
        assert mock_httpx_post.call_count >= 1
693
        # If secondary_constraints are set they are expected to be parametrized
694
        mock_httpx_post.assert_has_calls([expected_call])
695
696
        # EP029 Topo2
697
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
698
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:07"
699
        paths2 = {
700
            "paths": [
701
                {
702
                    "cost": 14,
703
                    "hops": [
704
                        "00:00:00:00:00:00:00:01:1",
705
                        "00:00:00:00:00:00:00:01",
706
                        "00:00:00:00:00:00:00:01:2",
707
                        "00:00:00:00:00:00:00:02:1",
708
                        "00:00:00:00:00:00:00:02",
709 1
                        "00:00:00:00:00:00:00:02:2",
710
                        "00:00:00:00:00:00:00:03:1",
711
                        "00:00:00:00:00:00:00:03",
712
                        "00:00:00:00:00:00:00:03:2",
713
                        "00:00:00:00:00:00:00:04:1",
714
                        "00:00:00:00:00:00:00:04",
715
                        "00:00:00:00:00:00:00:04:2",
716
                        "00:00:00:00:00:00:00:07:2",
717
                        "00:00:00:00:00:00:00:07",
718
                        "00:00:00:00:00:00:00:07:1"
719
                    ]
720
                },
721
                {
722
                    "cost": 17,
723
                    "hops": [
724
                        "00:00:00:00:00:00:00:01:1",
725
                        "00:00:00:00:00:00:00:01",
726
                        "00:00:00:00:00:00:00:01:2",
727 1
                        "00:00:00:00:00:00:00:02:1",
728
                        "00:00:00:00:00:00:00:02",
729
                        "00:00:00:00:00:00:00:02:3",
730
                        "00:00:00:00:00:00:00:05:1",
731
                        "00:00:00:00:00:00:00:05",
732
                        "00:00:00:00:00:00:00:05:2",
733 1
                        "00:00:00:00:00:00:00:06:1",
734
                        "00:00:00:00:00:00:00:06",
735
                        "00:00:00:00:00:00:00:06:2",
736
                        "00:00:00:00:00:00:00:04:3",
737
                        "00:00:00:00:00:00:00:04",
738
                        "00:00:00:00:00:00:00:04:2",
739 1
                        "00:00:00:00:00:00:00:07:2",
740
                        "00:00:00:00:00:00:00:07",
741
                        "00:00:00:00:00:00:00:07:1"
742
                    ]
743
                }
744
            ]
745
        }
746
747
        current_path = [
748
            Link(
749
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
750
                id_to_interface_mock("00:00:00:00:00:00:00:02:1")
751
            ),
752
            Link(
753
                id_to_interface_mock("00:00:00:00:00:00:00:02:2"),
754
                id_to_interface_mock("00:00:00:00:00:00:00:03:1")
755
            ),
756
            Link(
757
                id_to_interface_mock("00:00:00:00:00:00:00:03:2"),
758
                id_to_interface_mock("00:00:00:00:00:00:00:04:1")
759
            ),
760
            Link(
761
                id_to_interface_mock("00:00:00:00:00:00:00:04:2"),
762 1
                id_to_interface_mock("00:00:00:00:00:00:00:07:2")
763 1
            ),
764 1
        ]
765 1
        path_interfaces = [
766 1
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:1"),
767 1
            ("00:00:00:00:00:00:00:02:2", "00:00:00:00:00:00:00:03:1"),
768 1
            ("00:00:00:00:00:00:00:03:2", "00:00:00:00:00:00:00:04:1"),
769 1
            ("00:00:00:00:00:00:00:04:2", "00:00:00:00:00:00:00:07:2")
770 1
        ]
771
        path_switches = {
772
            "00:00:00:00:00:00:00:02",
773
            "00:00:00:00:00:00:00:03",
774
            "00:00:00:00:00:00:00:04"
775 1
        }
776 1
777
        expected_disjoint_path = [
778 1
            Link(
779 1
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
780 1
                id_to_interface_mock("00:00:00:00:00:00:00:02:1")
781
            ),
782 1
            Link(
783 1
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
784
                id_to_interface_mock("00:00:00:00:00:00:00:05:1")
785
            ),
786
            Link(
787
                id_to_interface_mock("00:00:00:00:00:00:00:05:2"),
788
                id_to_interface_mock("00:00:00:00:00:00:00:06:1")
789
            ),
790 1
            Link(
791 1
                id_to_interface_mock("00:00:00:00:00:00:00:06:2"),
792 1
                id_to_interface_mock("00:00:00:00:00:00:00:04:3")
793 1
            ),
794
            Link(
795 1
                id_to_interface_mock("00:00:00:00:00:00:00:04:2"),
796
                id_to_interface_mock("00:00:00:00:00:00:00:07:2")
797
            ),
798
        ]
799
800
        mock_response.json.return_value = {"paths": paths2["paths"]}
801
        mock_httpx_post.return_value = mock_response
802
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
803
        args = mock_shared.call_args[0]
804
        assert args[0] == paths2["paths"][-1]
805
        assert args[1] == path_interfaces
806
        assert args[2] == path_switches
807
        assert len(paths) == 1
808
        assert (
809
            [link.id for link in paths[0]] ==
810
            [link.id for link in expected_disjoint_path]
811
        )
812
813
    @patch("httpx.post")
814
    def test_get_disjoint_paths_simple_evc(self, mock_httpx_post):
815
        """Test get_disjoint_paths method for simple EVCs."""
816
        controller = MagicMock()
817
        controller.get_interface_by_id.side_effect = id_to_interface_mock
818
        DynamicPathManager.set_controller(controller)
819
820
        evc = MagicMock()
821
        evc.secondary_constraints = {
822
            "spf_attribute": "hop",
823
            "spf_max_path_cost": 20,
824 1
            "mandatory_metrics": {
825
                "ownership": "red"
826
            }
827
        }
828
        evc.uni_a.interface.id = "1"
829
        evc.uni_z.interface.id = "2"
830 1
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
831
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
832
833
        mock_paths = {
834
            "paths": [
835
                {
836
                    "cost": 5,
837
                    "hops": [
838
                        "00:00:00:00:00:00:00:01:1",
839
                        "00:00:00:00:00:00:00:01",
840
                        "00:00:00:00:00:00:00:01:3",
841 1
                        "00:00:00:00:00:00:00:02:2",
842 1
                        "00:00:00:00:00:00:00:02",
843 1
                        "00:00:00:00:00:00:00:02:1"
844 1
                        ]
845 1
                },
846 1
                {
847 1
                    "cost": 8,
848
                    "hops": [
849
                        "00:00:00:00:00:00:00:01:1",
850
                        "00:00:00:00:00:00:00:01",
851
                        "00:00:00:00:00:00:00:01:4",
852 1
                        "00:00:00:00:00:00:00:03:3",
853 1
                        "00:00:00:00:00:00:00:03",
854 1
                        "00:00:00:00:00:00:00:03:2",
855 1
                        "00:00:00:00:00:00:00:02:3",
856
                        "00:00:00:00:00:00:00:02",
857
                        "00:00:00:00:00:00:00:02:1"
858 1
                        ]
859 1
                },
860
            ]
861
        }
862
        current_path = [
863
            Link(
864
                id_to_interface_mock("00:00:00:00:00:00:00:01:3"),
865 1
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
866 1
            ),
867
        ]
868
        expected_disjoint_path = [
869
            Link(
870
                id_to_interface_mock("00:00:00:00:00:00:00:01:4"),
871
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
872
            ),
873 1
            Link(
874 1
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
875 1
                id_to_interface_mock("00:00:00:00:00:00:00:03:2")
876 1
            ),
877 1
        ]
878 1
879 1
        mock_response = MagicMock()
880
        mock_response.status_code = 200
881 1
        mock_response.json.return_value = mock_paths
882 1
        mock_httpx_post.return_value = mock_response
883 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
884 1
        assert len(paths) == 1
885
        assert (
886 1
            [link.id for link in paths[0]] ==
887
            [link.id for link in expected_disjoint_path]
888 1
        )
889
890
    @patch("httpx.post")
891
    @patch("napps.kytos.mef_eline.models.path.log")
892
    @patch("time.sleep")
893
    def test_get_disjoint_paths_error(self, _, mock_log, mock_post):
894
        """Test get_disjoint_paths with reported errors. These are caught under
895
        httpx.RequestError."""
896
        mock_post.side_effect = TimeoutException('mock')
897
        unwanted_path = [
898
            Link(
899
                id_to_interface_mock("00:00:00:00:00:00:00:01:4"),
900
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
901
            ),
902 1
        ]
903
        evc = MagicMock()
904
        evc.secondary_constraints = {
905
            "spf_attribute": "hop",
906 1
            "spf_max_path_cost": 20,
907 1
            "mandatory_metrics": {
908
                "ownership": "red"
909
            }
910 1
        }
911 1
        evc.uni_a.interface.id = "1"
912
        evc.uni_z.interface.id = "2"
913
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
914
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
915
        path = DynamicPathManager.get_disjoint_paths(evc, unwanted_path)
916
        assert len(list(path)) == 0
917
        assert mock_log.error.call_count == 1
918
919
        mock_post.side_effect = ConnectError('mock')
920
        path = DynamicPathManager.get_disjoint_paths(evc, unwanted_path)
921
        assert len(list(path)) == 0
922
        assert mock_log.error.call_count == 2
923
924
    def test_get_shared_components(self):
925
        """Test get_shared_components"""
926
        mock_path = {"hops": [
927
            '00:00:00:00:00:00:00:01:1',
928
            '00:00:00:00:00:00:00:01',
929
            '00:00:00:00:00:00:00:01:4',
930
            '00:00:00:00:00:00:00:05:2',
931
            '00:00:00:00:00:00:00:05',
932
            '00:00:00:00:00:00:00:05:3',
933
            '00:00:00:00:00:00:00:02:4',
934
            '00:00:00:00:00:00:00:02',
935
            '00:00:00:00:00:00:00:02:3',
936
            '00:00:00:00:00:00:00:03:2',
937
            '00:00:00:00:00:00:00:03',
938
            '00:00:00:00:00:00:00:03:1'
939
        ]}
940
        mock_links = [
941
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:2"),
942
            ("00:00:00:00:00:00:00:02:3", "00:00:00:00:00:00:00:03:2")
943
        ]
944
        mock_switches = {"00:00:00:00:00:00:00:02"}
945
        actual_lk, actual_sw = DynamicPathManager.get_shared_components(
946
            mock_path, mock_links, mock_switches
947
        )
948
        assert actual_lk == 1
949
        assert actual_sw == 1
950