"""Base service manager plugin. Provides the shared start/stop/enable/disable logic that product-specific service managers (imav, im360) inherit from. """ import asyncio import logging from defence360agent import utils from defence360agent.contracts import messages, plugins logger = logging.getLogger(__name__) class BaseServiceManager(plugins.MessageSink): """Base service manager: start/stop services based on config changes. Subclasses populate ``_services`` (list of async check callables) and ``_units`` (dict of name → unitctl) in their ``__init__``. """ def __init__(self): self._lock = asyncio.Lock() self._services = [] self._units = {} async def _ensure_consistent_services_state(self): for service in self._services: await service() @plugins.expect(messages.MessageType.ConfigUpdate) async def on_config_update( self, message_ignored: messages.MessageType.ConfigUpdate ): async with self._lock: await self._ensure_consistent_services_state() @utils.log_error_and_ignore() async def _ensure_service_status( self, unitctl, service_name, should_be_running, reload=False ): is_running = await unitctl.is_active() if is_running is not should_be_running: if should_be_running: logger.info( "%s is enabled in the config but it is not" " running. Enabling it...", service_name, ) await unitctl.enable(now=True) logger.info("Enabled %s", service_name) else: logger.info( "%s is not enabled in the config but it is" " running. Disabling it...", service_name, ) await unitctl.disable(now=True) logger.info("Disabled %s", service_name) else: if is_running and reload: await unitctl.reload() logger.info( "Reloading %s after config update...", service_name )