From eecbff76fa6401edaf2abbee9d86e08162f752eb Mon Sep 17 00:00:00 2001 From: Tomer Haskalovitch Date: Tue, 24 Feb 2026 13:38:36 +0200 Subject: [PATCH] mgr/nvmeof: add unittests Fixes: https://tracker.ceph.com/issues/74702 Signed-off-by: Tomer Haskalovitch --- src/pybind/mgr/nvmeof/tests/__init__.py | 0 .../mgr/nvmeof/tests/test_nvmeof_module.py | 85 +++++++++++++++++++ .../orchestrator/tests/test_orchestrator.py | 55 ++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 src/pybind/mgr/nvmeof/tests/__init__.py create mode 100644 src/pybind/mgr/nvmeof/tests/test_nvmeof_module.py diff --git a/src/pybind/mgr/nvmeof/tests/__init__.py b/src/pybind/mgr/nvmeof/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/nvmeof/tests/test_nvmeof_module.py b/src/pybind/mgr/nvmeof/tests/test_nvmeof_module.py new file mode 100644 index 00000000000..44741a38129 --- /dev/null +++ b/src/pybind/mgr/nvmeof/tests/test_nvmeof_module.py @@ -0,0 +1,85 @@ + +from contextlib import contextmanager +from unittest.mock import MagicMock + +import nvmeof.module as nvmeof_mod + + +class FakeRados: + def __init__(self, exists: bool): + self._exists = exists + self.opened_pools = [] + + def pool_exists(self, pool_name: str) -> bool: + return self._exists + + @contextmanager + def open_ioctx(self, pool_name: str): + self.opened_pools.append(pool_name) + yield object() + + +def patch_rbd_pool_init(monkeypatch): + rbd_instance = MagicMock() + monkeypatch.setattr(nvmeof_mod.rbd, "RBD", lambda: rbd_instance) + return rbd_instance + + +def make_mgr(mon_handler, exists: bool, monkeypatch): + mgr = nvmeof_mod.NVMeoF.__new__(nvmeof_mod.NVMeoF) + mgr.mon_command = mon_handler + mgr._print_log = lambda *args, **kwargs: None + mgr.run = False + + mgr._fake_rados = FakeRados(exists) + mgr.remote = MagicMock() + + def _pool_exists(self, pool_name: str) -> bool: + return self._fake_rados.pool_exists(pool_name) + + def _rbd_pool_init(self, pool_name: str): + with self._fake_rados.open_ioctx(pool_name) as ioctx: + nvmeof_mod.rbd.RBD().pool_init(ioctx, False) + + monkeypatch.setattr(nvmeof_mod.NVMeoF, "_pool_exists", _pool_exists, raising=True) + monkeypatch.setattr(nvmeof_mod.NVMeoF, "_rbd_pool_init", _rbd_pool_init, raising=True) + + return mgr + + +def test_pool_exists_skips_create_calls_enable_and_pool_init(monkeypatch): + calls = [] + + def mon_command(cmd, inbuf): + calls.append(cmd) + return 0, "", "" + + rbd_instance = patch_rbd_pool_init(monkeypatch) + mgr = make_mgr(mon_command, exists=True, monkeypatch=monkeypatch) + + mgr.create_pool_if_not_exists() + + assert not any(c.get("prefix") == "osd pool create" for c in calls) + assert any(c.get("prefix") == "osd pool application enable" for c in calls) + + assert mgr._fake_rados.opened_pools == [".nvmeof"] + rbd_instance.pool_init.assert_called_once() + + +def test_pool_missing_creates_then_enables_then_pool_init(monkeypatch): + calls = [] + + def mon_command(cmd, inbuf): + calls.append(cmd) + return 0, "", "" + + rbd_instance = patch_rbd_pool_init(monkeypatch) + mgr = make_mgr(mon_command, exists=False, monkeypatch=monkeypatch) + + mgr.create_pool_if_not_exists() + + assert any(c.get("prefix") == "osd pool create" for c in calls) + assert any(c.get("prefix") == "osd pool application enable" for c in calls) + + assert mgr._fake_rados.opened_pools == [".nvmeof"] + rbd_instance.pool_init.assert_called_once() diff --git a/src/pybind/mgr/orchestrator/tests/test_orchestrator.py b/src/pybind/mgr/orchestrator/tests/test_orchestrator.py index 53c47ea8bfe..39306485083 100644 --- a/src/pybind/mgr/orchestrator/tests/test_orchestrator.py +++ b/src/pybind/mgr/orchestrator/tests/test_orchestrator.py @@ -293,3 +293,58 @@ def test_preview_table_osd_smoke(): } ] preview_table_osd(data) + + +@mock.patch("orchestrator.module.OrchestratorCli.release_name", new_callable=mock.PropertyMock) +@mock.patch("orchestrator.module.OrchestratorCli._apply_misc") +@mock.patch("orchestrator.module.OrchestratorCli.remote") +@mock.patch("orchestrator.module.OrchestratorCli.get") +class TestApplyNvmeof: + + def setup_method(self): + self.m = OrchestratorCli('orchestrator', 0, 0) + + def test_missing_group_raises_validation_error(self, mock_get, mock_remote, mock_apply_misc, mock_release_name): + res = self.m._apply_nvmeof(pool="mypool", group="") + + assert res.retval != 0 + assert "The --group argument is required" in res.stderr + mock_apply_misc.assert_not_called() + + def test_inbuf_raises_validation_error(self, mock_get, mock_remote, mock_apply_misc, mock_release_name): + res = self.m._apply_nvmeof(pool="mypool", group="mygroup", inbuf="some_yaml_content") + + assert res.retval != 0 + assert "unrecognized command -i; -h or --help for usage" in res.stderr + mock_apply_misc.assert_not_called() + + def test_custom_pool_skips_metadata_pool_creation(self, mock_get, mock_remote, mock_apply_misc, mock_release_name): + mock_apply_misc.return_value = HandleCommandResult(retval=0, stdout="Success") + + res = self.m._apply_nvmeof(pool="custompool", group="mygroup") + + mock_remote.assert_not_called() + mock_apply_misc.assert_called_once() + assert res.retval == 0 + + def test_default_pool_fails_if_module_disabled(self, mock_get, mock_remote, mock_apply_misc, mock_release_name): + mock_release_name.return_value = "squid" + mock_get.return_value = {'modules': [], 'always_on_modules': {}} + + res = self.m._apply_nvmeof(pool=".nvmeof", group="mygroup") + + assert res.retval != 0 + assert "nvmeof module must be enabled to use .nvmeof pool" in res.stderr + mock_remote.assert_not_called() + mock_apply_misc.assert_not_called() + + def test_default_pool_creates_metadata_pool_if_module_enabled(self, mock_get, mock_remote, mock_apply_misc, mock_release_name): + mock_release_name.return_value = "squid" + mock_get.return_value = {'modules': ['nvmeof'], 'always_on_modules': {}} + mock_apply_misc.return_value = HandleCommandResult(retval=0, stdout="Success") + + res = self.m._apply_nvmeof(pool=".nvmeof", group="mygroup") + + mock_remote.assert_called_once_with('nvmeof', 'create_pool_if_not_exists') + mock_apply_misc.assert_called_once() + assert res.retval == 0 -- 2.47.3