Passed
Push — master ( 1aa63c...62b589 )
by Vinicius
03:50 queued 14s
created

TestPath.test_status_case_8()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

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