machine.py: add support for supervisord service management

Signed-off-by: mirokymac <toufubomb@gmail.com>
This commit is contained in:
mirokymac 2022-11-18 23:37:33 +08:00 committed by GitHub
parent e628b243c2
commit 48f3bb2189
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 4 deletions

View File

@ -120,8 +120,9 @@ with system services such as systemd.
provider: systemd_dbus
# The provider implementation used to collect system service information
# and run service actions (ie: start, restart, stop). This can be "none",
# "systemd_dbus", or "systemd_cli". If the provider is set to "none" service
# action APIs will be disabled. The default is systemd_dbus.
# "supervisord", "systemd_dbus", or "systemd_cli". If the provider is set
# to "none" service action APIs will be disabled.
# The default is systemd_dbus.
sudo_password:
# The password for the linux user. When set Moonraker can run linux commands
# that require elevated permissions. This option accepts Jinja2 Templates,
@ -140,6 +141,10 @@ force_validation:
# By default Moonraker will not attempt to revalidate if a previous attempt
# at validation successfully completed. Setting this value to True will force
# Moonraker to perform validation. The default is False.
supervisord_config_path:
# Path to the superrvisord config file. In case for multi supervisord instance
# running on single machine, the default '/var/run/supervisord.sock' is occupied
# by other services.
```
!!! Note

View File

@ -108,7 +108,8 @@ class Machine:
providers: Dict[str, type] = {
"none": BaseProvider,
"systemd_cli": SystemdCliProvider,
"systemd_dbus": SystemdDbusProvider
"systemd_dbus": SystemdDbusProvider,
"supervisord": SuperisordProvider
}
self.provider_type = config.get('provider', 'systemd_dbus')
pclass = providers.get(self.provider_type)
@ -747,7 +748,7 @@ class BaseProvider:
action: str,
service_name: str
) -> None:
raise self.server.error("Serice Actions Not Available", 503)
raise self.server.error("Service Actions Not Available", 503)
async def check_virt_status(self) -> Dict[str, Any]:
return {
@ -1171,6 +1172,113 @@ class SystemdDbusProvider(BaseProvider):
processed[key] = val
return processed
# for docker klipper-moonraker image multi-service managing
# since in container, all command is launched by normal user,
# sudo_cmd is not needed.
class SuperisordProvider(BaseProvider):
def __init__(self, config: ConfigHelper) -> None:
super().__init__(config)
self.spv_conf: str = config.get("supervisord_config_path", "")
async def initialize(self) -> None:
for svc in ("klipper", "moonraker"):
self.available_services[svc] = {
'active_state': "none",
'sub_state': "unknown"
}
self.svc_cmd = self.shell_cmd.build_shell_command(
f"supervisorctl {self.spv_conf}"
f"status {' '.join(list(self.available_services.keys()))}"
)
await self._update_service_status(0, notify=True)
pstats: ProcStats = self.server.lookup_component('proc_stats')
pstats.register_stat_callback(self._update_service_status)
async def shutdown(self) -> None:
raise self.server.error(
"[machine]: Supervisord manager can not process SHUTDOWN."
"Please try KILL container or stop Supervisord via ssh terminal."
)
return
async def reboot(self) -> None:
raise self.server.error(
"[machine]: Supervisord manager can not process REBOOT."
"Please try KILL container or stop Supervisord via ssh terminal."
)
return
async def do_service_action(
self, action: str, service_name: str
) -> None:
# slow reaction for supervisord, timeout set to 6.0
await self._exec_command(
f"supervisorctl {self.spv_conf}"
f"{action} {service_name}",
timeout=6.
)
async def check_virt_status(self) -> Dict[str, Any]:
virt_id = virt_type = "none"
if (
os.path.exists("/.dockerenv") or
os.path.exists("/.dockerinit")
):
virt_id = "docker"
virt_type = "docker"
return {
'virt_type': virt_type,
'virt_identifier': virt_id
}
async def _exec_command(
self, command: str, tries: int = 1, timeout=2.
) -> str:
return await self.shell_cmd.exec_cmd(
command, proc_input=None, log_complete=False, retries=tries,
timeout=timeout
)
async def _update_service_status(self,
sequence: int,
notify: bool = True
) -> None:
if sequence % 2:
# Update every other sequence
return
svcs = list(self.available_services.keys())
try:
# slow reaction for supervisord, timeout set to 6.0
resp = await self.svc_cmd.run_with_response(
log_complete=False, timeout=6.
)
resp_l = resp.strip().split("\n") # drop lengend
for svc, state in zip(svcs, resp_l):
sub_state = state.split()[1].lower()
new_state: Dict[str, str] = {
'active_state': "active",
'sub_state': sub_state
}
if self.available_services[svc] != new_state:
self.available_services[svc] = new_state
if notify:
self.server.send_event(
"machine:service_state_changed",
{svc: new_state})
except Exception:
logging.exception("Error processing service state update")
# service files is defined since docker build.
# not needed to implete SV.
async def extract_service_info(
self,
service: str,
pid: int,
properties: List[str],
raw: bool = False
) -> Dict[str, Any]:
return {}
# Install validation
INSTALL_VERSION = 1