Soft restart supervisor (#2281)

* Add softrestart to supervisor

* decouble

* adjust logger

* make sure it need run

* Use job condition

* add more job running
This commit is contained in:
Pascal Vizeli 2020-11-21 12:48:16 +01:00 committed by GitHub
parent 1427e0ae96
commit 7e94537e36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 67 additions and 4 deletions

View File

@ -1,5 +1,8 @@
#!/usr/bin/execlineb -S0
#!/usr/bin/execlineb -S1
# ==============================================================================
# Take down the S6 supervision tree when Supervisor fails
# ==============================================================================
if { s6-test ${1} -ne 100 }
if { s6-test ${1} -ne 256 }
redirfd -w 2 /dev/null s6-svscanctl -t /var/run/s6/services

View File

@ -60,4 +60,4 @@ if __name__ == "__main__":
loop.close()
_LOGGER.info("Closing Supervisor")
sys.exit(0)
sys.exit(coresys.core.exit_code)

View File

@ -251,6 +251,7 @@ class RestAPI(CoreSysAttributes):
web.get("/supervisor/logs", api_supervisor.logs),
web.post("/supervisor/update", api_supervisor.update),
web.post("/supervisor/reload", api_supervisor.reload),
web.post("/supervisor/restart", api_supervisor.restart),
web.post("/supervisor/options", api_supervisor.options),
web.post("/supervisor/repair", api_supervisor.repair),
]

View File

@ -195,6 +195,11 @@ class APISupervisor(CoreSysAttributes):
"""Try to repair the local setup / overlayfs."""
return asyncio.shield(self.sys_core.repair())
@api_process
def restart(self, request: web.Request) -> Awaitable[None]:
"""Soft restart Supervisor."""
return asyncio.shield(self.sys_supervisor.restart())
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request: web.Request) -> Awaitable[bytes]:
"""Return supervisor Docker logs."""

View File

@ -26,6 +26,7 @@ class Core(CoreSysAttributes):
"""Initialize Supervisor object."""
self.coresys: CoreSys = coresys
self._state: Optional[CoreState] = None
self.exit_code: int = 0
@property
def state(self) -> CoreState:
@ -257,7 +258,7 @@ class Core(CoreSysAttributes):
_LOGGER.warning("Stage 2: Force Shutdown!")
self.state = CoreState.CLOSE
_LOGGER.info("Supervisor is down")
_LOGGER.info("Supervisor is down - %d", self.exit_code)
self.sys_loop.stop()
async def shutdown(self):

View File

@ -20,6 +20,7 @@ class JobCondition(str, Enum):
HEALTHY = "healthy"
INTERNET_SYSTEM = "internet_system"
INTERNET_HOST = "internet_host"
RUNNING = "running"
class Job:
@ -83,6 +84,14 @@ class Job:
)
return False
if JobCondition.RUNNING in self.conditions:
if self._coresys.core.state != CoreState.RUNNING:
_LOGGER.warning(
"'%s' blocked from execution, system is not running",
self._method.__qualname__,
)
return False
if JobCondition.FREE_SPACE in self.conditions:
free_space = self._coresys.host.info.free_space
if free_space < MINIMUM_FREE_SPACE_THRESHOLD:

View File

@ -125,6 +125,7 @@ class Tasks(CoreSysAttributes):
JobCondition.HEALTHY,
JobCondition.FREE_SPACE,
JobCondition.INTERNET_HOST,
JobCondition.RUNNING,
]
)
async def _update_addons(self):
@ -150,7 +151,13 @@ class Tasks(CoreSysAttributes):
except AddonsError:
_LOGGER.error("Can't auto update Add-on %s", addon.slug)
@Job(conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_HOST])
@Job(
conditions=[
JobCondition.FREE_SPACE,
JobCondition.INTERNET_HOST,
JobCondition.RUNNING,
]
)
async def _update_supervisor(self):
"""Check and run update of Supervisor Supervisor."""
if not self.sys_supervisor.need_update:
@ -231,6 +238,7 @@ class Tasks(CoreSysAttributes):
finally:
self._cache[HASS_WATCHDOG_API] = 0
@Job(conditions=JobCondition.RUNNING)
async def _update_cli(self):
"""Check and run update of cli."""
if not self.sys_plugins.cli.need_update:
@ -241,6 +249,7 @@ class Tasks(CoreSysAttributes):
)
await self.sys_plugins.cli.update()
@Job(conditions=JobCondition.RUNNING)
async def _update_dns(self):
"""Check and run update of CoreDNS plugin."""
if not self.sys_plugins.dns.need_update:
@ -252,6 +261,7 @@ class Tasks(CoreSysAttributes):
)
await self.sys_plugins.dns.update()
@Job(conditions=JobCondition.RUNNING)
async def _update_audio(self):
"""Check and run update of PulseAudio plugin."""
if not self.sys_plugins.audio.need_update:
@ -263,6 +273,7 @@ class Tasks(CoreSysAttributes):
)
await self.sys_plugins.audio.update()
@Job(conditions=JobCondition.RUNNING)
async def _update_observer(self):
"""Check and run update of Observer plugin."""
if not self.sys_plugins.observer.need_update:
@ -274,6 +285,7 @@ class Tasks(CoreSysAttributes):
)
await self.sys_plugins.observer.update()
@Job(conditions=JobCondition.RUNNING)
async def _update_multicast(self):
"""Check and run update of multicast."""
if not self.sys_plugins.multicast.need_update:

View File

@ -11,6 +11,8 @@ import aiohttp
from aiohttp.client_exceptions import ClientError
from packaging.version import parse as pkg_parse
from supervisor.jobs.decorator import Job, JobCondition
from .const import SUPERVISOR_VERSION, URL_HASSIO_APPARMOR
from .coresys import CoreSys, CoreSysAttributes
from .docker.stats import DockerStats
@ -144,6 +146,12 @@ class Supervisor(CoreSysAttributes):
await self.update_apparmor()
self.sys_create_task(self.sys_core.stop())
@Job(conditions=[JobCondition.RUNNING])
async def restart(self) -> None:
"""Restart Supervisor soft."""
self.sys_core.exit_code = 100
self.sys_create_task(self.sys_core.stop())
@property
def in_progress(self) -> bool:
"""Return True if a task is in progress."""

View File

@ -181,3 +181,27 @@ async def test_exception_not_handle(coresys: CoreSys):
with pytest.raises(JobException):
assert await test.execute()
async def test_running(coresys: CoreSys):
"""Test the running decorator."""
class TestClass:
"""Test class."""
def __init__(self, coresys: CoreSys):
"""Initialize the test class."""
self.coresys = coresys
@Job(conditions=[JobCondition.RUNNING])
async def execute(self):
"""Execute the class method."""
return True
test = TestClass(coresys)
coresys.core.state = CoreState.RUNNING
assert await test.execute()
coresys.core.state = CoreState.FREEZE
assert not await test.execute()