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

TestDynamicPathManager.test_valid_path_invalid()   A

Complexity

Conditions 1

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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