From d47b154c2a128dee3661edb3a50954a354cf3af5 Mon Sep 17 00:00:00 2001 From: Nazarii Hnydyn Date: Tue, 10 Feb 2026 17:28:22 +0200 Subject: [PATCH] [spm]: Handle enabled/disabled action for generated/transient units gracefully Signed-off-by: Nazarii Hnydyn --- sonic_package_manager/logger.py | 10 ++++++--- sonic_package_manager/manager.py | 37 ++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/sonic_package_manager/logger.py b/sonic_package_manager/logger.py index 3d5e06d35f2..4dd652ff9bb 100644 --- a/sonic_package_manager/logger.py +++ b/sonic_package_manager/logger.py @@ -2,10 +2,11 @@ """ Logger for sonic-package-manager. """ -import logging.handlers - +import logging import click_log +from logging.handlers import SysLogHandler + class Formatter(click_log.ColorFormatter): """ Click logging formatter. """ @@ -25,5 +26,8 @@ class Formatter(click_log.ColorFormatter): click_handler = click_log.ClickHandler() click_handler.formatter = Formatter() +syslog_handler = SysLogHandler() +syslog_handler.setFormatter(logging.Formatter('%(name)s: %(message)s')) + log.addHandler(click_handler) -log.addHandler(logging.handlers.SysLogHandler()) +log.addHandler(syslog_handler) diff --git a/sonic_package_manager/manager.py b/sonic_package_manager/manager.py index b6a3be50c36..940a93ad421 100644 --- a/sonic_package_manager/manager.py +++ b/sonic_package_manager/manager.py @@ -4,6 +4,7 @@ import functools import os import pkgutil +import subprocess import tempfile import yang as ly from inspect import signature @@ -1023,6 +1024,31 @@ def _start_feature(self, package: Package): self._systemctl_action(package, 'enable') self._systemctl_action(package, 'start') + @staticmethod + def _systemctl_is_generated_or_transient(unit_name: str) -> bool: + """Returns True if systemd considers the unit generated/transient. + + On some platforms, SONiC's systemd generator + may materialize unit files under /run/systemd/generator. + Such units cannot be enabled/disabled persistently. + """ + try: + proc = subprocess.run( + ['systemctl', 'is-enabled', unit_name], + capture_output=True, + text=True, + timeout=15, + ) + except subprocess.TimeoutExpired as err: + log.warning(f'systemctl is-enabled timed out for unit {unit_name}: timeout={err.timeout}s') + return False + except Exception as err: + log.error(f'systemctl is-enabled failed for unit {unit_name}: {err}') + return False + + state = (proc.stdout or '').strip().lower() + return state in ('generated', 'transient') + def _systemctl_action(self, package: Package, action: str): """ Execute systemctl action for a service. """ @@ -1034,10 +1060,17 @@ def _systemctl_action(self, package: Package, action: str): single_instance = host_service or (asic_service and not self.is_multi_npu) multi_instance = asic_service and self.is_multi_npu if single_instance: - run_command(['systemctl', action, name]) + if action in ('enable', 'disable') and self._systemctl_is_generated_or_transient(name): + log.warning(f'Skipping systemctl {action} for generated/transient unit {name}') + else: + run_command(['systemctl', action, name]) if multi_instance: for npu in range(self.num_npus): - run_command(['systemctl', action, f'{name}@{npu}']) + inst_name = f'{name}@{npu}' + if action in ('enable', 'disable') and self._systemctl_is_generated_or_transient(inst_name): + log.warning(f'Skipping systemctl {action} for generated/transient unit {inst_name}') + else: + run_command(['systemctl', action, inst_name]) def _disable_feature(self, package: Package, block: bool = True): """ Stops the feature and blocks till operation is finished if