Passed
Pull Request — master (#651)
by
unknown
04:11
created

TestDynamicPathManager.test_get_disjoint_paths()   B

Complexity

Conditions 1

Size

Total Lines 346
Code Lines 266

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 64
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 266
nop 3
dl 0
loc 346
ccs 64
cts 64
cp 1
crap 1
rs 7
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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