Passed
Pull Request — master (#617)
by Aldo
04:23
created

TestDynamicPathManager.test_get_shared_components()   A

Complexity

Conditions 1

Size

Total Lines 26
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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