TestControllerAsync.test_start_controller()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 24
nop 3
dl 0
loc 29
rs 9.304
c 0
b 0
f 0
ccs 24
cts 24
cp 1
crap 1
1
"""Test kytos.core.controller module."""
2 1
import json
3 1
import logging
4 1
import sys
5 1
import tempfile
6 1
import warnings
7 1
from collections import Counter
8 1
from copy import copy
9 1
from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
10
11 1
import pytest
12 1
from janus import Queue
13 1
from pyof.foundation.exceptions import PackException
14
15 1
from kytos.core import Controller
16 1
from kytos.core.auth import Auth
17 1
from kytos.core.buffers import KytosBuffers, KytosEventBuffer
18 1
from kytos.core.common import EntityStatus
19 1
from kytos.core.config import KytosConfig
20 1
from kytos.core.events import KytosEvent
21 1
from kytos.core.exceptions import KytosNAppSetupException
22 1
from kytos.core.logs import LogManager
23 1
from kytos.core.rest_api import Request
24 1
from kytos.lib.helpers import (get_interface_mock, get_link_mock,
25
                               get_switch_mock)
26
27
28
# pylint: disable=protected-access, too-many-public-methods, too-many-lines
29 1
class TestController:
30
    """Controller tests."""
31
32 1
    def setup_method(self):
33
        """Instantiate a controller."""
34
35 1
        self.options = KytosConfig().options['daemon']
36 1
        self.napps_manager = Mock()
37 1
        Auth.get_user_controller = MagicMock()
38 1
        self.controller = Controller(self.options)
39 1
        self.controller._buffers = MagicMock()
40 1
        self.controller.napps_manager = self.napps_manager
41 1
        self.controller.log = Mock()
42 1
        self.controller.log.getEffectiveLevel.return_value = 20
43 1
        self.app = self.controller.api_server.app
44 1
        self.base_url = "http://127.0.0.1/api/kytos/core/"
45
46 1
    @staticmethod
47 1
    @patch('kytos.core.controller.LogManager')
48 1
    @patch('kytos.core.logs.Path')
49 1
    @pytest.mark.skip(reason="TODO issue 371 in a future PR")
50 1
    def test_websocket_log_usage(path, log_manager):
51
        """Assert that the web socket log is used."""
52
        # Save original state
53
        handlers_bak = copy(logging.root.handlers)
54
55
        # Minimum to instantiate Controller
56
        options = Mock(napps='', logger_decorators=[])
57
        path.return_value.exists.return_value = False
58
        controller = Controller(options)
59
60
        # The test
61
        controller.enable_logs()
62
        log_manager.enable_websocket.assert_called_once()
63
64
        # Restore original state
65
        logging.root.handlers = handlers_bak
66
67 1
    @patch('kytos.core.api_server.APIServer.remove_napp_endpoints')
68 1
    def test_unload_napp_listener(self, _):
69
        """Call NApp shutdown listener on unload."""
70 1
        username, napp_name = 'test', 'napp'
71 1
        listener = self._add_napp(username, napp_name)
72
73 1
        listener.assert_not_called()
74 1
        self.controller.unload_napp(username, napp_name)
75 1
        listener.assert_called()
76
77 1
    @patch('kytos.core.api_server.APIServer.remove_napp_endpoints')
78 1
    def test_unload_napp_other_listener(self, _):
79
        """Should not call other NApps' shutdown listener on unload."""
80 1
        username, napp_name = 'test', 'napp1'
81 1
        self._add_napp(username, napp_name)
82 1
        other_listener = self._add_napp('test', 'napp2')
83
84 1
        self.controller.unload_napp(username, napp_name)
85 1
        other_listener.assert_not_called()
86
87 1
    def _add_napp(self, username, napp_name):
88
        """Add a mocked NApp to the controller."""
89 1
        napp_id = f'{username}/{napp_name}'
90 1
        event_name = f'kytos/core.shutdown.{napp_id}'
91 1
        listener = Mock()
92 1
        self.controller.events_listeners[event_name] = [listener]
93 1
        napp = Mock(_listeners={})
94 1
        self.controller.napps[(username, napp_name)] = napp
95 1
        return listener
96
97 1
    def test_deprecation_warning(self):
98
        """Deprecated method should suggest @rest decorator."""
99 1
        with warnings.catch_warnings(record=True) as wrngs:
100 1
            warnings.simplefilter("always")  # trigger all warnings
101 1
            self.controller.register_rest_endpoint('x', lambda x: x, ['GET'])
102 1
            assert 1 == len(wrngs)
103 1
            warning = wrngs[0]
104 1
            assert warning.category == DeprecationWarning
105 1
            assert '@rest' in str(warning.message)
106
107 1
    def test_loggers(self):
108
        """Test that all controller loggers are under kytos
109
        hierarchy logger.
110
        """
111 1
        loggers = self.controller.loggers()
112 1
        for logger in loggers:
113 1
            assert logger.name.startswith("kytos")
114
115 1
    def test_debug_on(self):
116
        """Test the enable debug feature."""
117
        # Enable debug for kytos.core
118 1
        self.controller.toggle_debug("kytos.core")
119 1
        self._test_debug_result()
120
121 1
    def test_debug_on_defaults(self):
122
        """Test the enable debug feature. Test the default parameter"""
123
        # Enable debug for kytos.core
124 1
        self.controller.toggle_debug("kytos.core")
125 1
        self._test_debug_result()
126
127 1
    def _test_debug_result(self):
128
        """Verify if the loggers have level debug."""
129 1
        loggers = self.controller.loggers()
130 1
        for logger in loggers:
131
            # Check if all kytos.core loggers are in DEBUG mode.
132
            # All the rest must remain the same.
133 1
            if logger.name.startswith("kytos.core"):
134 1
                assert logger.getEffectiveLevel(), logging.DEBUG
135
            else:
136 1
                assert logger.getEffectiveLevel(), logging.CRITICAL
137
138 1
    def test_debug_off(self):
139
        """Test the disable debug feature"""
140
        # Fist we enable the debug
141 1
        self.controller.toggle_debug("kytos.core")
142
        # ... then we disable the debug for the test
143 1
        self.controller.toggle_debug("kytos.core")
144 1
        loggers = self.controller.loggers()
145 1
        for logger in loggers:
146 1
            assert logger.getEffectiveLevel(), logging.CRITICAL
147
148 1
    @patch.object(LogManager, 'load_config_file')
149 1
    def test_debug_no_name(self, mock_load_config_file):
150
        """Test the enable debug logger with default levels."""
151
        # Mock the LogManager that loads the default Loggers
152 1
        self.controller.toggle_debug()
153 1
        self._test_debug_result()
154
155 1
        mock_load_config_file.assert_called_once()
156
157 1
    @patch.object(LogManager, 'load_config_file')
158 1
    def test_debug_empty_name(self, mock_load_config_file):
159
        """Test the enable debug logger with default levels."""
160
        # Mock the LogManager that loads the default Loggers
161 1
        self.controller.toggle_debug('')
162 1
        self._test_debug_result()
163
164 1
        mock_load_config_file.assert_called_once()
165
166 1
    def test_debug_wrong_name(self):
167
        """Test the enable debug logger with wrong name."""
168 1
        pytest.raises(ValueError,
169
                      self.controller.toggle_debug, name="foobar")
170
171 1
    @patch('kytos.core.controller.init_apm')
172 1
    @patch('kytos.core.controller.db_conn_wait')
173 1
    @patch('kytos.core.controller.Controller.start_controller')
174 1
    @patch('kytos.core.controller.Controller.create_pidfile')
175 1
    @patch('kytos.core.controller.Controller.enable_logs')
176 1
    async def test_start(self, *args):
177
        """Test start method."""
178 1
        (mock_enable_logs, mock_create_pidfile,
179
         mock_start_controller, mock_db_conn_wait,
180
         mock_init_apm) = args
181 1
        await self.controller.start()
182
183 1
        mock_enable_logs.assert_called()
184 1
        mock_create_pidfile.assert_called()
185 1
        mock_start_controller.assert_called()
186 1
        mock_db_conn_wait.assert_not_called()
187 1
        mock_init_apm.assert_not_called()
188 1
        assert self.controller.apm is None
189
190 1
    @patch('kytos.core.controller.sys')
191 1
    @patch('kytos.core.controller.init_apm')
192 1
    @patch('kytos.core.controller.db_conn_wait')
193 1
    @patch('kytos.core.controller.Controller.start_controller')
194 1
    @patch('kytos.core.controller.Controller.create_pidfile')
195 1
    @patch('kytos.core.controller.Controller.enable_logs')
196 1
    async def test_start_error_broad_exception(self, *args):
197
        """Test start error handling broad exception."""
198 1
        (mock_enable_logs, mock_create_pidfile,
199
         mock_start_controller, mock_db_conn_wait,
200
         mock_init_apm, mock_sys) = args
201 1
        mock_start_controller.side_effect = Exception
202 1
        await self.controller.start()
203
204 1
        mock_enable_logs.assert_called()
205 1
        mock_create_pidfile.assert_called()
206 1
        mock_start_controller.assert_called()
207 1
        mock_db_conn_wait.assert_not_called()
208 1
        mock_init_apm.assert_not_called()
209 1
        mock_sys.exit.assert_called()
210
211 1
    @patch('kytos.core.controller.init_apm')
212 1
    @patch('kytos.core.controller.db_conn_wait')
213 1
    @patch('kytos.core.controller.Controller.start_controller')
214 1
    @patch('kytos.core.controller.Controller.create_pidfile')
215 1
    @patch('kytos.core.controller.Controller.enable_logs')
216 1
    async def test_start_with_mongodb_and_apm(self, *args):
217
        """Test start method with database and APM options set."""
218 1
        (mock_enable_logs, mock_create_pidfile,
219
         mock_start_controller, mock_db_conn_wait,
220
         mock_init_apm) = args
221 1
        self.controller.options.database = "mongodb"
222 1
        self.controller.options.apm = "es"
223 1
        await self.controller.start()
224
225 1
        mock_enable_logs.assert_called()
226 1
        mock_create_pidfile.assert_called()
227 1
        mock_start_controller.assert_called()
228 1
        mock_db_conn_wait.assert_called()
229 1
        mock_init_apm.assert_called()
230 1
        assert self.controller.apm is not None
231
232 1
    @patch('kytos.core.controller.sys.exit')
233 1
    @patch('kytos.core.controller.Controller.create_pidfile')
234 1
    @patch('kytos.core.controller.Controller.enable_logs')
235 1
    async def test_start_with_invalid_database_backend(self, *args):
236
        """Test start method with unsupported database backend."""
237 1
        (mock_enable_logs, _, mock_sys_exit) = args
238 1
        self.controller.options.database = "invalid"
239 1
        await self.controller.start()
240 1
        mock_enable_logs.assert_called()
241 1
        mock_sys_exit.assert_called()
242
243 1
    @patch('os.getpid')
244 1
    @patch('kytos.core.controller.atexit')
245 1
    def test_create_pidfile(self, *args):
246
        """Test activate method."""
247 1
        (_, mock_getpid) = args
248 1
        mock_getpid.return_value = 2
249 1
        with tempfile.NamedTemporaryFile() as tmp_file:
250 1
            tmp_file.write(b'4194305')  # pid_max +1
251 1
            tmp_file.seek(0)
252 1
            self.controller.options.pidfile = tmp_file.name
253
254 1
            self.controller.create_pidfile()
255
256 1
            pid = tmp_file.read()
257 1
            assert pid == b'2'
258
259 1
    @patch('kytos.core.controller.Controller.__init__')
260 1
    @patch('kytos.core.controller.Controller.start')
261 1
    @patch('kytos.core.controller.Controller.stop')
262 1
    def test_restart(self, *args):
263
        """Test restart method."""
264 1
        (mock_stop, mock_start, mock_init) = args
265 1
        self.controller.started_at = 1
266
267 1
        graceful = True
268 1
        self.controller.restart(graceful)
269
270 1
        mock_stop.assert_called_with(graceful)
271 1
        mock_init.assert_called_with(self.controller.options)
272 1
        mock_start.assert_called_with(restart=True)
273
274 1
    @patch('kytos.core.controller.Controller.stop_controller')
275 1
    def test_stop(self, mock_stop_controller):
276
        """Test stop method."""
277 1
        self.controller.started_at = 1
278
279 1
        graceful = True
280 1
        self.controller.stop(graceful)
281
282 1
        mock_stop_controller.assert_called_with(graceful)
283
284 1
    def test_status(self):
285
        """Test status method."""
286 1
        status_1 = self.controller.status()
287 1
        self.controller.started_at = 1
288 1
        status_2 = self.controller.status()
289
290 1
        assert status_1 == 'Stopped'
291 1
        assert status_2 == 'Running since 1'
292
293 1
    @patch('kytos.core.controller.now')
294 1
    def test_uptime(self, mock_now):
295
        """Test uptime method."""
296 1
        mock_now.return_value = 11
297
298 1
        uptime_1 = self.controller.uptime()
299 1
        self.controller.started_at = 1
300 1
        uptime_2 = self.controller.uptime()
301
302 1
        assert uptime_1 == 0
303 1
        assert uptime_2 == 10
304
305 1
    def test_metadata_endpoint(self):
306
        """Test metadata_endpoint method."""
307 1
        req = Request(scope={"type": "http"})
308 1
        resp = self.controller.metadata_endpoint(req)
309 1
        json_metadata = json.loads(resp.body.decode())
310
311 1
        expected_keys = ['__version__', '__author__', '__license__', '__url__',
312
                         '__description__']
313 1
        assert list(json_metadata.keys()) == expected_keys
314
315 1
    def test_notify_listeners(self):
316
        """Test notify_listeners method."""
317 1
        method = MagicMock()
318 1
        self.controller.events_listeners = {'kytos/any': [method]}
319
320 1
        event = MagicMock()
321 1
        event.name = 'kytos/any'
322 1
        self.controller.notify_listeners(event)
323
324 1
        method.assert_called_with(event)
325
326 1
    def test_get_interface_by_id__not_interface(self):
327
        """Test get_interface_by_id method when interface does not exist."""
328 1
        resp_interface = self.controller.get_interface_by_id(None)
329
330 1
        assert resp_interface is None
331
332 1
    def test_get_interface_by_id__not_switch(self):
333
        """Test get_interface_by_id method when switch does not exist."""
334 1
        interface = MagicMock()
335 1
        switch = MagicMock()
336 1
        switch.interfaces = {123: interface}
337 1
        self.controller.switches = {'00:00:00:00:00:00:00:02': switch}
338
339 1
        interface_id = '00:00:00:00:00:00:00:01:123'
340 1
        resp_interface = self.controller.get_interface_by_id(interface_id)
341
342 1
        assert resp_interface is None
343
344 1
    def test_get_interface_by_id(self):
345
        """Test get_interface_by_id method."""
346 1
        interface = MagicMock()
347 1
        switch = MagicMock()
348 1
        switch.interfaces = {123: interface}
349 1
        self.controller.switches = {'00:00:00:00:00:00:00:01': switch}
350
351 1
        interface_id = '00:00:00:00:00:00:00:01:123'
352 1
        resp_interface = self.controller.get_interface_by_id(interface_id)
353
354 1
        assert resp_interface == interface
355
356 1
    def test_get_switch_by_dpid(self):
357
        """Test get_switch_by_dpid method."""
358 1
        dpid = '00:00:00:00:00:00:00:01'
359 1
        switch = MagicMock(dpid=dpid)
360 1
        self.controller.switches = {dpid: switch}
361
362 1
        resp_switch = self.controller.get_switch_by_dpid(dpid)
363
364 1
        assert resp_switch == switch
365
366 1
    def test_get_switch_or_create__exists(self):
367
        """Test status_api method when switch exists."""
368 1
        dpid = '00:00:00:00:00:00:00:01'
369 1
        switch = MagicMock(dpid=dpid)
370 1
        self.controller.switches = {dpid: switch}
371 1
        self.controller.buffers.conn = MagicMock()
372
373 1
        connection = MagicMock()
374 1
        resp_switch = self.controller.get_switch_or_create(dpid, connection)
375
376 1
        assert resp_switch == switch
377 1
        self.controller.buffers.conn.put.assert_called()
378 1
        ev_name = "kytos/core.switch.reconnected"
379 1
        assert self.controller.buffers.conn.put.call_args[0][0].name == ev_name
380
381 1
    def test_get_switch_or_create__not_exists(self):
382
        """Test status_api method when switch does not exist."""
383 1
        self.controller.switches = {}
384 1
        self.controller.buffers.conn = MagicMock()
385
386 1
        dpid = '00:00:00:00:00:00:00:01'
387 1
        connection = MagicMock()
388 1
        switch = self.controller.get_switch_or_create(dpid, connection)
389
390 1
        expected_switches = {'00:00:00:00:00:00:00:01': switch}
391 1
        assert self.controller.switches == expected_switches
392 1
        self.controller.buffers.conn.put.assert_called()
393 1
        ev_name = "kytos/core.switch.new"
394 1
        assert self.controller.buffers.conn.put.call_args[0][0].name == ev_name
395
396 1
    def test_create_or_update_connection(self):
397
        """Test create_or_update_connection method."""
398 1
        self.controller.connections = {}
399
400 1
        connection = MagicMock()
401 1
        connection.id = '123'
402 1
        self.controller.create_or_update_connection(connection)
403
404 1
        assert self.controller.connections == {'123': connection}
405
406 1
    def test_get_connection_by_id(self):
407
        """Test get_connection_by_id method."""
408 1
        connection = MagicMock()
409 1
        connection.id = '123'
410 1
        self.controller.connections = {connection.id: connection}
411
412 1
        resp_connection = self.controller.get_connection_by_id('123')
413
414 1
        assert resp_connection == connection
415
416 1
    def test_remove_connection(self):
417
        """Test remove_connection method."""
418 1
        connection = MagicMock()
419 1
        connection.id = '123'
420 1
        self.controller.connections = {connection.id: connection}
421
422 1
        self.controller.remove_connection(connection)
423
424 1
        assert not self.controller.connections
425
426 1
    def test_remove_switch(self):
427
        """Test remove_switch method."""
428 1
        switch = MagicMock()
429 1
        switch.dpid = '00:00:00:00:00:00:00:01'
430 1
        self.controller.switches = {switch.dpid: switch}
431
432 1
        self.controller.remove_switch(switch)
433
434 1
        assert not self.controller.switches
435
436 1
    def test_remove_switch__error(self):
437
        """Test remove_switch method to error case."""
438 1
        switch_1 = MagicMock()
439 1
        switch_2 = MagicMock()
440 1
        switch_1.dpid = '00:00:00:00:00:00:00:01'
441 1
        switch_2.dpid = '00:00:00:00:00:00:00:02'
442 1
        self.controller.switches = {switch_1.dpid: switch_1}
443
444 1
        self.controller.remove_switch(switch_2)
445
446 1
        assert self.controller.switches == {switch_1.dpid: switch_1}
447
448 1
    def test_new_connection(self):
449
        """Test new_connection method."""
450 1
        self.controller.connections = {}
451
452 1
        connection = MagicMock()
453 1
        connection.id = '123'
454 1
        event = MagicMock()
455 1
        event.source = connection
456 1
        self.controller.new_connection(event)
457
458 1
        assert self.controller.connections == {'123': connection}
459
460 1
    def test_add_new_switch(self):
461
        """Test add_new_switch method."""
462 1
        self.controller.switches = {}
463
464 1
        switch = MagicMock()
465 1
        switch.dpid = '00:00:00:00:00:00:00:01'
466 1
        self.controller.add_new_switch(switch)
467
468 1
        expected_switches = {'00:00:00:00:00:00:00:01': switch}
469 1
        assert self.controller.switches == expected_switches
470
471 1
    @patch('kytos.core.controller.module_from_spec')
472 1
    @patch('kytos.core.controller.spec_from_file_location')
473 1
    def test_import_napp(self, *args):
474
        """Test _import_napp method."""
475 1
        (mock_spec_from_file, mock_module_from_spec) = args
476 1
        napp_spec = MagicMock()
477 1
        napp_spec.name = 'spec_name'
478 1
        mock_spec_from_file.return_value = napp_spec
479 1
        napp_module = MagicMock()
480 1
        mock_module_from_spec.return_value = napp_module
481
482 1
        self.controller.options.napps = 'napps'
483 1
        self.controller._import_napp('kytos', 'napp')
484
485 1
        assert sys.modules[napp_spec.name] == napp_module
486 1
        mock_spec_from_file.assert_called_with('napps.kytos.napp.main',
487
                                               'napps/kytos/napp/main.py')
488 1
        napp_spec.loader.exec_module.assert_called_with(napp_module)
489
490 1
    def test_load_napp__loaded(self):
491
        """Test load_napp method when napp is already loaded."""
492 1
        napp = MagicMock()
493 1
        self.controller.napps = {('kytos', 'napp'): napp}
494
495 1
        self.controller.load_napp('kytos', 'napp')
496
497 1
        assert self.controller.napps == {('kytos', 'napp'): napp}
498
499 1
    @patch('kytos.core.controller.Controller._import_napp')
500 1
    def test_load_napp__module_not_found(self, mock_import_napp):
501
        """Test load_napp method when module is not found."""
502 1
        mock_import_napp.side_effect = ModuleNotFoundError
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ModuleNotFoundError does not seem to be defined.
Loading history...
503 1
        self.controller.napps = {}
504
505 1
        self.controller.load_napp('kytos', 'napp')
506
507 1
        assert not self.controller.napps
508
509 1
    @patch('kytos.core.controller.Controller._import_napp')
510 1
    def test_load_napp__file_not_found(self, mock_import_napp):
511
        """Test load_napp method when file is not found."""
512 1
        mock_import_napp.side_effect = FileNotFoundError
513 1
        self.controller.napps = {}
514
515 1
        self.controller.load_napp('kytos', 'napp')
516
517 1
        assert not self.controller.napps
518
519 1
    @patch('kytos.core.api_server.APIServer.register_napp_endpoints')
520 1
    @patch('kytos.core.controller.Controller._import_napp')
521 1
    def test_load_napp__error(self, *args):
522
        """Test load_napp method when an error is raised on napp module
523
           attribution."""
524 1
        (mock_import_napp, _) = args
525 1
        self.controller.napps = {}
526
527 1
        module = MagicMock()
528 1
        module.Main.side_effect = Exception
529 1
        mock_import_napp.return_value = module
530
531 1
        with pytest.raises(KytosNAppSetupException):
532 1
            self.controller.load_napp('kytos', 'napp')
533
534 1
        assert not self.controller.napps
535
536 1
    @patch('kytos.core.api_server.APIServer.register_napp_endpoints')
537 1
    @patch('kytos.core.controller.Controller._import_napp')
538 1
    def test_load_napp(self, *args):
539
        """Test load_napp method."""
540 1
        (mock_import_napp, mock_register) = args
541 1
        self.controller.napps = {}
542
543 1
        napp = MagicMock()
544 1
        module = MagicMock()
545 1
        module.Main.return_value = napp
546 1
        mock_import_napp.return_value = module
547
548 1
        self.controller.load_napp('kytos', 'napp')
549
550 1
        assert self.controller.napps == {('kytos', 'napp'): napp}
551 1
        napp.start.assert_called()
552 1
        mock_register.assert_called_with(napp)
553
554 1
    def test_pre_install_napps(self):
555
        """Test pre_install_napps method."""
556 1
        napp_1 = MagicMock()
557 1
        napp_2 = MagicMock()
558 1
        installed_napps = [napp_1]
559 1
        napps = [str(napp_1), str(napp_2)]
560 1
        self.napps_manager.get_installed_napps.return_value = installed_napps
561
562 1
        self.controller.pre_install_napps(napps)
563
564 1
        self.napps_manager.install.assert_called_with(str(napp_2), enable=True)
565
566 1
    @patch('kytos.core.controller.Controller.load_napp')
567 1
    def test_load_napps(self, mock_load):
568
        """Test load_napps method."""
569 1
        napp = MagicMock()
570 1
        napp.username = 'kytos'
571 1
        napp.name = 'name'
572 1
        enabled_napps = [napp]
573 1
        self.napps_manager.get_enabled_napps.return_value = enabled_napps
574
575 1
        self.controller.load_napps()
576
577 1
        mock_load.assert_called_with('kytos', 'name')
578
579 1
    @patch('kytos.core.controller.Controller.unload_napp')
580 1
    def test_unload_napps(self, mock_unload):
581
        """Test un_load_napps method."""
582 1
        napp_tuples = [("kytos", "of_core"), ("kytos", "mef_eline")]
583 1
        enabled_napps = []
584 1
        expected_calls = []
585 1
        for username, napp_name in napp_tuples:
586 1
            mock = MagicMock()
587 1
            mock.username = username
588 1
            mock.name = napp_name
589 1
            enabled_napps.append(mock)
590 1
            expected_calls.append(call(mock.username, mock.name))
591 1
        self.napps_manager.get_enabled_napps.return_value = enabled_napps
592
593 1
        self.controller.unload_napps()
594 1
        assert mock_unload.call_count == len(enabled_napps)
595 1
        assert mock_unload.mock_calls == list(reversed(expected_calls))
596
597 1
    @patch('kytos.core.controller.import_module')
598 1
    def test_reload_napp_module__module_not_found(self, mock_import_module):
599
        """Test reload_napp_module method when module is not found."""
600 1
        mock_import_module.side_effect = ModuleNotFoundError
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ModuleNotFoundError does not seem to be defined.
Loading history...
601
602 1
        with pytest.raises(ModuleNotFoundError):
603 1
            self.controller.reload_napp_module('kytos', 'napp', 'napp_file')
604
605 1
    @patch('kytos.core.controller.reload_module')
606 1
    @patch('kytos.core.controller.import_module')
607 1
    def test_reload_napp_module__import_error(self, *args):
608
        """Test reload_napp_module method when an import error occurs."""
609 1
        (mock_import_module, mock_reload_module) = args
610 1
        napp_module = MagicMock()
611 1
        mock_import_module.return_value = napp_module
612 1
        mock_reload_module.side_effect = ImportError
613
614 1
        with pytest.raises(ImportError):
615 1
            self.controller.reload_napp_module('kytos', 'napp', 'napp_file')
616
617 1
    @patch('kytos.core.controller.reload_module')
618 1
    @patch('kytos.core.controller.import_module')
619 1
    def test_reload_napp_module(self, *args):
620
        """Test reload_napp_module method."""
621 1
        (mock_import_module, mock_reload_module) = args
622 1
        napp_module = MagicMock()
623 1
        mock_import_module.return_value = napp_module
624
625 1
        self.controller.reload_napp_module('kytos', 'napp', 'napp_file')
626
627 1
        mock_import_module.assert_called_with('napps.kytos.napp.napp_file')
628 1
        mock_reload_module.assert_called_with(napp_module)
629
630 1
    @patch('kytos.core.controller.Controller.load_napp')
631 1
    @patch('kytos.core.controller.Controller.unload_napp')
632 1
    @patch('kytos.core.controller.Controller.reload_napp_module')
633 1
    def test_reload_napp(self, *args):
634
        """Test reload_napp method."""
635 1
        (mock_reload_napp_module, mock_unload, mock_load) = args
636
637 1
        code = self.controller.reload_napp('kytos', 'napp')
638
639 1
        mock_unload.assert_called_with('kytos', 'napp')
640 1
        calls = [call('kytos', 'napp', 'settings'),
641
                 call('kytos', 'napp', 'main')]
642 1
        mock_reload_napp_module.assert_has_calls(calls)
643 1
        mock_load.assert_called_with('kytos', 'napp')
644 1
        assert code == 200
645
646 1
    @patch('kytos.core.controller.Controller.unload_napp')
647 1
    @patch('kytos.core.controller.Controller.reload_napp_module')
648 1
    def test_reload_napp__error(self, *args):
649
        """Test reload_napp method to error case."""
650 1
        (mock_reload_napp_module, _) = args
651 1
        mock_reload_napp_module.side_effect = ModuleNotFoundError
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ModuleNotFoundError does not seem to be defined.
Loading history...
652
653 1
        code = self.controller.reload_napp('kytos', 'napp')
654
655 1
        assert code == 400
656
657 1
    @patch('kytos.core.controller.Controller.reload_napp', return_value=200)
658 1
    def test_rest_reload_napp(self, mock_reload_napp):
659
        """Test rest_reload_napp method."""
660 1
        req = Request(
661
            scope={
662
                "type": "http",
663
                "path_params": {"username": "kytos", "napp_name": "napp"},
664
            }
665
        )
666 1
        resp = self.controller.rest_reload_napp(req)
667
668 1
        mock_reload_napp.assert_called_with('kytos', 'napp')
669 1
        assert json.loads(resp.body.decode()) == 'reloaded'
670 1
        assert resp.status_code == 200
671
672 1
    @patch('kytos.core.controller.Controller.reload_napp')
673 1
    def test_rest_reload_all_napps(self, mock_reload_napp):
674
        """Test rest_reload_all_napps method."""
675 1
        req = Request(
676
            scope={
677
                "type": "http",
678
                "path_params": {"username": "kytos", "napp_name": "napp"},
679
            }
680
        )
681 1
        self.controller.napps = [('kytos', 'napp')]
682 1
        resp = self.controller.rest_reload_all_napps(req)
683
684 1
        mock_reload_napp.assert_called_with('kytos', 'napp')
685 1
        assert json.loads(resp.body.decode()) == 'reloaded'
686 1
        assert resp.status_code == 200
687
688 1
    def test_init_attrs(self):
689
        """Test init attrs."""
690 1
        self.controller.start_auth()
691 1
        assert self.controller.auth
692 1
        assert self.controller.dead_letter
693
694 1
    def test_try_to_fmt_traceback_msg(self) -> None:
695
        """Test test_try_to_fmt_traceback_msg."""
696 1
        counter = Counter(range(5))
697 1
        msg = "some traceback msg"
698 1
        fmt_msg = self.controller._try_to_fmt_traceback_msg(msg, counter)
699 1
        assert msg in fmt_msg
700 1
        assert "counters" in fmt_msg
701
702 1
    def test_config_default_maxsize_multiplier(self) -> None:
703
        """Test KytosConfig default maxsize multiplier."""
704 1
        event_buffer_conf = self.controller.options.event_buffer_conf
705 1
        assert event_buffer_conf
706 1
        queues = event_buffer_conf.values()
707 1
        assert queues
708 1
        for queue in queues:
709 1
            assert queue["queue"]["maxsize_multiplier"] == 2
710
711 1
    def test_get_links_from_interfaces(self) -> None:
712
        """Test get_links_from_interfaces."""
713 1
        interfaces = [MagicMock(id=f"intf{n}") for n in range(4)]
714 1
        links = {
715
            "link1": MagicMock(id="link1",
716
                               endpoint_a=interfaces[0],
717
                               endpoint_b=interfaces[1]),
718
            "link2": MagicMock(id="link2",
719
                               endpoint_a=interfaces[2],
720
                               endpoint_b=interfaces[3]),
721
        }
722 1
        self.controller.links = links
723 1
        response = self.controller.get_links_from_interfaces(interfaces)
724 1
        assert links == response
725 1
        response = self.controller.get_links_from_interfaces(interfaces[:2])
726 1
        assert response == {"link1": links["link1"]}
727
728 1
    def test_get_link(self) -> None:
729
        """Test get_link."""
730 1
        dpid_a = "00:00:00:00:00:00:00:01"
731 1
        dpid_b = "00:00:00:00:00:00:00:02"
732 1
        mock_switch_a = get_switch_mock(dpid_a, 0x04)
733 1
        mock_switch_b = get_switch_mock(dpid_b, 0x04)
734 1
        mock_interface_a = get_interface_mock('s1-eth1', 1, mock_switch_a)
735 1
        mock_interface_b = get_interface_mock('s2-eth1', 1, mock_switch_b)
736 1
        mock_link = get_link_mock(mock_interface_a, mock_interface_b)
737
738 1
        self.controller.links = {"link1": mock_link}
739 1
        assert self.controller.get_link("link1") == mock_link
740
741 1
    def test_get_link_or_create(self):
742
        """Test _get_link_or_create."""
743 1
        dpid_a = "00:00:00:00:00:00:00:01"
744 1
        dpid_b = "00:00:00:00:00:00:00:02"
745 1
        mock_switch_a = get_switch_mock(dpid_a, 0x04)
746 1
        mock_switch_b = get_switch_mock(dpid_b, 0x04)
747 1
        mock_interface_a = get_interface_mock('s1-eth1', 1, mock_switch_a)
748 1
        mock_interface_b = get_interface_mock('s2-eth1', 1, mock_switch_b)
749 1
        mock_interface_a.id = dpid_a
750 1
        mock_interface_a.link = None
751 1
        mock_interface_b.id = dpid_b
752 1
        mock_interface_b.link = None
753
754 1
        link, created = self.controller.get_link_or_create(mock_interface_a,
755
                                                           mock_interface_b)
756 1
        mock_interface_a.link = link
757 1
        mock_interface_b.link = link
758 1
        assert created
759 1
        assert len(self.controller.links) == 1
760 1
        assert link.endpoint_a.id == dpid_a
761 1
        assert link.endpoint_b.id == dpid_b
762 1
        assert mock_interface_a.nni is True
763 1
        mock_interface_a.update_link.assert_called()
764 1
        assert mock_interface_b.nni is True
765 1
        mock_interface_b.update_link.assert_called()
766
767 1
        link, created = self.controller.get_link_or_create(mock_interface_a,
768
                                                           mock_interface_b)
769 1
        assert not created
770 1
        assert len(self.controller.links) == 1
771
772
        # enable link
773 1
        link_dict = {'enabled': True}
774 1
        self.controller.links = {}
775 1
        link, _ = self.controller.get_link_or_create(
776
            mock_interface_a, mock_interface_b, link_dict
777
        )
778 1
        assert link._enabled is True
779
        # disable link
780 1
        link_dict = {'enabled': False}
781 1
        self.controller.links = {}
782 1
        link, _ = self.controller.get_link_or_create(
783
            mock_interface_a, mock_interface_b, link_dict
784
        )
785 1
        assert link._enabled is False
786
787 1
    def test_detect_mismatched_link(self):
788
        """Test detect_mismatched_link"""
789 1
        mock_link_1 = MagicMock(id='link_1')
790 1
        mock_link_1.endpoint_a = MagicMock(link=mock_link_1)
791 1
        mock_link_1.endpoint_b = MagicMock(link=None)
792 1
        assert self.controller.detect_mismatched_link(mock_link_1)
793
794 1
        mock_link_1.endpoint_a.link = None
795 1
        mock_link_1.endpoint_b.link = mock_link_1
796 1
        assert self.controller.detect_mismatched_link(mock_link_1)
797
798 1
        mock_link_2 = MagicMock(id='link_2')
799 1
        mock_link_1.endpoint_a.link = mock_link_2
800 1
        assert self.controller.detect_mismatched_link(mock_link_1)
801
802 1
        mock_link_1.endpoint_a.link = mock_link_1
803 1
        assert not self.controller.detect_mismatched_link(mock_link_1)
804
805 1
    @patch('kytos.core.controller.Controller.detect_mismatched_link')
806 1
    def test_link_status_mismatched(self, mock_detect_mismatched_link):
807
        """Test link_status_mismatched"""
808 1
        mock_link_1 = MagicMock()
809 1
        mock_detect_mismatched_link.return_value = True
810 1
        assert (self.controller.link_status_mismatched(mock_link_1)
811
                == EntityStatus.DOWN)
812
813 1
        mock_detect_mismatched_link.return_value = False
814 1
        assert self.controller.link_status_mismatched(mock_link_1) is None
815
816 1
    def test_get_link_or_create_old_mismatched_link(self):
817
        """Test _get_link_or_create with recently added old link
818
         which was mismatched.
819
         Also testing detect_mismatched_link."""
820 1
        dpid_a = "00:00:00:00:00:00:00:01"
821 1
        dpid_b = "00:00:00:00:00:00:00:02"
822 1
        mock_switch_a = get_switch_mock(dpid_a, 0x04)
823 1
        mock_switch_b = get_switch_mock(dpid_b, 0x04)
824 1
        mock_interface_a = get_interface_mock('s1-eth1', 1, mock_switch_a)
825 1
        mock_interface_b = get_interface_mock('s2-eth1', 1, mock_switch_b)
826 1
        mock_interface_c = get_interface_mock('s2-eth2', 2, mock_switch_b)
827 1
        mock_interface_a.id = dpid_a + ':1'
828 1
        mock_interface_a.link = None
829 1
        mock_interface_b.id = dpid_b + ':1'
830 1
        mock_interface_b.link = None
831 1
        mock_interface_c.id = dpid_b + ':2'
832 1
        mock_interface_c.link = None
833 1
        link1, _ = self.controller.get_link_or_create(mock_interface_a,
834
                                                      mock_interface_b)
835 1
        mock_interface_a.link = link1
836 1
        mock_interface_b.link = link1
837
838
        # Create mismatching
839 1
        link2, _ = self.controller.get_link_or_create(mock_interface_a,
840
                                                      mock_interface_c)
841 1
        mock_interface_a.link = link2
842 1
        mock_interface_c.link = link2
843
844 1
        assert self.controller.detect_mismatched_link(link1)
845 1
        link1.add_metadata('old_data', 'important_data')
846 1
        assert link1.metadata.get('old_data')
847
848
        # Clean link1 mismatch and make link2 mismatched
849 1
        actual_link, _ = self.controller.get_link_or_create(mock_interface_a,
850
                                                            mock_interface_b)
851 1
        mock_interface_a.link = actual_link
852 1
        mock_interface_b.link = actual_link
853
854 1
        assert actual_link == link1
855 1
        assert self.controller.detect_mismatched_link(link2)
856 1
        assert not self.controller.detect_mismatched_link(link1)
857
858 1
    def test_get_link_or_create_mismatched(self):
859
        """Test _get_link_or_create with mismatched link."""
860 1
        dpid_a = "00:00:00:00:00:00:00:01"
861 1
        dpid_b = "00:00:00:00:00:00:00:02"
862 1
        mock_switch_a = get_switch_mock(dpid_a, 0x04)
863 1
        mock_switch_b = get_switch_mock(dpid_b, 0x04)
864 1
        mock_interface_a = get_interface_mock('s1-eth1', 1, mock_switch_a)
865 1
        mock_interface_b = get_interface_mock('s2-eth1', 1, mock_switch_b)
866 1
        mock_interface_c = get_interface_mock('s2-eth2', 2, mock_switch_b)
867 1
        mock_interface_a.id = dpid_a + ':1'
868 1
        mock_interface_a.link = None
869 1
        mock_interface_b.id = dpid_b + ':1'
870 1
        mock_interface_b.link = None
871 1
        mock_interface_c.id = dpid_b + ':2'
872 1
        mock_interface_c.link = None
873
874 1
        link1, created = self.controller.get_link_or_create(mock_interface_a,
875
                                                            mock_interface_b)
876 1
        assert created
877 1
        assert link1.endpoint_a.id == mock_interface_a.id
878 1
        assert link1.endpoint_b.id == mock_interface_b.id
879
880 1
        mock_interface_a.link = link1
881 1
        mock_interface_b.link = link1
882
883 1
        link2, created = self.controller.get_link_or_create(mock_interface_a,
884
                                                            mock_interface_c)
885 1
        assert created
886 1
        assert self.controller.log.warning.call_count == 1
887 1
        assert link2.endpoint_a.id == mock_interface_a.id
888 1
        assert link2.endpoint_b.id == mock_interface_c.id
889
890 1
        mock_interface_a.link = link2
891 1
        mock_interface_c.link = link2
892
893 1
        link3, created = self.controller.get_link_or_create(mock_interface_b,
894
                                                            mock_interface_c)
895 1
        assert created
896 1
        assert self.controller.log.warning.call_count == 3
897 1
        assert link3.endpoint_a.id == mock_interface_b.id
898 1
        assert link3.endpoint_b.id == mock_interface_c.id
899
900
901 1
class TestControllerAsync:
902
903
    """TestControllerAsync."""
904
905 1
    async def test_start_controller(self, controller, monkeypatch):
906
        """Test start controller."""
907 1
        controller._buffers = KytosBuffers()
908 1
        controller.loop = MagicMock()
909 1
        server = MagicMock()
910 1
        monkeypatch.setattr("kytos.core.controller.KytosServer", server)
911 1
        napp = MagicMock()
912 1
        controller._pool = MagicMock()
913 1
        controller.pre_install_napps = MagicMock()
914 1
        controller.api_server = MagicMock()
915 1
        controller.load_napps = MagicMock()
916 1
        controller.options.napps_pre_installed = [napp]
917 1
        await controller.start_controller()
918 1
        assert controller.buffers
919
920 1
        controller.server.serve_forever.assert_called()
921 1
        all_buffers = controller.buffers.get_all_buffers()
922
        # It's expected that all buffers have a task + the api server task
923 1
        assert controller.loop.create_task.call_count == len(all_buffers) + 1
924 1
        assert len(controller._tasks) == len(all_buffers) + 1
925 1
        controller.pre_install_napps.assert_called_with([napp])
926 1
        controller.load_napps.assert_called()
927 1
        controller.api_server.start_web_ui.assert_called()
928
929
        # These monitors are expected by default
930 1
        expected_buffer_qmons = ["msg_in", "msg_out", "raw", "app"]
931 1
        expected_tp_qmons = ["sb", "app", "db"]
932 1
        expected_len = len(expected_tp_qmons) + len(expected_buffer_qmons)
933 1
        assert len(controller.qmonitors) == expected_len
934
935 1
    async def test_stop_controller(self, controller):
936
        """Test stop_controller method."""
937 1
        controller.loop = MagicMock()
938 1
        api_server = MagicMock()
939 1
        napp_dir_listener = MagicMock()
940 1
        controller.server = MagicMock()
941 1
        controller.unload_napps = MagicMock()
942 1
        controller._buffers = MagicMock()
943 1
        controller.api_server = api_server
944 1
        controller.napp_dir_listener = napp_dir_listener
945 1
        controller.stop_queue_monitors = MagicMock()
946 1
        controller.apm = MagicMock()
947
948 1
        controller.stop_controller()
949 1
        controller.apm.close.assert_called()
950 1
        controller.buffers.send_stop_signal.assert_called()
951 1
        api_server.stop.assert_called()
952 1
        napp_dir_listener.stop.assert_called()
953 1
        controller.unload_napps.assert_called()
954 1
        controller.server.shutdown.assert_called()
955 1
        controller.loop.stop.assert_called()
956 1
        controller.stop_queue_monitors.assert_called()
957
958 1
    async def test_raw_event_handler(self, controller):
959
        """Test raw_event_handler async method by handling a shutdown event."""
960 1
        controller._buffers = KytosBuffers()
961 1
        event = KytosEvent("kytos/core.shutdown")
962 1
        controller.notify_listeners = MagicMock()
963 1
        await controller.buffers.raw._queue.async_q.put(event)
964 1
        await controller.event_handler("raw")
965 1
        controller.notify_listeners.assert_called_with(event)
966
967 1
    async def test_msg_in_event_handler(self, controller):
968
        """Test msg_in_event_handler async method by handling a shutdown
969
           event."""
970 1
        controller._buffers = KytosBuffers()
971 1
        event = KytosEvent("kytos/core.shutdown")
972 1
        controller.notify_listeners = MagicMock()
973 1
        await controller.buffers.msg_in._queue.async_q.put(event)
974 1
        await controller.event_handler("msg_in")
975 1
        controller.notify_listeners.assert_called_with(event)
976
977 1
    async def test_msg_out_event_handler(self, controller):
978
        """Test msg_out_event_handler async method by handling a common and a
979
           shutdown event."""
980 1
        controller._buffers = KytosBuffers()
981 1
        controller.notify_listeners = MagicMock()
982 1
        dst = MagicMock()
983 1
        dst.state = 0
984 1
        packet = MagicMock()
985 1
        msg = MagicMock()
986 1
        msg.pack.return_value = packet
987
988 1
        event_1 = KytosEvent('kytos/core.any',
989
                             content={'message': msg, 'destination': dst})
990 1
        event_2 = KytosEvent('kytos/core.shutdown')
991
992 1
        await controller.buffers.msg_out._queue.async_q.put(event_1)
993 1
        await controller.buffers.msg_out._queue.async_q.put(event_2)
994 1
        await controller.msg_out_event_handler()
995 1
        dst.send.assert_called_with(packet)
996 1
        controller.notify_listeners.assert_called_with(event_1)
997
998 1
    async def test_msg_out_event_handler_pack_exc(self, controller):
999
        """Test msg_out_event_handler async pack exception."""
1000 1
        controller._buffers = KytosBuffers()
1001 1
        dst, msg = MagicMock(), MagicMock()
1002 1
        dst.state = 0
1003 1
        msg.pack.side_effect = PackException("some error")
1004 1
        event_1 = KytosEvent('kytos/core.any',
1005
                             content={'message': msg, 'destination': dst})
1006 1
        event_2 = KytosEvent('kytos/core.shutdown')
1007
1008 1
        await controller.buffers.msg_out._queue.async_q.put(event_1)
1009 1
        await controller.buffers.msg_out._queue.async_q.put(event_2)
1010 1
        await controller.msg_out_event_handler()
1011 1
        assert controller.log.error.call_count == 1
1012
1013 1
    async def test_msg_out_event_handler_broad_exc(self, controller):
1014
        """Test msg_out_event_handler async broad exception."""
1015 1
        controller._buffers = KytosBuffers()
1016 1
        dst, msg = MagicMock(), MagicMock()
1017 1
        dst.state = 0
1018 1
        msg.pack.side_effect = ValueError("some error")
1019 1
        event_1 = KytosEvent('kytos/core.any',
1020
                             content={'message': msg, 'destination': dst})
1021 1
        event_2 = KytosEvent('kytos/core.shutdown')
1022
1023 1
        await controller.buffers.msg_out._queue.async_q.put(event_1)
1024 1
        await controller.buffers.msg_out._queue.async_q.put(event_2)
1025 1
        await controller.msg_out_event_handler()
1026 1
        assert controller.log.exception.call_count == 1
1027
1028 1
    async def test_app_event_handler(self, controller):
1029
        """Test app_event_handler async method by handling a shutdown event."""
1030 1
        controller._buffers = KytosBuffers()
1031 1
        event = KytosEvent("kytos/core.shutdown")
1032 1
        controller.notify_listeners = MagicMock()
1033 1
        await controller.buffers.app._queue.async_q.put(event)
1034 1
        await controller.event_handler("app")
1035 1
        controller.notify_listeners.assert_called_with(event)
1036
1037 1
    async def test_app_event_handler_exc(self, controller):
1038
        """Test app_event_handler async method exc."""
1039 1
        controller._buffers = KytosBuffers()
1040 1
        event1 = KytosEvent("kytos/core.any", content={"message": ""})
1041 1
        event2 = KytosEvent("kytos/core.shutdown")
1042 1
        controller.notify_listeners = MagicMock()
1043 1
        controller.notify_listeners.side_effect = [ValueError("some error"), 1]
1044 1
        await controller.buffers.app._queue.async_q.put(event1)
1045 1
        await controller.buffers.app._queue.async_q.put(event2)
1046 1
        await controller.event_handler("app")
1047 1
        assert controller.log.exception.call_count == 1
1048 1
        controller.notify_listeners.assert_called_with(event2)
1049
1050 1
    async def test_configuration_endpoint(self, controller, api_client):
1051
        """Should return the attribute options as json."""
1052 1
        expected = vars(controller.options)
1053 1
        expected.pop("jwt_secret", None)
1054 1
        resp = await api_client.get("kytos/core/config")
1055 1
        assert resp.status_code == 200
1056 1
        assert expected == resp.json()
1057
1058 1
    async def test_publish_connection_error(self, controller):
1059
        """Test publish_connection_error."""
1060 1
        controller.buffers.conn.aput = AsyncMock()
1061 1
        await controller.publish_connection_error(MagicMock())
1062 1
        controller.buffers.conn.aput.assert_called()
1063
1064 1
    async def test_full_queue_counter(self, controller) -> None:
1065
        """Test full queue counter."""
1066 1
        maxsize = 2
1067 1
        queue = Queue(maxsize=maxsize)
1068 1
        buffer = KytosEventBuffer("app", queue)
1069 1
        for i in range(maxsize):
1070 1
            await buffer.aput(KytosEvent(str(i)))
1071 1
        assert buffer.full()
1072 1
        controller._buffers.get_all_buffers.return_value = [buffer]
1073 1
        counter = controller._full_queue_counter()
1074 1
        assert counter
1075 1
        assert len(counter["app"]) == maxsize
1076 1
        queue.close()
1077
        await queue.wait_closed()
1078