mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-12-24 20:35:55 +00:00
Reduce executor code for docker (#4438)
* Reduce executor code for docker * Fix pylint errors and move import/export image * Fix test and a couple other risky executor calls * Fix dataclass and return * Fix test case and add one for corrupt docker * Add some coverage * Undo changes to docker manager startup
This commit is contained in:
@@ -4,7 +4,7 @@ import asyncio
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
from docker.errors import DockerException
|
||||
from docker.errors import DockerException, NotFound
|
||||
import pytest
|
||||
from securetar import SecureTarFile
|
||||
|
||||
@@ -17,7 +17,7 @@ from supervisor.coresys import CoreSys
|
||||
from supervisor.docker.addon import DockerAddon
|
||||
from supervisor.docker.const import ContainerState
|
||||
from supervisor.docker.monitor import DockerContainerStateEvent
|
||||
from supervisor.exceptions import AddonsJobError, AudioUpdateError
|
||||
from supervisor.exceptions import AddonsError, AddonsJobError, AudioUpdateError
|
||||
from supervisor.store.repository import Repository
|
||||
from supervisor.utils.dt import utcnow
|
||||
|
||||
@@ -38,11 +38,6 @@ def _fire_test_event(coresys: CoreSys, name: str, state: ContainerState):
|
||||
)
|
||||
|
||||
|
||||
async def mock_current_state(state: ContainerState) -> ContainerState:
|
||||
"""Mock for current state method."""
|
||||
return state
|
||||
|
||||
|
||||
async def mock_stop() -> None:
|
||||
"""Mock for stop method."""
|
||||
|
||||
@@ -142,7 +137,7 @@ async def test_addon_watchdog(coresys: CoreSys, install_addon_ssh: Addon) -> Non
|
||||
Addon, "start"
|
||||
) as start, patch.object(DockerAddon, "current_state") as current_state:
|
||||
# Restart if it becomes unhealthy
|
||||
current_state.return_value = mock_current_state(ContainerState.UNHEALTHY)
|
||||
current_state.return_value = ContainerState.UNHEALTHY
|
||||
_fire_test_event(coresys, f"addon_{TEST_ADDON_SLUG}", ContainerState.UNHEALTHY)
|
||||
await asyncio.sleep(0)
|
||||
restart.assert_called_once()
|
||||
@@ -151,7 +146,7 @@ async def test_addon_watchdog(coresys: CoreSys, install_addon_ssh: Addon) -> Non
|
||||
restart.reset_mock()
|
||||
|
||||
# Rebuild if it failed
|
||||
current_state.return_value = mock_current_state(ContainerState.FAILED)
|
||||
current_state.return_value = ContainerState.FAILED
|
||||
with patch.object(DockerAddon, "stop", return_value=mock_stop()) as stop:
|
||||
_fire_test_event(coresys, f"addon_{TEST_ADDON_SLUG}", ContainerState.FAILED)
|
||||
await asyncio.sleep(0)
|
||||
@@ -162,14 +157,14 @@ async def test_addon_watchdog(coresys: CoreSys, install_addon_ssh: Addon) -> Non
|
||||
start.reset_mock()
|
||||
|
||||
# Do not process event if container state has changed since fired
|
||||
current_state.return_value = mock_current_state(ContainerState.HEALTHY)
|
||||
current_state.return_value = ContainerState.HEALTHY
|
||||
_fire_test_event(coresys, f"addon_{TEST_ADDON_SLUG}", ContainerState.FAILED)
|
||||
await asyncio.sleep(0)
|
||||
restart.assert_not_called()
|
||||
start.assert_not_called()
|
||||
|
||||
# Other addons ignored
|
||||
current_state.return_value = mock_current_state(ContainerState.UNHEALTHY)
|
||||
current_state.return_value = ContainerState.UNHEALTHY
|
||||
_fire_test_event(coresys, "addon_local_non_installed", ContainerState.UNHEALTHY)
|
||||
await asyncio.sleep(0)
|
||||
restart.assert_not_called()
|
||||
@@ -186,7 +181,7 @@ async def test_watchdog_on_stop(coresys: CoreSys, install_addon_ssh: Addon) -> N
|
||||
with patch.object(Addon, "restart") as restart, patch.object(
|
||||
DockerAddon,
|
||||
"current_state",
|
||||
return_value=mock_current_state(ContainerState.STOPPED),
|
||||
return_value=ContainerState.STOPPED,
|
||||
), patch.object(DockerAddon, "stop", return_value=mock_stop()):
|
||||
# Do not restart when addon stopped by user
|
||||
_fire_test_event(coresys, f"addon_{TEST_ADDON_SLUG}", ContainerState.RUNNING)
|
||||
@@ -248,7 +243,7 @@ async def test_watchdog_during_attach(
|
||||
), patch.object(DockerAddon, "attach"), patch.object(
|
||||
DockerAddon,
|
||||
"current_state",
|
||||
return_value=mock_current_state(ContainerState.STOPPED),
|
||||
return_value=ContainerState.STOPPED,
|
||||
):
|
||||
coresys.config.last_boot = coresys.hardware.helper.last_boot + boot_timedelta
|
||||
addon = Addon(coresys, store.slug)
|
||||
@@ -322,6 +317,7 @@ async def test_start(
|
||||
"""Test starting an addon without healthcheck."""
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
await install_addon_ssh.load()
|
||||
await asyncio.sleep(0)
|
||||
assert install_addon_ssh.state == AddonState.STOPPED
|
||||
|
||||
start_task = await install_addon_ssh.start()
|
||||
@@ -345,6 +341,7 @@ async def test_start_wait_healthcheck(
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
container.attrs["Config"] = {"Healthcheck": "exists"}
|
||||
await install_addon_ssh.load()
|
||||
await asyncio.sleep(0)
|
||||
assert install_addon_ssh.state == AddonState.STOPPED
|
||||
|
||||
start_task = asyncio.create_task(await install_addon_ssh.start())
|
||||
@@ -374,6 +371,7 @@ async def test_start_timeout(
|
||||
"""Test starting an addon times out while waiting."""
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
await install_addon_ssh.load()
|
||||
await asyncio.sleep(0)
|
||||
assert install_addon_ssh.state == AddonState.STOPPED
|
||||
|
||||
start_task = await install_addon_ssh.start()
|
||||
@@ -398,6 +396,7 @@ async def test_restart(
|
||||
"""Test restarting an addon."""
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
await install_addon_ssh.load()
|
||||
await asyncio.sleep(0)
|
||||
assert install_addon_ssh.state == AddonState.STOPPED
|
||||
|
||||
start_task = await install_addon_ssh.restart()
|
||||
@@ -426,6 +425,69 @@ async def test_backup(
|
||||
assert await install_addon_ssh.backup(tarfile) is None
|
||||
|
||||
|
||||
async def test_backup_with_pre_post_command(
|
||||
coresys: CoreSys,
|
||||
install_addon_ssh: Addon,
|
||||
container: MagicMock,
|
||||
tmp_supervisor_data,
|
||||
path_extern,
|
||||
) -> None:
|
||||
"""Test backing up an addon with pre and post command."""
|
||||
container.status = "running"
|
||||
container.exec_run.return_value = (0, None)
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
await install_addon_ssh.load()
|
||||
|
||||
tarfile = SecureTarFile(coresys.config.path_tmp / "test.tar.gz", "w")
|
||||
with patch.object(
|
||||
Addon, "backup_pre", new=PropertyMock(return_value="backup_pre")
|
||||
), patch.object(Addon, "backup_post", new=PropertyMock(return_value="backup_post")):
|
||||
assert await install_addon_ssh.backup(tarfile) is None
|
||||
|
||||
assert container.exec_run.call_count == 2
|
||||
assert container.exec_run.call_args_list[0].args[0] == "backup_pre"
|
||||
assert container.exec_run.call_args_list[1].args[0] == "backup_post"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"get_error,exception_on_exec",
|
||||
[
|
||||
(NotFound("missing"), False),
|
||||
(DockerException(), False),
|
||||
(None, True),
|
||||
(None, False),
|
||||
],
|
||||
)
|
||||
async def test_backup_with_pre_command_error(
|
||||
coresys: CoreSys,
|
||||
install_addon_ssh: Addon,
|
||||
container: MagicMock,
|
||||
get_error: DockerException | None,
|
||||
exception_on_exec: bool,
|
||||
tmp_supervisor_data,
|
||||
path_extern,
|
||||
) -> None:
|
||||
"""Test backing up an addon with error running pre command."""
|
||||
if get_error:
|
||||
coresys.docker.containers.get.side_effect = get_error
|
||||
|
||||
if exception_on_exec:
|
||||
container.exec_run.side_effect = DockerException()
|
||||
else:
|
||||
container.exec_run.return_value = (1, None)
|
||||
|
||||
install_addon_ssh.path_data.mkdir()
|
||||
await install_addon_ssh.load()
|
||||
|
||||
tarfile = SecureTarFile(coresys.config.path_tmp / "test.tar.gz", "w")
|
||||
with patch.object(DockerAddon, "is_running", return_value=True), patch.object(
|
||||
Addon, "backup_pre", new=PropertyMock(return_value="backup_pre")
|
||||
), pytest.raises(AddonsError):
|
||||
assert await install_addon_ssh.backup(tarfile) is None
|
||||
|
||||
assert not tarfile.path.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", ["running", "stopped"])
|
||||
async def test_backup_cold_mode(
|
||||
coresys: CoreSys,
|
||||
@@ -444,7 +506,7 @@ async def test_backup_cold_mode(
|
||||
with patch.object(
|
||||
AddonModel, "backup_mode", new=PropertyMock(return_value=AddonBackupMode.COLD)
|
||||
), patch.object(
|
||||
DockerAddon, "_is_running", side_effect=[status == "running", False, False]
|
||||
DockerAddon, "is_running", side_effect=[status == "running", False, False]
|
||||
):
|
||||
start_task = await install_addon_ssh.backup(tarfile)
|
||||
|
||||
@@ -465,7 +527,7 @@ async def test_restore(
|
||||
await install_addon_ssh.load()
|
||||
|
||||
tarfile = SecureTarFile(get_fixture_path(f"backup_local_ssh_{status}.tar.gz"), "r")
|
||||
with patch.object(DockerAddon, "_is_running", return_value=False), patch.object(
|
||||
with patch.object(DockerAddon, "is_running", return_value=False), patch.object(
|
||||
CpuArch, "supported", new=PropertyMock(return_value=["aarch64"])
|
||||
):
|
||||
start_task = await coresys.addons.restore(TEST_ADDON_SLUG, tarfile)
|
||||
@@ -482,6 +544,7 @@ async def test_start_when_running(
|
||||
"""Test starting an addon without healthcheck."""
|
||||
container.status = "running"
|
||||
await install_addon_ssh.load()
|
||||
await asyncio.sleep(0)
|
||||
assert install_addon_ssh.state == AddonState.STARTED
|
||||
|
||||
caplog.clear()
|
||||
|
||||
Reference in New Issue
Block a user