diff --git a/moonraker/components/update_manager/app_deploy.py b/moonraker/components/update_manager/app_deploy.py index 2ee1a6b..6c387ae 100644 --- a/moonraker/components/update_manager/app_deploy.py +++ b/moonraker/components/update_manager/app_deploy.py @@ -9,6 +9,7 @@ import pathlib import shutil import hashlib import json +import logging from .base_deploy import BaseDeploy # Annotation imports @@ -79,8 +80,29 @@ class AppDeploy(BaseDeploy): self.venv_args = config.get('venv_args', None) self.info_tags: List[str] = config.getlist("info_tags", []) - self.is_service = config.getboolean("is_system_service", True) - + self.managed_services: List[str] = [] + svc_default = [] + if config.getboolean("is_system_service", True): + svc_default.append(self.name) + svc_choices = [self.name, "klipper", "moonraker"] + services: List[str] = config.getlist( + "managed_services", svc_default, separator=None + ) + for svc in services: + if svc not in svc_choices: + raw = " ".join(services) + self.server.add_warning( + f"[{config.get_name()}]: Option 'restart_action: {raw}' " + f"contains an invalid value '{svc}'. All values must be " + f"one of the following choices: {svc_choices}" + ) + break + for svc in svc_choices: + if svc in services and svc not in self.managed_services: + self.managed_services.append(svc) + logging.debug( + f"Extension {self.name} managed services: {self.managed_services}" + ) # We need to fetch all potential options for an Application. Not # all options apply to each subtype, however we can't limit the # options in children if we want to switch between channels and @@ -167,25 +189,30 @@ class AppDeploy(BaseDeploy): raise NotImplementedError async def restart_service(self): - if not self.is_service: - self.notify_status( - "Application not configured as service, skipping restart") + if not self.managed_services: return - if self.name == "moonraker": - # Launch restart async so the request can return - # before the server restarts - event_loop = self.server.get_event_loop() - event_loop.delay_callback(.1, self._do_restart) - else: - await self._do_restart() + is_full = self.cmd_helper.is_full_update() + for svc in self.managed_services: + if is_full and svc != self.name: + self.notify_status(f"Service {svc} restart postponed...") + self.cmd_helper.add_pending_restart(svc) + continue + self.cmd_helper.remove_pending_restart(svc) + self.notify_status(f"Restarting service {svc}...") + if svc == "moonraker": + # Launch restart async so the request can return + # before the server restarts + event_loop = self.server.get_event_loop() + event_loop.delay_callback(.1, self._do_restart, svc) + else: + await self._do_restart(svc) - async def _do_restart(self) -> None: - self.notify_status("Restarting Service...") + async def _do_restart(self, svc_name: str) -> None: machine: Machine = self.server.lookup_component("machine") try: - await machine.do_service_action("restart", self.name) + await machine.do_service_action("restart", svc_name) except Exception: - if self.name == "moonraker": + if svc_name == "moonraker": # We will always get an error when restarting moonraker # from within the child process, so ignore it return diff --git a/moonraker/components/update_manager/update_manager.py b/moonraker/components/update_manager/update_manager.py index 65f3339..85362bc 100644 --- a/moonraker/components/update_manager/update_manager.py +++ b/moonraker/components/update_manager/update_manager.py @@ -27,6 +27,7 @@ from typing import ( Any, Awaitable, Optional, + Set, Tuple, Type, Union, @@ -324,10 +325,13 @@ class UpdateManager: kupdater = self.updaters.get('klipper') if isinstance(kupdater, AppDeploy): self.klippy_identified_evt = asyncio.Event() - klippy_updated = True + check_restart = True if not await self._check_need_reinstall(app_name): - klippy_updated = await kupdater.update() - if klippy_updated: + check_restart = await kupdater.update() + if self.cmd_helper.needs_service_restart(app_name): + await kupdater.restart_service() + check_restart = True + if check_restart: self.cmd_helper.notify_update_response( "Waiting for Klippy to reconnect (this may take" " up to 2 minutes)...") @@ -344,8 +348,11 @@ class UpdateManager: # Update Moonraker app_name = 'moonraker' + moon_updater = cast(AppDeploy, self.updaters["moonraker"]) if not await self._check_need_reinstall(app_name): - await self.updaters['moonraker'].update() + await moon_updater.update() + if self.cmd_helper.needs_service_restart(app_name): + await moon_updater.restart_service() self.cmd_helper.set_full_complete(True) self.cmd_helper.notify_update_response( "Full Update Complete", is_complete=True) @@ -490,6 +497,7 @@ class CommandHelper: self.cur_update_id: Optional[int] = None self.full_update: bool = False self.full_complete: bool = False + self.pending_service_restarts: Set[str] = set() def get_server(self) -> Server: return self.server @@ -511,10 +519,18 @@ class CommandHelper: self.cur_update_id = uid self.full_update = app == "full" self.full_complete = not self.full_update + self.pending_service_restarts.clear() def is_full_update(self) -> bool: return self.full_update + def add_pending_restart(self, svc_name: str) -> None: + self.pending_service_restarts.add(svc_name) + + def remove_pending_restart(self, svc_name: str) -> None: + if svc_name in self.pending_service_restarts: + self.pending_service_restarts.remove(svc_name) + def set_full_complete(self, complete: bool = False): self.full_complete = complete @@ -522,6 +538,10 @@ class CommandHelper: self.cur_update_app = self.cur_update_id = None self.full_update = False self.full_complete = False + self.pending_service_restarts.clear() + + def needs_service_restart(self, svc_name: str) -> bool: + return svc_name in self.pending_service_restarts def is_app_updating(self, app_name: str) -> bool: return self.cur_update_app == app_name