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:
@@ -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
|
||||
Reference in New Issue
Block a user