TestPath.test_status_case_3()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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