From c8042a57006d5604250b8dc3610bb94f78019781 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Sat, 26 Mar 2022 15:21:02 -0400 Subject: [PATCH] update_manager: add support for extensions While use of "unofficial" klippy extras an moonraker components is not officially supported, there is no harm in facilitating updates for these extensions in the update manager. This adds configuration which will restart either moonraker or klipper after an extension is updated. Signed-off-by: Eric Callahan --- .../components/update_manager/app_deploy.py | 59 ++++++++++++++----- .../update_manager/update_manager.py | 28 +++++++-- 2 files changed, 67 insertions(+), 20 deletions(-) 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