From b4363bb25e2f7c06193b1d7701adb5cc607916f1 Mon Sep 17 00:00:00 2001 From: Lumir Sliva <61183145+lumir-sliva@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:14:21 +0200 Subject: [PATCH] mgr/cephadm: don't treat unknown daemon state as error MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit cephadm ls may transiently report state='unknown' for daemons that are still starting up. The state mapping in _refresh_host_daemons mapped 'unknown' to DaemonDescriptionStatus.error, which caused update_failed_daemon_health_check to fire CEPHADM_FAILED_DAEMON for daemons that were simply mid-startup. The warning clears on the next refresh once the daemon reaches a real state, but it causes spurious QA failures across many test suites. Map 'unknown' to DaemonDescriptionStatus.unknown instead — the enum value already exists and is handled by to_str() and the dashboard. get_error_daemons() only checks for .error, so unknown daemons no longer trigger the health warning. Signed-off-by: Lumir Sliva <61183145+lumir-sliva@users.noreply.github.com> --- src/pybind/mgr/cephadm/module.py | 2 +- src/pybind/mgr/cephadm/tests/test_cephadm.py | 42 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 5fcc4afac0c..0d0c8988af8 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -1123,7 +1123,7 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule): 'running': DaemonDescriptionStatus.running, 'stopped': DaemonDescriptionStatus.stopped, 'error': DaemonDescriptionStatus.error, - 'unknown': DaemonDescriptionStatus.error, + 'unknown': DaemonDescriptionStatus.unknown, }[d['state']] cached_dd = None diff --git a/src/pybind/mgr/cephadm/tests/test_cephadm.py b/src/pybind/mgr/cephadm/tests/test_cephadm.py index e2c5f5bc4bd..f56a2a7619c 100644 --- a/src/pybind/mgr/cephadm/tests/test_cephadm.py +++ b/src/pybind/mgr/cephadm/tests/test_cephadm.py @@ -419,6 +419,48 @@ class TestCephadm(object): dds = wait(cephadm_module, cephadm_module.list_daemons()) assert {d.name() for d in dds} == {'rgw.myrgw.foobar', 'haproxy.test.bar'} + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm( + json.dumps([ + dict( + name='rgw.myrgw.running', + style='cephadm', + fsid='fsid', + container_id='container_id', + state='running', + ), + dict( + name='rgw.myrgw.unknown', + style='cephadm', + fsid='fsid', + container_id='container_id', + state='unknown', + ), + dict( + name='rgw.myrgw.error', + style='cephadm', + fsid='fsid', + container_id='container_id', + state='error', + ), + ]) + )) + def test_unknown_state_not_error(self, cephadm_module: CephadmOrchestrator): + # Verify that daemons with state='unknown' from cephadm ls are not + # treated as errors and do not trigger CEPHADM_FAILED_DAEMON. + # See https://tracker.ceph.com/issues/65728 + cephadm_module.service_cache_timeout = 10 + with with_host(cephadm_module, 'myhost'): + CephadmServe(cephadm_module)._refresh_host_daemons('myhost') + dds = {d.name(): d for d in wait(cephadm_module, cephadm_module.list_daemons())} + # unknown should map to DaemonDescriptionStatus.unknown, not error + assert dds['rgw.myrgw.unknown'].status == DaemonDescriptionStatus.unknown + assert dds['rgw.myrgw.error'].status == DaemonDescriptionStatus.error + assert dds['rgw.myrgw.running'].status == DaemonDescriptionStatus.running + # only the error daemon should appear in get_error_daemons + error_names = {d.name() for d in cephadm_module.cache.get_error_daemons()} + assert 'rgw.myrgw.error' in error_names + assert 'rgw.myrgw.unknown' not in error_names + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) def test_daemon_action(self, cephadm_module: CephadmOrchestrator): cephadm_module.service_cache_timeout = 10 -- 2.47.3