Passed
Push — master ( f52c58...a51a5b )
by Vinicius
03:13 queued 16s
created

build.tests.unit.models.test_path   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 951
Duplicated Lines 3.89 %

Test Coverage

Coverage 99.71%

Importance

Changes 0
Metric Value
eloc 717
dl 37
loc 951
ccs 344
cts 345
cp 0.9971
rs 9.5629
c 0
b 0
f 0
wmc 34

30 Methods

Rating   Name   Duplication   Size   Complexity  
A TestPath.test_link_affected_by_interface_2() 0 10 1
A TestPath.test_link_affected_by_interface_1() 0 10 1
A TestPath.test_is_affected_by_link_1() 0 4 1
A TestPath.test_status_case_1() 0 4 1
A TestPath.test_status_case_3() 0 5 1
A TestPath.test_status_case_2() 0 9 1
A TestPath.test_is_valid_with_loop() 0 17 2
A TestPath.test_is_valid() 18 18 1
A TestPath.test_empty_is_valid() 0 4 1
A TestDynamicPathManager.test_get_best_paths_error() 0 22 1
A TestPath._mocked_httpx_get_status_case_4() 0 9 1
A TestPath.test_compare_different_paths() 0 22 1
A TestPath.test_choose_vlans() 0 16 1
A TestPath.test_choose_vlans_tags_not_available() 0 19 2
B TestDynamicPathManager.test_get_best_paths() 0 100 1
A TestDynamicPathManager.test_get_disjoint_paths_error() 0 33 1
A TestPath.test_status_case_4() 0 9 1
A TestPath.test_status_case_7() 0 9 1
B TestDynamicPathManager.test_get_disjoint_paths_simple_evc() 0 75 1
A TestDynamicPathManager.test_clear_path() 0 22 1
B TestDynamicPathManager.test_get_disjoint_paths() 0 346 1
A TestPath.test_as_dict() 0 10 1
A TestPath.test_is_valid_invalid() 19 19 2
A TestPath.test_is_valid_diconnected() 0 14 2
A TestDynamicPathManager.test_create_path_invalid() 0 12 1
A TestPath.test_compare_same_paths() 0 14 1
A TestDynamicPathManager.test_get_shared_components() 0 26 1
A TestPath.test_status_case_6() 0 9 1
A TestPath.test_status_case_5() 0 9 1
A TestPath.test_status_case_8() 0 10 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
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
    def test_create_path_invalid(self):
329
        """Test create_path method"""
330 1
        path = [
331
            '00:00:00:00:00:00:00:01:1',
332
            '00:00:00:00:00:00:00:02:3',
333
            '00:00:00:00:00:00:00:02',
334
            '00:00:00:00:00:00:00:02:4',
335
            '00:00:00:00:00:00:00:03:2',
336
            '00:00:00:00:00:00:00:03',
337
            '00:00:00:00:00:00:00:03:1',
338
        ]
339 1
        assert DynamicPathManager.create_path(path) is None
340
341 1
    @patch("httpx.post")
342 1
    def test_get_best_paths(self, mock_httpx_post):
343
        """Test get_best_paths method."""
344 1
        controller = MagicMock()
345 1
        controller.get_interface_by_id.side_effect = id_to_interface_mock
346 1
        DynamicPathManager.set_controller(controller)
347
348 1
        paths1 = {
349
            "paths": [
350
                {
351
                    "cost": 5,
352
                    "hops": [
353
                        "00:00:00:00:00:00:00:01:1",
354
                        "00:00:00:00:00:00:00:01",
355
                        "00:00:00:00:00:00:00:01:2",
356
                        "00:00:00:00:00:00:00:02:2",
357
                        "00:00:00:00:00:00:00:02",
358
                        "00:00:00:00:00:00:00:02:1"
359
                        ]
360
                },
361
                {
362
                    "cost": 11,
363
                    "hops": [
364
                        "00:00:00:00:00:00:00:01:1",
365
                        "00:00:00:00:00:00:00:01",
366
                        "00:00:00:00:00:00:00:01:2",
367
                        "00:00:00:00:00:00:00:02:2",
368
                        "00:00:00:00:00:00:00:02",
369
                        "00:00:00:00:00:00:00:02:3",
370
                        "00:00:00:00:00:00:00:03:3",
371
                        "00:00:00:00:00:00:00:03",
372
                        "00:00:00:00:00:00:00:03:4",
373
                        "00:00:00:00:00:00:00:04:4",
374
                        "00:00:00:00:00:00:00:04",
375
                        "00:00:00:00:00:00:00:04:1"
376
                        ]
377
                },
378
            ]
379
        }
380
381 1
        expected_paths_0 = [
382
            Link(
383
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
384
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
385
            ),
386
        ]
387
388 1
        expected_paths_1 = [
389
            Link(
390
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
391
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
392
            ),
393
            Link(
394
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
395
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
396
            ),
397
            Link(
398
                id_to_interface_mock("00:00:00:00:00:00:00:03:4"),
399
                id_to_interface_mock("00:00:00:00:00:00:00:04:4")
400
            ),
401
        ]
402
403 1
        mock_response = MagicMock()
404 1
        mock_response.status_code = 200
405 1
        mock_response.json.return_value = paths1
406 1
        mock_httpx_post.return_value = mock_response
407 1
        kwargs = {
408
            "spf_attribute": settings.SPF_ATTRIBUTE,
409
            "spf_max_path_cost": 8,
410
            "mandatory_metrics": {
411
                "ownership": "red"
412
            }
413
        }
414 1
        circuit = MagicMock()
415 1
        circuit.uni_a.interface.id = "1"
416 1
        circuit.uni_z.interface.id = "2"
417 1
        max_paths = 2
418 1
        res_paths = list(DynamicPathManager.get_best_paths(circuit,
419
                         max_paths=max_paths, **kwargs))
420 1
        assert (
421
            [link.id for link in res_paths[0]] ==
422
            [link.id for link in expected_paths_0]
423
        )
424 1
        assert (
425
            [link.id for link in res_paths[1]] ==
426
            [link.id for link in expected_paths_1]
427
        )
428 1
        expected_call = call(
429
            "http://localhost:8181/api/kytos/pathfinder/v3/",
430
            json={
431
                **{
432
                    "source": circuit.uni_a.interface.id,
433
                    "destination": circuit.uni_z.interface.id,
434
                    "spf_max_paths": max_paths,
435
                },
436
                **kwargs
437
            },
438
            timeout=10,
439
        )
440 1
        mock_httpx_post.assert_has_calls([expected_call])
441
442 1
    @patch('time.sleep')
443 1
    @patch("napps.kytos.mef_eline.models.path.log")
444 1
    @patch("httpx.post")
445 1
    def test_get_best_paths_error(self, mock_httpx_post, mock_log, _):
446
        """Test get_best_paths method."""
447 1
        controller = MagicMock()
448 1
        DynamicPathManager.set_controller(controller)
449 1
        circuit = MagicMock()
450 1
        circuit.id = "id"
451 1
        circuit.uni_a.interface.id = "1"
452 1
        circuit.uni_z.interface.id = "2"
453 1
        circuit.name = "some_name"
454
455 1
        mock_response = MagicMock()
456 1
        mock_response.status_code = 400
457 1
        mock_response.json.return_value = {}
458 1
        mock_httpx_post.return_value = mock_response
459
460 1
        res_paths = list(DynamicPathManager.get_best_paths(circuit))
461 1
        assert not res_paths
462 1
        assert isinstance(res_paths, list)
463 1
        assert mock_log.error.call_count == 1
464
465
    # pylint: disable=too-many-statements, too-many-locals
466 1
    @patch.object(
467
        DynamicPathManager,
468
        "get_shared_components",
469
        side_effect=DynamicPathManager.get_shared_components
470
    )
471 1
    @patch("httpx.post")
472 1
    def test_get_disjoint_paths(self, mock_httpx_post, mock_shared):
473
        """Test get_disjoint_paths method."""
474
475 1
        controller = MagicMock()
476 1
        controller.get_interface_by_id.side_effect = id_to_interface_mock
477 1
        DynamicPathManager.set_controller(controller)
478
479 1
        evc = MagicMock()
480 1
        evc.secondary_constraints = {
481
            "spf_attribute": "hop",
482
            "spf_max_path_cost": 20,
483
            "mandatory_metrics": {
484
                "ownership": "red"
485
            }
486
        }
487 1
        evc.uni_a.interface.id = "1"
488 1
        evc.uni_z.interface.id = "2"
489 1
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
490 1
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
491
492
        # Topo0
493 1
        paths1 = {
494
            "paths": [
495
                {
496
                    "cost": 11,
497
                    "hops": [
498
                        "00:00:00:00:00:00:00:01:1",
499
                        "00:00:00:00:00:00:00:01",
500
                        "00:00:00:00:00:00:00:01:2",
501
                        "00:00:00:00:00:00:00:02:2",
502
                        "00:00:00:00:00:00:00:02",
503
                        "00:00:00:00:00:00:00:02:3",
504
                        "00:00:00:00:00:00:00:04:2",
505
                        "00:00:00:00:00:00:00:04",
506
                        "00:00:00:00:00:00:00:04:3",
507
                        "00:00:00:00:00:00:00:05:2",
508
                        "00:00:00:00:00:00:00:05",
509
                        "00:00:00:00:00:00:00:05:1"
510
                        ]
511
                },
512
                {
513
                    "cost": 11,
514
                    "hops": [
515
                        "00:00:00:00:00:00:00:01:1",
516
                        "00:00:00:00:00:00:00:01",
517
                        "00:00:00:00:00:00:00:01:3",
518
                        "00:00:00:00:00:00:00:03:2",
519
                        "00:00:00:00:00:00:00:03",
520
                        "00:00:00:00:00:00:00:03:3",
521
                        "00:00:00:00:00:00:00:04:4",
522
                        "00:00:00:00:00:00:00:04",
523
                        "00:00:00:00:00:00:00:04:3",
524
                        "00:00:00:00:00:00:00:05:2",
525
                        "00:00:00:00:00:00:00:05",
526
                        "00:00:00:00:00:00:00:05:1"
527
                        ]
528
                },
529
                {
530
                    "cost": 14,
531
                    "hops": [
532
                        "00:00:00:00:00:00:00:01:1",
533
                        "00:00:00:00:00:00:00:01",
534
                        "00:00:00:00:00:00:00:01:2",
535
                        "00:00:00:00:00:00:00:02:2",
536
                        "00:00:00:00:00:00:00:02",
537
                        "00:00:00:00:00:00:00:02:3",
538
                        "00:00:00:00:00:00:00:04:2",
539
                        "00:00:00:00:00:00:00:04",
540
                        "00:00:00:00:00:00:00:04:5",
541
                        "00:00:00:00:00:00:00:06:2",
542
                        "00:00:00:00:00:00:00:06",
543
                        "00:00:00:00:00:00:00:06:3",
544
                        "00:00:00:00:00:00:00:05:3",
545
                        "00:00:00:00:00:00:00:05",
546
                        "00:00:00:00:00:00:00:05:1"
547
                    ]
548
                },
549
                {
550
                    "cost": 14,
551
                    "hops": [
552
                        "00:00:00:00:00:00:00:01:1",
553
                        "00:00:00:00:00:00:00:01",
554
                        "00:00:00:00:00:00:00:01:3",
555
                        "00:00:00:00:00:00:00:03:2",
556
                        "00:00:00:00:00:00:00:03",
557
                        "00:00:00:00:00:00:00:03:3",
558
                        "00:00:00:00:00:00:00:04:4",
559
                        "00:00:00:00:00:00:00:04",
560
                        "00:00:00:00:00:00:00:04:5",
561
                        "00:00:00:00:00:00:00:06:2",
562
                        "00:00:00:00:00:00:00:06",
563
                        "00:00:00:00:00:00:00:06:3",
564
                        "00:00:00:00:00:00:00:05:3",
565
                        "00:00:00:00:00:00:00:05",
566
                        "00:00:00:00:00:00:00:05:1"
567
                    ]
568
                },
569
                {
570
                    "cost": 17,
571
                    "hops": [
572
                        "00:00:00:00:00:00:00:01:1",
573
                        "00:00:00:00:00:00:00:01",
574
                        "00:00:00:00:00:00:00:01:3",
575
                        "00:00:00:00:00:00:00:03:2",
576
                        "00:00:00:00:00:00:00:03",
577
                        "00:00:00:00:00:00:00:03:3",
578
                        "00:00:00:00:00:00:00:04:4",
579
                        "00:00:00:00:00:00:00:04",
580
                        "00:00:00:00:00:00:00:04:5",
581
                        "00:00:00:00:00:00:00:06:2",
582
                        "00:00:00:00:00:00:00:06",
583
                        "00:00:00:00:00:00:00:06:4",
584
                        "00:00:00:00:00:00:00:07:2",
585
                        "00:00:00:00:00:00:00:07",
586
                        "00:00:00:00:00:00:00:07:3",
587
                        "00:00:00:00:00:00:00:05:4",
588
                        "00:00:00:00:00:00:00:05",
589
                        "00:00:00:00:00:00:00:05:1"
590
                    ]
591
                },
592
            ]
593
        }
594
595 1
        mock_response = MagicMock()
596 1
        mock_response.status_code = 200
597 1
        mock_response.json.return_value = paths1
598
599
        # when we dont have the current_path
600 1
        mock_httpx_post.return_value = mock_response
601 1
        disjoint_paths = list(DynamicPathManager.get_disjoint_paths(evc, []))
602 1
        assert not disjoint_paths
603
604 1
        current_path = [
605
            Link(
606
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
607
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
608
            ),
609
            Link(
610
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
611
                id_to_interface_mock("00:00:00:00:00:00:00:04:2")
612
            ),
613
            Link(
614
                id_to_interface_mock("00:00:00:00:00:00:00:04:3"),
615
                id_to_interface_mock("00:00:00:00:00:00:00:05:2")
616
            ),
617
        ]
618 1
        path_links = [
619
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:2"),
620
            ("00:00:00:00:00:00:00:02:3", "00:00:00:00:00:00:00:04:2"),
621
            ("00:00:00:00:00:00:00:04:3", "00:00:00:00:00:00:00:05:2")
622
        ]
623 1
        path_switches = {
624
            "00:00:00:00:00:00:00:04",
625
            "00:00:00:00:00:00:00:02"
626
        }
627
628
        # only one path available from pathfinder (precesilly the
629
        # current_path), so the maximum disjoint path will be empty
630 1
        mock_response.json.return_value = {"paths": paths1["paths"][0:1]}
631 1
        mock_httpx_post.return_value = mock_response
632 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
633 1
        args = mock_shared.call_args[0]
634 1
        assert args[0] == paths1["paths"][0]
635 1
        assert args[1] == path_links
636 1
        assert args[2] == path_switches
637 1
        assert len(paths) == 0
638
639 1
        expected_disjoint_path = [
640
            Link(
641
                id_to_interface_mock("00:00:00:00:00:00:00:01:3"),
642
                id_to_interface_mock("00:00:00:00:00:00:00:03:2")
643
            ),
644
            Link(
645
                id_to_interface_mock("00:00:00:00:00:00:00:03:3"),
646
                id_to_interface_mock("00:00:00:00:00:00:00:04:4")
647
            ),
648
            Link(
649
                id_to_interface_mock("00:00:00:00:00:00:00:04:5"),
650
                id_to_interface_mock("00:00:00:00:00:00:00:06:2")
651
            ),
652
            Link(
653
                id_to_interface_mock("00:00:00:00:00:00:00:06:3"),
654
                id_to_interface_mock("00:00:00:00:00:00:00:05:3")
655
            ),
656
        ]
657
658
        # there are one alternative path
659 1
        mock_response.json.return_value = paths1
660 1
        mock_httpx_post.return_value = mock_response
661 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
662 1
        args = mock_shared.call_args[0]
663 1
        assert args[0] == paths1["paths"][-1]
664 1
        assert args[1] == path_links
665 1
        assert args[2] == {
666
            "00:00:00:00:00:00:00:04",
667
            "00:00:00:00:00:00:00:02"
668
        }
669 1
        assert len(paths) == 4
670
        # for more information on the paths please refer to EP029
671 1
        assert len(paths[0]) == 4  # path S-Z-W-I-D
672 1
        assert len(paths[1]) == 5  # path S-Z-W-I-J-D
673 1
        assert len(paths[2]) == 3  # path S-Z-W-D
674 1
        assert len(paths[3]) == 4  # path S-X-W-I-D
675 1
        assert (
676
            [link.id for link in paths[0]] ==
677
            [link.id for link in expected_disjoint_path]
678
        )
679
680 1
        max_paths = 10
681 1
        expected_call = call(
682
            "http://localhost:8181/api/kytos/pathfinder/v3/",
683
            json={
684
                **{
685
                    "source": evc.uni_a.interface.id,
686
                    "destination": evc.uni_z.interface.id,
687
                    "spf_max_paths": max_paths,
688
                },
689
                **evc.secondary_constraints
690
            },
691
            timeout=10,
692
        )
693 1
        assert mock_httpx_post.call_count >= 1
694
        # If secondary_constraints are set they are expected to be parametrized
695 1
        mock_httpx_post.assert_has_calls([expected_call])
696
697
        # EP029 Topo2
698 1
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
699 1
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:07"
700 1
        paths2 = {
701
            "paths": [
702
                {
703
                    "cost": 14,
704
                    "hops": [
705
                        "00:00:00:00:00:00:00:01:1",
706
                        "00:00:00:00:00:00:00:01",
707
                        "00:00:00:00:00:00:00:01:2",
708
                        "00:00:00:00:00:00:00:02:1",
709
                        "00:00:00:00:00:00:00:02",
710
                        "00:00:00:00:00:00:00:02:2",
711
                        "00:00:00:00:00:00:00:03:1",
712
                        "00:00:00:00:00:00:00:03",
713
                        "00:00:00:00:00:00:00:03:2",
714
                        "00:00:00:00:00:00:00:04:1",
715
                        "00:00:00:00:00:00:00:04",
716
                        "00:00:00:00:00:00:00:04:2",
717
                        "00:00:00:00:00:00:00:07:2",
718
                        "00:00:00:00:00:00:00:07",
719
                        "00:00:00:00:00:00:00:07:1"
720
                    ]
721
                },
722
                {
723
                    "cost": 17,
724
                    "hops": [
725
                        "00:00:00:00:00:00:00:01:1",
726
                        "00:00:00:00:00:00:00:01",
727
                        "00:00:00:00:00:00:00:01:2",
728
                        "00:00:00:00:00:00:00:02:1",
729
                        "00:00:00:00:00:00:00:02",
730
                        "00:00:00:00:00:00:00:02:3",
731
                        "00:00:00:00:00:00:00:05:1",
732
                        "00:00:00:00:00:00:00:05",
733
                        "00:00:00:00:00:00:00:05:2",
734
                        "00:00:00:00:00:00:00:06:1",
735
                        "00:00:00:00:00:00:00:06",
736
                        "00:00:00:00:00:00:00:06:2",
737
                        "00:00:00:00:00:00:00:04:3",
738
                        "00:00:00:00:00:00:00:04",
739
                        "00:00:00:00:00:00:00:04:2",
740
                        "00:00:00:00:00:00:00:07:2",
741
                        "00:00:00:00:00:00:00:07",
742
                        "00:00:00:00:00:00:00:07:1"
743
                    ]
744
                }
745
            ]
746
        }
747
748 1
        current_path = [
749
            Link(
750
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
751
                id_to_interface_mock("00:00:00:00:00:00:00:02:1")
752
            ),
753
            Link(
754
                id_to_interface_mock("00:00:00:00:00:00:00:02:2"),
755
                id_to_interface_mock("00:00:00:00:00:00:00:03:1")
756
            ),
757
            Link(
758
                id_to_interface_mock("00:00:00:00:00:00:00:03:2"),
759
                id_to_interface_mock("00:00:00:00:00:00:00:04:1")
760
            ),
761
            Link(
762
                id_to_interface_mock("00:00:00:00:00:00:00:04:2"),
763
                id_to_interface_mock("00:00:00:00:00:00:00:07:2")
764
            ),
765
        ]
766 1
        path_interfaces = [
767
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:1"),
768
            ("00:00:00:00:00:00:00:02:2", "00:00:00:00:00:00:00:03:1"),
769
            ("00:00:00:00:00:00:00:03:2", "00:00:00:00:00:00:00:04:1"),
770
            ("00:00:00:00:00:00:00:04:2", "00:00:00:00:00:00:00:07:2")
771
        ]
772 1
        path_switches = {
773
            "00:00:00:00:00:00:00:02",
774
            "00:00:00:00:00:00:00:03",
775
            "00:00:00:00:00:00:00:04"
776
        }
777
778 1
        expected_disjoint_path = [
779
            Link(
780
                id_to_interface_mock("00:00:00:00:00:00:00:01:2"),
781
                id_to_interface_mock("00:00:00:00:00:00:00:02:1")
782
            ),
783
            Link(
784
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
785
                id_to_interface_mock("00:00:00:00:00:00:00:05:1")
786
            ),
787
            Link(
788
                id_to_interface_mock("00:00:00:00:00:00:00:05:2"),
789
                id_to_interface_mock("00:00:00:00:00:00:00:06:1")
790
            ),
791
            Link(
792
                id_to_interface_mock("00:00:00:00:00:00:00:06:2"),
793
                id_to_interface_mock("00:00:00:00:00:00:00:04:3")
794
            ),
795
            Link(
796
                id_to_interface_mock("00:00:00:00:00:00:00:04:2"),
797
                id_to_interface_mock("00:00:00:00:00:00:00:07:2")
798
            ),
799
        ]
800
801 1
        mock_response.json.return_value = {"paths": paths2["paths"]}
802 1
        mock_httpx_post.return_value = mock_response
803 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
804 1
        args = mock_shared.call_args[0]
805 1
        assert args[0] == paths2["paths"][-1]
806 1
        assert args[1] == path_interfaces
807 1
        assert args[2] == path_switches
808 1
        assert len(paths) == 1
809 1
        assert (
810
            [link.id for link in paths[0]] ==
811
            [link.id for link in expected_disjoint_path]
812
        )
813
814 1
    @patch("httpx.post")
815 1
    def test_get_disjoint_paths_simple_evc(self, mock_httpx_post):
816
        """Test get_disjoint_paths method for simple EVCs."""
817 1
        controller = MagicMock()
818 1
        controller.get_interface_by_id.side_effect = id_to_interface_mock
819 1
        DynamicPathManager.set_controller(controller)
820
821 1
        evc = MagicMock()
822 1
        evc.secondary_constraints = {
823
            "spf_attribute": "hop",
824
            "spf_max_path_cost": 20,
825
            "mandatory_metrics": {
826
                "ownership": "red"
827
            }
828
        }
829 1
        evc.uni_a.interface.id = "1"
830 1
        evc.uni_z.interface.id = "2"
831 1
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
832 1
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
833
834 1
        mock_paths = {
835
            "paths": [
836
                {
837
                    "cost": 5,
838
                    "hops": [
839
                        "00:00:00:00:00:00:00:01:1",
840
                        "00:00:00:00:00:00:00:01",
841
                        "00:00:00:00:00:00:00:01:3",
842
                        "00:00:00:00:00:00:00:02:2",
843
                        "00:00:00:00:00:00:00:02",
844
                        "00:00:00:00:00:00:00:02:1"
845
                        ]
846
                },
847
                {
848
                    "cost": 8,
849
                    "hops": [
850
                        "00:00:00:00:00:00:00:01:1",
851
                        "00:00:00:00:00:00:00:01",
852
                        "00:00:00:00:00:00:00:01:4",
853
                        "00:00:00:00:00:00:00:03:3",
854
                        "00:00:00:00:00:00:00:03",
855
                        "00:00:00:00:00:00:00:03:2",
856
                        "00:00:00:00:00:00:00:02:3",
857
                        "00:00:00:00:00:00:00:02",
858
                        "00:00:00:00:00:00:00:02:1"
859
                        ]
860
                },
861
            ]
862
        }
863 1
        current_path = [
864
            Link(
865
                id_to_interface_mock("00:00:00:00:00:00:00:01:3"),
866
                id_to_interface_mock("00:00:00:00:00:00:00:02:2")
867
            ),
868
        ]
869 1
        expected_disjoint_path = [
870
            Link(
871
                id_to_interface_mock("00:00:00:00:00:00:00:01:4"),
872
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
873
            ),
874
            Link(
875
                id_to_interface_mock("00:00:00:00:00:00:00:02:3"),
876
                id_to_interface_mock("00:00:00:00:00:00:00:03:2")
877
            ),
878
        ]
879
880 1
        mock_response = MagicMock()
881 1
        mock_response.status_code = 200
882 1
        mock_response.json.return_value = mock_paths
883 1
        mock_httpx_post.return_value = mock_response
884 1
        paths = list(DynamicPathManager.get_disjoint_paths(evc, current_path))
885 1
        assert len(paths) == 1
886 1
        assert (
887
            [link.id for link in paths[0]] ==
888
            [link.id for link in expected_disjoint_path]
889
        )
890
891 1
    @patch("httpx.post")
892 1
    @patch("napps.kytos.mef_eline.models.path.log")
893 1
    @patch("time.sleep")
894 1
    def test_get_disjoint_paths_error(self, _, mock_log, mock_post):
895
        """Test get_disjoint_paths with reported errors. These are caught under
896
        httpx.RequestError."""
897 1
        mock_post.side_effect = TimeoutException('mock')
898 1
        unwanted_path = [
899
            Link(
900
                id_to_interface_mock("00:00:00:00:00:00:00:01:4"),
901
                id_to_interface_mock("00:00:00:00:00:00:00:03:3")
902
            ),
903
        ]
904 1
        evc = MagicMock()
905 1
        evc.secondary_constraints = {
906
            "spf_attribute": "hop",
907
            "spf_max_path_cost": 20,
908
            "mandatory_metrics": {
909
                "ownership": "red"
910
            }
911
        }
912 1
        evc.uni_a.interface.id = "1"
913 1
        evc.uni_z.interface.id = "2"
914 1
        evc.uni_a.interface.switch.id = "00:00:00:00:00:00:00:01"
915 1
        evc.uni_z.interface.switch.id = "00:00:00:00:00:00:00:05"
916 1
        path = DynamicPathManager.get_disjoint_paths(evc, unwanted_path)
917 1
        assert len(list(path)) == 0
918 1
        assert mock_log.error.call_count == 1
919
920 1
        mock_post.side_effect = ConnectError('mock')
921 1
        path = DynamicPathManager.get_disjoint_paths(evc, unwanted_path)
922 1
        assert len(list(path)) == 0
923 1
        assert mock_log.error.call_count == 2
924
925 1
    def test_get_shared_components(self):
926
        """Test get_shared_components"""
927 1
        mock_path = {"hops": [
928
            '00:00:00:00:00:00:00:01:1',
929
            '00:00:00:00:00:00:00:01',
930
            '00:00:00:00:00:00:00:01:4',
931
            '00:00:00:00:00:00:00:05:2',
932
            '00:00:00:00:00:00:00:05',
933
            '00:00:00:00:00:00:00:05:3',
934
            '00:00:00:00:00:00:00:02:4',
935
            '00:00:00:00:00:00:00:02',
936
            '00:00:00:00:00:00:00:02:3',
937
            '00:00:00:00:00:00:00:03:2',
938
            '00:00:00:00:00:00:00:03',
939
            '00:00:00:00:00:00:00:03:1'
940
        ]}
941 1
        mock_links = [
942
            ("00:00:00:00:00:00:00:01:2", "00:00:00:00:00:00:00:02:2"),
943
            ("00:00:00:00:00:00:00:02:3", "00:00:00:00:00:00:00:03:2")
944
        ]
945 1
        mock_switches = {"00:00:00:00:00:00:00:02"}
946 1
        actual_lk, actual_sw = DynamicPathManager.get_shared_components(
947
            mock_path, mock_links, mock_switches
948
        )
949 1
        assert actual_lk == 1
950
        assert actual_sw == 1
951