mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-14 04:36:31 +00:00
Allow restarting core in safe mode (#5017)
This commit is contained in:
parent
b4a79bd068
commit
06513e88c6
@ -53,6 +53,7 @@ ATTR_PANEL_PATH = "panel_path"
|
|||||||
ATTR_REMOVABLE = "removable"
|
ATTR_REMOVABLE = "removable"
|
||||||
ATTR_REMOVE_CONFIG = "remove_config"
|
ATTR_REMOVE_CONFIG = "remove_config"
|
||||||
ATTR_REVISION = "revision"
|
ATTR_REVISION = "revision"
|
||||||
|
ATTR_SAFE_MODE = "safe_mode"
|
||||||
ATTR_SEAT = "seat"
|
ATTR_SEAT = "seat"
|
||||||
ATTR_SIGNED = "signed"
|
ATTR_SIGNED = "signed"
|
||||||
ATTR_STARTUP_TIME = "startup_time"
|
ATTR_STARTUP_TIME = "startup_time"
|
||||||
|
@ -36,6 +36,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..validate import docker_image, network_port, version_tag
|
from ..validate import docker_image, network_port, version_tag
|
||||||
|
from .const import ATTR_SAFE_MODE
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -62,6 +63,12 @@ SCHEMA_UPDATE = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SCHEMA_RESTART = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_SAFE_MODE, default=False): vol.Boolean(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIHomeAssistant(CoreSysAttributes):
|
class APIHomeAssistant(CoreSysAttributes):
|
||||||
"""Handle RESTful API for Home Assistant functions."""
|
"""Handle RESTful API for Home Assistant functions."""
|
||||||
@ -166,9 +173,13 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
return asyncio.shield(self.sys_homeassistant.core.start())
|
return asyncio.shield(self.sys_homeassistant.core.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
async def restart(self, request: web.Request) -> None:
|
||||||
"""Restart Home Assistant."""
|
"""Restart Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.core.restart())
|
body = await api_validate(SCHEMA_RESTART, request)
|
||||||
|
|
||||||
|
await asyncio.shield(
|
||||||
|
self.sys_homeassistant.core.restart(safe_mode=body[ATTR_SAFE_MODE])
|
||||||
|
)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Constants for homeassistant."""
|
"""Constants for homeassistant."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
from pathlib import PurePath
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ WATCHDOG_RETRY_SECONDS = 10
|
|||||||
WATCHDOG_MAX_ATTEMPTS = 5
|
WATCHDOG_MAX_ATTEMPTS = 5
|
||||||
WATCHDOG_THROTTLE_PERIOD = timedelta(minutes=30)
|
WATCHDOG_THROTTLE_PERIOD = timedelta(minutes=30)
|
||||||
WATCHDOG_THROTTLE_MAX_CALLS = 10
|
WATCHDOG_THROTTLE_MAX_CALLS = 10
|
||||||
|
SAFE_MODE_FILENAME = PurePath("safe-mode")
|
||||||
|
|
||||||
CLOSING_STATES = [
|
CLOSING_STATES = [
|
||||||
CoreState.SHUTDOWN,
|
CoreState.SHUTDOWN,
|
||||||
|
@ -35,6 +35,7 @@ from ..utils import convert_to_ascii
|
|||||||
from ..utils.sentry import capture_exception
|
from ..utils.sentry import capture_exception
|
||||||
from .const import (
|
from .const import (
|
||||||
LANDINGPAGE,
|
LANDINGPAGE,
|
||||||
|
SAFE_MODE_FILENAME,
|
||||||
WATCHDOG_MAX_ATTEMPTS,
|
WATCHDOG_MAX_ATTEMPTS,
|
||||||
WATCHDOG_RETRY_SECONDS,
|
WATCHDOG_RETRY_SECONDS,
|
||||||
WATCHDOG_THROTTLE_MAX_CALLS,
|
WATCHDOG_THROTTLE_MAX_CALLS,
|
||||||
@ -362,8 +363,14 @@ class HomeAssistantCore(JobGroup):
|
|||||||
limit=JobExecutionLimit.GROUP_ONCE,
|
limit=JobExecutionLimit.GROUP_ONCE,
|
||||||
on_condition=HomeAssistantJobError,
|
on_condition=HomeAssistantJobError,
|
||||||
)
|
)
|
||||||
async def restart(self) -> None:
|
async def restart(self, *, safe_mode: bool = False) -> None:
|
||||||
"""Restart Home Assistant Docker."""
|
"""Restart Home Assistant Docker."""
|
||||||
|
# Create safe mode marker file if necessary
|
||||||
|
if safe_mode:
|
||||||
|
await self.sys_run_in_executor(
|
||||||
|
(self.sys_config.path_homeassistant / SAFE_MODE_FILENAME).touch
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.instance.restart()
|
await self.instance.restart()
|
||||||
except DockerError as err:
|
except DockerError as err:
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
"""Test homeassistant api."""
|
"""Test homeassistant api."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
|
from supervisor.homeassistant.core import HomeAssistantCore
|
||||||
from supervisor.homeassistant.module import HomeAssistant
|
from supervisor.homeassistant.module import HomeAssistant
|
||||||
|
|
||||||
from tests.api import common_test_api_advanced_logs
|
from tests.api import common_test_api_advanced_logs
|
||||||
@ -92,3 +94,24 @@ async def test_api_set_image(api_client: TestClient, coresys: CoreSys):
|
|||||||
coresys.homeassistant.image == "ghcr.io/home-assistant/qemux86-64-homeassistant"
|
coresys.homeassistant.image == "ghcr.io/home-assistant/qemux86-64-homeassistant"
|
||||||
)
|
)
|
||||||
assert coresys.homeassistant.override_image is False
|
assert coresys.homeassistant.override_image is False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_api_restart(
|
||||||
|
api_client: TestClient,
|
||||||
|
container: MagicMock,
|
||||||
|
tmp_supervisor_data: Path,
|
||||||
|
):
|
||||||
|
"""Test restarting homeassistant."""
|
||||||
|
safe_mode_marker = tmp_supervisor_data / "homeassistant" / "safe-mode"
|
||||||
|
|
||||||
|
with patch.object(HomeAssistantCore, "_block_till_run"):
|
||||||
|
await api_client.post("/homeassistant/restart")
|
||||||
|
|
||||||
|
container.restart.assert_called_once()
|
||||||
|
assert not safe_mode_marker.exists()
|
||||||
|
|
||||||
|
with patch.object(HomeAssistantCore, "_block_till_run"):
|
||||||
|
await api_client.post("/homeassistant/restart", json={"safe_mode": True})
|
||||||
|
|
||||||
|
assert container.restart.call_count == 2
|
||||||
|
assert safe_mode_marker.exists()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user