diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index 47cd6f62..82598988 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -140,7 +140,7 @@ class ConfigService(abc.ABC): self.run_startup(wait) if not wait: if self.validation_mode == ConfigServiceMode.TIMER: - time.sleep(self.validation_timer) + self.wait_validation() else: self.run_validation() @@ -275,6 +275,14 @@ class ConfigService(abc.ABC): f"node({self.node.name}) service({self.name}) failed startup: {e}" ) + def wait_validation(self) -> None: + """ + Waits for a period of time to consider service started successfully. + + :return: nothing + """ + time.sleep(self.validation_timer) + def run_validation(self) -> None: """ Runs validation commands for service on node. @@ -298,7 +306,7 @@ class ConfigService(abc.ABC): ) time.sleep(self.validation_period) - if cmds and time.monotonic() - start > 0: + if cmds and time.monotonic() - start > self.validation_timer: raise ConfigServiceBootError( f"node({self.node.name}) service({self.name}) failed to validate" ) diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py new file mode 100644 index 00000000..eaba4d47 --- /dev/null +++ b/daemon/tests/test_config_services.py @@ -0,0 +1,300 @@ +from unittest import mock + +import pytest + +from core.config import Configuration +from core.configservice.base import ( + ConfigService, + ConfigServiceBootError, + ConfigServiceMode, +) +from core.emulator.enumerations import ConfigDataTypes +from core.errors import CoreCommandError, CoreError + +TEMPLATE_TEXT = "echo hello" + + +class MyService(ConfigService): + name = "MyService" + group = "MyGroup" + directories = ["/usr/local/lib"] + files = ["test.sh"] + executables = [] + dependencies = [] + startup = [f"sh {files[0]}"] + validate = [f"pidof {files[0]}"] + shutdown = [f"pkill {files[0]}"] + validation_mode = ConfigServiceMode.BLOCKING + default_configs = [ + Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), + Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), + Configuration( + _id="value3", + _type=ConfigDataTypes.STRING, + label="Multiple Choice", + options=["value1", "value2", "value3"], + ), + ] + modes = { + "mode1": {"value1": "value1", "value2": "0", "value3": "value2"}, + "mode2": {"value1": "value2", "value2": "1", "value3": "value3"}, + "mode3": {"value1": "value3", "value2": "0", "value3": "value1"}, + } + + def get_text_template(self, name: str) -> str: + return TEMPLATE_TEXT + + +class TestConfigServices: + def test_set_template(self): + # given + node = mock.MagicMock() + text = "echo custom" + service = MyService(node) + + # when + service.set_template(MyService.files[0], text) + + # then + assert MyService.files[0] in service.custom_templates + assert service.custom_templates[MyService.files[0]] == text + + def test_create_directories(self): + # given + node = mock.MagicMock() + service = MyService(node) + + # when + service.create_dirs() + + # then + node.privatedir.assert_called_with(MyService.directories[0]) + + def test_create_files_custom(self): + # given + node = mock.MagicMock() + service = MyService(node) + text = "echo custom" + service.set_template(MyService.files[0], text) + + # when + service.create_files() + + # then + node.nodefile.assert_called_with(MyService.files[0], text) + + def test_create_files_text(self): + # given + node = mock.MagicMock() + service = MyService(node) + + # when + service.create_files() + + # then + node.nodefile.assert_called_with(MyService.files[0], TEMPLATE_TEXT) + + def test_run_startup(self): + # given + node = mock.MagicMock() + wait = True + service = MyService(node) + + # when + service.run_startup(wait=wait) + + # then + node.cmd.assert_called_with(MyService.startup[0], wait=wait) + + def test_run_startup_exception(self): + # given + node = mock.MagicMock() + node.cmd.side_effect = CoreCommandError(1, "error") + service = MyService(node) + + # when + with pytest.raises(ConfigServiceBootError): + service.run_startup(wait=True) + + def test_shutdown(self): + # given + node = mock.MagicMock() + service = MyService(node) + + # when + service.stop() + + # then + node.cmd.assert_called_with(MyService.shutdown[0]) + + def test_run_validation(self): + # given + node = mock.MagicMock() + service = MyService(node) + + # when + service.run_validation() + + # then + node.cmd.assert_called_with(MyService.validate[0]) + + def test_run_validation_timer(self): + # given + node = mock.MagicMock() + service = MyService(node) + service.validation_mode = ConfigServiceMode.TIMER + service.validation_timer = 0 + + # when + service.run_validation() + + # then + node.cmd.assert_called_with(MyService.validate[0]) + + def test_run_validation_timer_exception(self): + # given + node = mock.MagicMock() + node.cmd.side_effect = CoreCommandError(1, "error") + service = MyService(node) + service.validation_mode = ConfigServiceMode.TIMER + service.validation_period = 0 + service.validation_timer = 0 + + # when + with pytest.raises(ConfigServiceBootError): + service.run_validation() + + def test_run_validation_non_blocking(self): + # given + node = mock.MagicMock() + service = MyService(node) + service.validation_mode = ConfigServiceMode.NON_BLOCKING + service.validation_period = 0 + service.validation_timer = 0 + + # when + service.run_validation() + + # then + node.cmd.assert_called_with(MyService.validate[0]) + + def test_run_validation_non_blocking_exception(self): + # given + node = mock.MagicMock() + node.cmd.side_effect = CoreCommandError(1, "error") + service = MyService(node) + service.validation_mode = ConfigServiceMode.NON_BLOCKING + service.validation_period = 0 + service.validation_timer = 0 + + # when + with pytest.raises(ConfigServiceBootError): + service.run_validation() + + def test_render_config(self): + # given + node = mock.MagicMock() + service = MyService(node) + + # when + config = service.render_config() + + # then + assert config == {"value1": "", "value2": "", "value3": ""} + + def test_render_config_custom(self): + # given + node = mock.MagicMock() + service = MyService(node) + custom_config = {"value1": "1", "value2": "2", "value3": "3"} + service.set_config(custom_config) + + # when + config = service.render_config() + + # then + assert config == custom_config + + def test_set_config(self): + # given + node = mock.MagicMock() + service = MyService(node) + custom_config = {"value1": "1", "value2": "2", "value3": "3"} + + # when + service.set_config(custom_config) + + # then + assert service.custom_config == custom_config + + def test_set_config_exception(self): + # given + node = mock.MagicMock() + service = MyService(node) + custom_config = {"value4": "1"} + + # when + with pytest.raises(CoreError): + service.set_config(custom_config) + + def test_start_blocking(self): + # given + node = mock.MagicMock() + service = MyService(node) + service.create_dirs = mock.MagicMock() + service.create_files = mock.MagicMock() + service.run_startup = mock.MagicMock() + service.run_validation = mock.MagicMock() + service.wait_validation = mock.MagicMock() + + # when + service.start() + + # then + service.create_files.assert_called_once() + service.create_dirs.assert_called_once() + service.run_startup.assert_called_once() + service.run_validation.assert_not_called() + service.wait_validation.assert_not_called() + + def test_start_timer(self): + # given + node = mock.MagicMock() + service = MyService(node) + service.validation_mode = ConfigServiceMode.TIMER + service.create_dirs = mock.MagicMock() + service.create_files = mock.MagicMock() + service.run_startup = mock.MagicMock() + service.run_validation = mock.MagicMock() + service.wait_validation = mock.MagicMock() + + # when + service.start() + + # then + service.create_files.assert_called_once() + service.create_dirs.assert_called_once() + service.run_startup.assert_called_once() + service.run_validation.assert_not_called() + service.wait_validation.assert_called_once() + + def test_start_non_blocking(self): + # given + node = mock.MagicMock() + service = MyService(node) + service.validation_mode = ConfigServiceMode.NON_BLOCKING + service.create_dirs = mock.MagicMock() + service.create_files = mock.MagicMock() + service.run_startup = mock.MagicMock() + service.run_validation = mock.MagicMock() + service.wait_validation = mock.MagicMock() + + # when + service.start() + + # then + service.create_files.assert_called_once() + service.create_dirs.assert_called_once() + service.run_startup.assert_called_once() + service.run_validation.assert_called_once() + service.wait_validation.assert_not_called()