1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-05-08 08:58:31 +01:00

Make issue for problem with config for containers (#4317)

* Make issue for problem with config for containers

* Mount propagation in tests

* Fixes from rebase and feedback
This commit is contained in:
Mike Degatano
2023-05-30 13:25:38 -04:00
committed by GitHub
parent 0df19cee91
commit 841f68c175
21 changed files with 922 additions and 25 deletions
@@ -0,0 +1,141 @@
"""Test check Docker Config."""
from unittest.mock import MagicMock, patch
from supervisor.addons.addon import Addon
from supervisor.const import CoreState
from supervisor.coresys import CoreSys
from supervisor.docker.interface import DockerInterface
from supervisor.docker.manager import DockerAPI
from supervisor.resolution.checks.docker_config import CheckDockerConfig
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.data import Issue, Suggestion
from tests.conftest import mock_async_return_true
def _make_mock_container_get(bad_config_names: list[str]):
"""Make mock of container get."""
def mock_container_get(name):
out = MagicMock()
out.status = "running"
out.attrs = {"State": {}, "Mounts": []}
if name in bad_config_names:
out.attrs["Mounts"].append(
{
"Type": "bind",
"Source": "/mnt/data/supervisor/media",
"Destination": "/media",
"Mode": "rw",
"RW": True,
"Propagation": "rprivate",
}
)
return out
return mock_container_get
async def test_base(coresys: CoreSys):
"""Test check basics."""
docker_config = CheckDockerConfig(coresys)
assert docker_config.slug == "docker_config"
assert docker_config.enabled
async def test_check(docker: DockerAPI, coresys: CoreSys, install_addon_ssh: Addon):
"""Test check reports issue when containers have incorrect config."""
docker.containers.get = _make_mock_container_get(
["homeassistant", "hassio_audio", "addon_local_ssh"]
)
with patch.object(DockerInterface, "is_running", new=mock_async_return_true):
await coresys.plugins.load()
await coresys.homeassistant.load()
await coresys.addons.load()
docker_config = CheckDockerConfig(coresys)
coresys.core.state = CoreState.RUNNING
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
# An issue and suggestion is added per container with a config issue
await docker_config.run_check()
assert len(coresys.resolution.issues) == 4
assert Issue(IssueType.DOCKER_CONFIG, ContextType.CORE) in coresys.resolution.issues
assert (
Issue(IssueType.DOCKER_CONFIG, ContextType.ADDON, reference="local_ssh")
in coresys.resolution.issues
)
assert (
Issue(IssueType.DOCKER_CONFIG, ContextType.PLUGIN, reference="audio")
in coresys.resolution.issues
)
assert (
Issue(IssueType.DOCKER_CONFIG, ContextType.SYSTEM) in coresys.resolution.issues
)
assert len(coresys.resolution.suggestions) == 4
assert (
Suggestion(SuggestionType.EXECUTE_REBUILD, ContextType.CORE)
in coresys.resolution.suggestions
)
assert (
Suggestion(
SuggestionType.EXECUTE_REBUILD, ContextType.PLUGIN, reference="audio"
)
in coresys.resolution.suggestions
)
assert (
Suggestion(
SuggestionType.EXECUTE_REBUILD, ContextType.ADDON, reference="local_ssh"
)
in coresys.resolution.suggestions
)
assert (
Suggestion(SuggestionType.EXECUTE_REBUILD, ContextType.SYSTEM)
in coresys.resolution.suggestions
)
assert await docker_config.approve_check()
# IF config issue is resolved, all issues are removed except the main one. Which will be removed if check isn't approved
docker.containers.get = _make_mock_container_get([])
with patch.object(DockerInterface, "is_running", new=mock_async_return_true):
await coresys.plugins.load()
await coresys.homeassistant.load()
await coresys.addons.load()
assert not await docker_config.approve_check()
assert len(coresys.resolution.issues) == 1
assert len(coresys.resolution.suggestions) == 1
assert (
Issue(IssueType.DOCKER_CONFIG, ContextType.SYSTEM) in coresys.resolution.issues
)
async def test_did_run(coresys: CoreSys):
"""Test that the check ran as expected."""
docker_config = CheckDockerConfig(coresys)
should_run = docker_config.states
should_not_run = [state for state in CoreState if state not in should_run]
assert len(should_run) != 0
assert len(should_not_run) != 0
with patch(
"supervisor.resolution.checks.docker_config.CheckDockerConfig.run_check",
return_value=None,
) as check:
for state in should_run:
coresys.core.state = state
await docker_config()
check.assert_called_once()
check.reset_mock()
for state in should_not_run:
coresys.core.state = state
await docker_config()
check.assert_not_called()
check.reset_mock()
@@ -0,0 +1,118 @@
"""Test fixup core execute rebuild."""
from unittest.mock import MagicMock, patch
from docker.errors import NotFound
import pytest
from supervisor.addons.addon import Addon
from supervisor.coresys import CoreSys
from supervisor.docker.interface import DockerInterface
from supervisor.docker.manager import DockerAPI
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.fixups.addon_execute_rebuild import FixupAddonExecuteRebuild
def make_mock_container_get(status: str):
"""Make mock of container get."""
out = MagicMock()
out.status = status
out.attrs = {"State": {"ExitCode": 0}, "Mounts": []}
def mock_container_get(name):
return out
return mock_container_get
async def test_fixup(docker: DockerAPI, coresys: CoreSys, install_addon_ssh: Addon):
"""Test fixup rebuilds addon's container."""
docker.containers.get = make_mock_container_get("running")
addon_execute_rebuild = FixupAddonExecuteRebuild(coresys)
assert addon_execute_rebuild.auto is False
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(Addon, "restart") as restart:
await addon_execute_rebuild()
restart.assert_called_once()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
async def test_fixup_stopped_core(
docker: DockerAPI,
coresys: CoreSys,
install_addon_ssh: Addon,
caplog: pytest.LogCaptureFixture,
):
"""Test fixup just removes addon's container when it is stopped."""
caplog.clear()
docker.containers.get = make_mock_container_get("stopped")
addon_execute_rebuild = FixupAddonExecuteRebuild(coresys)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(Addon, "restart") as restart:
await addon_execute_rebuild()
restart.assert_not_called()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
docker.containers.get("addon_local_ssh").remove.assert_called_once_with(force=True)
assert "Addon local_ssh is stopped" in caplog.text
async def test_fixup_unknown_core(
docker: DockerAPI,
coresys: CoreSys,
install_addon_ssh: Addon,
caplog: pytest.LogCaptureFixture,
):
"""Test fixup does nothing if addon's container has already been removed."""
caplog.clear()
docker.containers.get.side_effect = NotFound("")
addon_execute_rebuild = FixupAddonExecuteRebuild(coresys)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(Addon, "restart") as restart, patch.object(
DockerInterface, "stop"
) as stop:
await addon_execute_rebuild()
restart.assert_not_called()
stop.assert_not_called()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
assert "Container for addon local_ssh does not exist" in caplog.text
async def test_fixup_addon_removed(coresys: CoreSys, caplog: pytest.LogCaptureFixture):
"""Test fixup does nothing if addon has been removed."""
caplog.clear()
addon_execute_rebuild = FixupAddonExecuteRebuild(coresys)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
await addon_execute_rebuild()
assert "Cannot rebuild addon local_ssh as it is not installed" in caplog.text
@@ -0,0 +1,94 @@
"""Test fixup core execute rebuild."""
from unittest.mock import MagicMock, patch
from docker.errors import NotFound
import pytest
from supervisor.coresys import CoreSys
from supervisor.docker.interface import DockerInterface
from supervisor.docker.manager import DockerAPI
from supervisor.homeassistant.core import HomeAssistantCore
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.fixups.core_execute_rebuild import FixupCoreExecuteRebuild
def make_mock_container_get(status: str):
"""Make mock of container get."""
out = MagicMock()
out.status = status
out.attrs = {"State": {"ExitCode": 0}, "Mounts": []}
def mock_container_get(name):
return out
return mock_container_get
async def test_fixup(docker: DockerAPI, coresys: CoreSys):
"""Test fixup rebuilds core's container."""
docker.containers.get = make_mock_container_get("running")
core_execute_rebuild = FixupCoreExecuteRebuild(coresys)
assert core_execute_rebuild.auto is False
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.CORE,
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(HomeAssistantCore, "rebuild") as rebuild:
await core_execute_rebuild()
rebuild.assert_called_once()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
async def test_fixup_stopped_core(
docker: DockerAPI, coresys: CoreSys, caplog: pytest.LogCaptureFixture
):
"""Test fixup just removes HA's container when it is stopped."""
caplog.clear()
docker.containers.get = make_mock_container_get("stopped")
core_execute_rebuild = FixupCoreExecuteRebuild(coresys)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.CORE,
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(HomeAssistantCore, "rebuild") as rebuild:
await core_execute_rebuild()
rebuild.assert_not_called()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
docker.containers.get("homeassistant").remove.assert_called_once_with(force=True)
assert "Home Assistant is stopped" in caplog.text
async def test_fixup_unknown_core(
docker: DockerAPI, coresys: CoreSys, caplog: pytest.LogCaptureFixture
):
"""Test fixup does nothing if core's container has already been removed."""
caplog.clear()
docker.containers.get.side_effect = NotFound("")
core_execute_rebuild = FixupCoreExecuteRebuild(coresys)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.CORE,
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(HomeAssistantCore, "rebuild") as rebuild, patch.object(
DockerInterface, "stop"
) as stop:
await core_execute_rebuild()
rebuild.assert_not_called()
stop.assert_not_called()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
assert "Container for Home Assistant does not exist" in caplog.text
@@ -10,7 +10,10 @@ from tests.dbus_service_mocks.systemd import Systemd as SystemdService
async def test_fixup(
coresys: CoreSys, all_dbus_services: dict[str, DBusServiceMock], path_extern
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock],
path_extern,
mount_propagation,
):
"""Test fixup."""
systemd_service: SystemdService = all_dbus_services["systemd"]
@@ -10,7 +10,10 @@ from tests.dbus_service_mocks.systemd import Systemd as SystemdService
async def test_fixup(
coresys: CoreSys, all_dbus_services: dict[str, DBusServiceMock], path_extern
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock],
path_extern,
mount_propagation,
):
"""Test fixup."""
systemd_service: SystemdService = all_dbus_services["systemd"]
@@ -0,0 +1,48 @@
"""Test fixup plugin execute rebuild."""
from unittest.mock import MagicMock, patch
import pytest
from supervisor.coresys import CoreSys
from supervisor.docker.manager import DockerAPI
from supervisor.plugins.audio import PluginAudio
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.fixups.plugin_execute_rebuild import (
FixupPluginExecuteRebuild,
)
def make_mock_container_get(status: str):
"""Make mock of container get."""
out = MagicMock()
out.status = status
out.attrs = {"State": {"ExitCode": 0}, "Mounts": []}
def mock_container_get(name):
return out
return mock_container_get
@pytest.mark.parametrize("status", ["running", "stopped"])
async def test_fixup(docker: DockerAPI, coresys: CoreSys, status: str):
"""Test fixup rebuilds plugin's container regardless of current state."""
docker.containers.get = make_mock_container_get(status)
plugin_execute_rebuild = FixupPluginExecuteRebuild(coresys)
assert plugin_execute_rebuild.auto is True
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.PLUGIN,
reference="audio",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(PluginAudio, "rebuild") as rebuild:
await plugin_execute_rebuild()
rebuild.assert_called_once()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
@@ -0,0 +1,58 @@
"""Test fixup system execute rebuild."""
from unittest.mock import patch
from supervisor.coresys import CoreSys
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.fixups.addon_execute_rebuild import FixupAddonExecuteRebuild
from supervisor.resolution.fixups.core_execute_rebuild import FixupCoreExecuteRebuild
from supervisor.resolution.fixups.plugin_execute_rebuild import (
FixupPluginExecuteRebuild,
)
from supervisor.resolution.fixups.system_execute_rebuild import (
FixupSystemExecuteRebuild,
)
async def test_fixup(coresys: CoreSys):
"""Test fixup applies other rebuild fixups for docker config issues."""
system_execute_rebuild = FixupSystemExecuteRebuild(coresys)
assert system_execute_rebuild.auto is False
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.CORE,
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.PLUGIN,
reference="audio",
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
coresys.resolution.create_issue(
IssueType.DOCKER_CONFIG,
ContextType.SYSTEM,
suggestions=[SuggestionType.EXECUTE_REBUILD],
)
with patch.object(
FixupAddonExecuteRebuild, "process_fixup"
) as addon_fixup, patch.object(
FixupCoreExecuteRebuild, "process_fixup"
) as core_fixup, patch.object(
FixupPluginExecuteRebuild, "process_fixup"
) as plugin_fixup:
await system_execute_rebuild()
addon_fixup.assert_called_once_with(reference="local_ssh")
core_fixup.assert_called_once()
plugin_fixup.assert_called_once_with(reference="audio")
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions