NFS and keepalived are deployed without systemd enable (DISABLED_SERVICES).
When a host exits maintenance, re-enabling ceph-<fsid>.target only starts
enabled units, so these daemons remain stopped.
Fixes: https://tracker.ceph.com/issues/77954
Signed-off-by: Shweta Bhosale <Shweta.Bhosale1@ibm.com>
SYSCTL_DIR,
UNIT_DIR,
DAEMON_FAILED_ERROR,
+ DISABLED_SERVICES,
)
from cephadmlib.context import CephadmContext
from cephadmlib.context_getters import (
Highlight,
LogDestination,
)
-from cephadmlib.systemd import check_unit, check_units, terminate_service, enable_service
+from cephadmlib.systemd import (
+ check_unit,
+ check_units,
+ terminate_service,
+ enable_service,
+ start_disabled_services_after_maintenance_exit,
+)
from cephadmlib import systemd_unit
from cephadmlib.signals import send_signal_to_container_entrypoint
from cephadmlib import runscripts
if c:
# Disable automatic systemd enable for NFS and keepalived; the mgr
# starts them when appropriate (see cephadm serve / DISABLED_SERVICES).
- enable_daemon = daemon_type not in ('nfs', 'keepalived')
+ enable_daemon = daemon_type not in DISABLED_SERVICES
deploy_daemon_units(
ctx,
ident,
# return success here or host will be permanently stuck in maintenance mode
# as no daemons can be deployed so no systemd target will ever exist to disable.
if not target_exists(ctx):
- return 'skipped - systemd target not present on this host. Host removed from maintenance mode.'
- # exit maintenance request
- if not systemd_target_state(ctx, target):
+ msg = (
+ 'skipped - systemd target not present on this host. '
+ 'Host removed from maintenance mode.')
+ elif not systemd_target_state(ctx, target):
_out, _err, code = call(ctx,
['systemctl', 'enable', target],
verbosity=CallVerbosity.DEBUG)
if code:
logger.error(f'Failed to enable the {target} target')
return 'failed - unable to enable the target'
- else:
- # starting a target waits by default
- _out, _err, code = call(ctx,
- ['systemctl', 'start', target],
- verbosity=CallVerbosity.DEBUG)
- if code:
- logger.error(f'Failed to start the {target} target')
- return 'failed - unable to start the target'
- else:
- return f'success - systemd target {target} enabled and started'
- return f'success - systemd target {target} enabled and started'
+ _out, _err, code = call(ctx,
+ ['systemctl', 'start', target],
+ verbosity=CallVerbosity.DEBUG)
+ if code:
+ logger.error(f'Failed to start the {target} target')
+ return 'failed - unable to start the target'
+ msg = f'success - systemd target {target} enabled and started'
+ else:
+ msg = f'success - systemd target {target} enabled and started'
+
+ start_disabled_services_after_maintenance_exit(ctx)
+ return msg
@infer_fsid
GID_NOGROUP = 65534
DAEMON_FAILED_ERROR = 17
+# Daemons not systemd-enabled on deploy; mgr starts them when appropriate.
+DISABLED_SERVICES = ['nfs', 'keepalived']
+
# Host labels
ADMIN_LABEL = '_admin'
from typing import Tuple, List
+from .constants import DISABLED_SERVICES
from .context import CephadmContext
from .call_wrappers import call, CallVerbosity
+from .listing import DaemonEntry, daemons_matching
logger = logging.getLogger()
['systemctl', 'enable', '--now', service_name],
verbosity=CallVerbosity.DEBUG,
)
+
+
+def start_disabled_services_after_maintenance_exit(
+ ctx: CephadmContext,
+) -> None:
+ """Start nfs/keepalived units after host-maintenance exit."""
+ if not ctx.fsid:
+ return
+ for daemon_type in DISABLED_SERVICES:
+ for entry in daemons_matching(
+ ctx, fsid=ctx.fsid, daemon_type=daemon_type
+ ):
+ if isinstance(entry, DaemonEntry):
+ unit = entry.identity.unit_name
+ else:
+ unit = entry.status['systemd_unit']
+ _out, _err, code = call(
+ ctx,
+ ['systemctl', 'start', unit],
+ verbosity=CallVerbosity.DEBUG,
+ )
+ if code:
+ logger.warning(
+ 'Failed to start %s after maintenance exit: %s',
+ unit,
+ _err,
+ )
+ else:
+ logger.info('Started %s after maintenance exit', unit)
assert retval.startswith('failed')
+class TestMaintenanceExitDisabledServices(object):
+ fsid = '00000000-0000-0000-0000-000000000001'
+
+ @staticmethod
+ def _nfs_daemon_entry(fsid: str):
+ from cephadmlib.daemon_identity import DaemonIdentity
+ from cephadmlib.listing import DaemonEntry
+
+ identity = DaemonIdentity.from_name(fsid, 'nfs.foo.host1')
+ return DaemonEntry(
+ identity=identity,
+ status={
+ 'style': 'cephadm:v1',
+ 'name': 'nfs.foo.host1',
+ 'fsid': fsid,
+ 'systemd_unit': identity.unit_name,
+ },
+ data_dir='/var/lib/ceph',
+ )
+
+ @mock.patch('cephadmlib.systemd.call')
+ def test_start_disabled_services_after_maintenance_exit(self, _call):
+ from cephadmlib.systemd import start_disabled_services_after_maintenance_exit
+
+ ctx = _cephadm.CephadmContext()
+ ctx.fsid = self.fsid
+ entry = self._nfs_daemon_entry(self.fsid)
+ nfs_unit = entry.identity.unit_name
+ with mock.patch('cephadmlib.systemd.daemons_matching', return_value=[entry]):
+ _call.return_value = '', '', 0
+ start_disabled_services_after_maintenance_exit(ctx)
+ _call.assert_any_call(
+ ctx, ['systemctl', 'start', nfs_unit],
+ verbosity=_cephadm.CallVerbosity.DEBUG)
+
+ @mock.patch('os.listdir', return_value=[])
+ @mock.patch('cephadmlib.systemd.daemons_matching')
+ @mock.patch('cephadmlib.systemd.call')
+ @mock.patch('cephadm.call')
+ @mock.patch('cephadm.logger')
+ @mock.patch('cephadm.systemd_target_state')
+ @mock.patch('cephadm.target_exists')
+ def test_exit_starts_disabled_services(
+ self, _target_exists, _target_state, _logger, _cephadm_call,
+ _systemd_call, _daemons_matching, _listdir):
+ entry = self._nfs_daemon_entry(TestMaintenance.fsid)
+ nfs_unit = entry.identity.unit_name
+
+ def _matching(ctx, fsid=None, daemon_type=None):
+ if daemon_type == 'nfs':
+ return [entry]
+ return []
+
+ _daemons_matching.side_effect = _matching
+ _cephadm_call.side_effect = [('', '', 0), ('', '', 0)]
+ _systemd_call.return_value = '', '', 0
+ _target_state.return_value = False
+ _target_exists.return_value = True
+ ctx: _cephadm.CephadmContext = _cephadm.cephadm_init_ctx(
+ ['host-maintenance', 'exit', '--fsid', TestMaintenance.fsid])
+ ctx.container_engine = mock_podman()
+ retval = _cephadm.change_maintenance_mode(ctx)
+ assert retval.startswith('success')
+ _systemd_call.assert_any_call(
+ ctx, ['systemctl', 'start', nfs_unit],
+ verbosity=_cephadm.CallVerbosity.DEBUG)
+
+
class TestMonitoring(object):
@mock.patch('cephadmlib.daemons.monitoring.call')
def test_get_version_alertmanager(self, _call):