mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-11-09 19:09:41 +00:00
Persistent notifications to repairs and fix free_space check (#6179)
* Persistent notifications to repairs and fix free_space check * Fix tests mocking too little free space
This commit is contained in:
@@ -1,15 +1,8 @@
|
||||
"""Helpers to check and fix issues with free space."""
|
||||
|
||||
from ...backups.const import BackupType
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ..const import (
|
||||
MINIMUM_FREE_SPACE_THRESHOLD,
|
||||
MINIMUM_FULL_BACKUPS,
|
||||
ContextType,
|
||||
IssueType,
|
||||
SuggestionType,
|
||||
)
|
||||
from ..const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
|
||||
from .base import CheckBase
|
||||
|
||||
|
||||
@@ -23,31 +16,12 @@ class CheckFreeSpace(CheckBase):
|
||||
|
||||
async def run_check(self) -> None:
|
||||
"""Run check if not affected by issue."""
|
||||
if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD:
|
||||
return
|
||||
|
||||
suggestions: list[SuggestionType] = []
|
||||
if (
|
||||
len(
|
||||
[
|
||||
backup
|
||||
for backup in self.sys_backups.list_backups
|
||||
if backup.sys_type == BackupType.FULL
|
||||
]
|
||||
)
|
||||
> MINIMUM_FULL_BACKUPS
|
||||
):
|
||||
suggestions.append(SuggestionType.CLEAR_FULL_BACKUP)
|
||||
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FREE_SPACE, ContextType.SYSTEM, suggestions=suggestions
|
||||
)
|
||||
if await self.approve_check():
|
||||
self.sys_resolution.create_issue(IssueType.FREE_SPACE, ContextType.SYSTEM)
|
||||
|
||||
async def approve_check(self, reference: str | None = None) -> bool:
|
||||
"""Approve check if it is affected by issue."""
|
||||
if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD:
|
||||
return False
|
||||
return True
|
||||
return await self.sys_host.info.free_space() <= MINIMUM_FREE_SPACE_THRESHOLD
|
||||
|
||||
@property
|
||||
def issue(self) -> IssueType:
|
||||
|
||||
@@ -9,7 +9,7 @@ FILE_CONFIG_RESOLUTION = Path(SUPERVISOR_DATA, "resolution.json")
|
||||
|
||||
SCHEDULED_HEALTHCHECK = 3600
|
||||
|
||||
MINIMUM_FREE_SPACE_THRESHOLD = 1
|
||||
MINIMUM_FREE_SPACE_THRESHOLD = 2
|
||||
MINIMUM_FULL_BACKUPS = 2
|
||||
|
||||
DNS_CHECK_HOST = "_checkdns.home-assistant.io"
|
||||
|
||||
@@ -10,9 +10,16 @@ from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import HomeAssistantAPIError
|
||||
from .checks.core_security import SecurityReference
|
||||
from .const import ContextType, IssueType
|
||||
from .data import Issue
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 = Issue(
|
||||
IssueType.SECURITY,
|
||||
ContextType.CORE,
|
||||
reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5,
|
||||
)
|
||||
|
||||
|
||||
class ResolutionNotify(CoreSysAttributes):
|
||||
"""Notify class for resolution."""
|
||||
@@ -29,44 +36,17 @@ class ResolutionNotify(CoreSysAttributes):
|
||||
):
|
||||
return
|
||||
|
||||
messages = []
|
||||
|
||||
for issue in self.sys_resolution.issues:
|
||||
if issue.type == IssueType.FREE_SPACE:
|
||||
messages.append(
|
||||
{
|
||||
"title": "Available space is less than 1GB!",
|
||||
"message": f"Available space is {await self.sys_host.info.free_space()}GB, see https://www.home-assistant.io/more-info/free-space for more information.",
|
||||
"notification_id": "supervisor_issue_free_space",
|
||||
}
|
||||
)
|
||||
if issue.type == IssueType.SECURITY and issue.context == ContextType.CORE:
|
||||
if (
|
||||
issue.reference
|
||||
== SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5
|
||||
):
|
||||
messages.append(
|
||||
{
|
||||
"title": "Security notification",
|
||||
"message": "The Supervisor detected that this version of Home Assistant could be insecure in combination with custom integrations. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security alert](https://www.home-assistant.io/latest-security-alert).",
|
||||
"notification_id": "supervisor_update_home_assistant_2021_1_5",
|
||||
}
|
||||
)
|
||||
if issue.type == IssueType.PWNED and issue.context == ContextType.ADDON:
|
||||
messages.append(
|
||||
{
|
||||
"title": f"Insecure secrets in {issue.reference}",
|
||||
"message": f"The add-on {issue.reference} uses secrets which are detected as not secure, see https://www.home-assistant.io/more-info/pwned-passwords for more information.",
|
||||
"notification_id": f"supervisor_issue_pwned_{issue.reference}",
|
||||
}
|
||||
)
|
||||
|
||||
for message in messages:
|
||||
# This one issue must remain a persistent notification rather then a repair because repairs didn't exist in HA 2021.1.5
|
||||
if ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 in self.sys_resolution.issues:
|
||||
try:
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
"post",
|
||||
"api/services/persistent_notification/create",
|
||||
json=message,
|
||||
json={
|
||||
"title": "Security notification",
|
||||
"message": "The Supervisor detected that this version of Home Assistant could be insecure in combination with custom integrations. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security alert](https://www.home-assistant.io/latest-security-alert).",
|
||||
"notification_id": "supervisor_update_home_assistant_2021_1_5",
|
||||
},
|
||||
) as resp:
|
||||
if resp.status in (200, 201):
|
||||
_LOGGER.debug("Successfully created persistent_notification")
|
||||
|
||||
@@ -139,10 +139,10 @@ async def test_free_space(coresys: CoreSys):
|
||||
return True
|
||||
|
||||
test = TestClass(coresys)
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))):
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, (2048.0**3))):
|
||||
assert await test.execute()
|
||||
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, (512.0**3))):
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))):
|
||||
assert not await test.execute()
|
||||
|
||||
coresys.jobs.ignore_conditions = [JobCondition.FREE_SPACE]
|
||||
|
||||
@@ -70,7 +70,7 @@ async def test_if_check_cleanup_issue(coresys: CoreSys):
|
||||
|
||||
assert free_space in coresys.resolution.issues
|
||||
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
|
||||
await coresys.resolution.check.check_system()
|
||||
|
||||
assert free_space not in coresys.resolution.issues
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
"""Test check free space fixup."""
|
||||
|
||||
# pylint: disable=import-error,protected-access
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.backups.const import BackupType
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.checks.free_space import CheckFreeSpace
|
||||
from supervisor.resolution.const import IssueType, SuggestionType
|
||||
|
||||
|
||||
@pytest.fixture(name="suggestion")
|
||||
async def fixture_suggestion(
|
||||
coresys: CoreSys, request: pytest.FixtureRequest
|
||||
) -> SuggestionType | None:
|
||||
"""Set up test for suggestion."""
|
||||
if request.param == SuggestionType.CLEAR_FULL_BACKUP:
|
||||
backup = MagicMock()
|
||||
backup.sys_type = BackupType.FULL
|
||||
with patch.object(
|
||||
type(coresys.backups),
|
||||
"list_backups",
|
||||
new=PropertyMock(return_value=[backup, backup, backup]),
|
||||
):
|
||||
yield SuggestionType.CLEAR_FULL_BACKUP
|
||||
else:
|
||||
yield request.param
|
||||
from supervisor.resolution.const import IssueType
|
||||
|
||||
|
||||
async def test_base(coresys: CoreSys):
|
||||
@@ -37,19 +16,14 @@ async def test_base(coresys: CoreSys):
|
||||
assert free_space.enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"suggestion",
|
||||
[None, SuggestionType.CLEAR_FULL_BACKUP],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_check(coresys: CoreSys, suggestion: SuggestionType | None):
|
||||
async def test_check(coresys: CoreSys):
|
||||
"""Test check."""
|
||||
free_space = CheckFreeSpace(coresys)
|
||||
await coresys.core.set_state(CoreState.RUNNING)
|
||||
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
|
||||
await free_space.run_check()
|
||||
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
@@ -58,10 +32,6 @@ async def test_check(coresys: CoreSys, suggestion: SuggestionType | None):
|
||||
await free_space.run_check()
|
||||
|
||||
assert coresys.resolution.issues[-1].type == IssueType.FREE_SPACE
|
||||
|
||||
if suggestion:
|
||||
assert coresys.resolution.suggestions[-1].type == suggestion
|
||||
else:
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
|
||||
|
||||
@@ -73,7 +43,7 @@ async def test_approve(coresys: CoreSys):
|
||||
with patch("shutil.disk_usage", return_value=(1, 1, 1)):
|
||||
assert await free_space.approve_check()
|
||||
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
|
||||
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
|
||||
assert not await free_space.approve_check()
|
||||
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ async def test_update_unavailable_addon(
|
||||
"version",
|
||||
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
|
||||
),
|
||||
patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))),
|
||||
patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))),
|
||||
):
|
||||
with pytest.raises(AddonNotSupportedError):
|
||||
await coresys.addons.update("local_ssh", backup=True)
|
||||
@@ -226,7 +226,7 @@ async def test_install_unavailable_addon(
|
||||
"version",
|
||||
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
|
||||
),
|
||||
patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))),
|
||||
patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))),
|
||||
pytest.raises(AddonNotSupportedError),
|
||||
):
|
||||
await coresys.addons.install("local_ssh")
|
||||
|
||||
Reference in New Issue
Block a user