Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 4 additions & 30 deletions supervisor/resolution/checks/free_space.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion supervisor/resolution/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

SCHEDULED_HEALTHCHECK = 3600

MINIMUM_FREE_SPACE_THRESHOLD = 1
MINIMUM_FREE_SPACE_THRESHOLD = 2
MINIMUM_FULL_BACKUPS = 2

DNS_CHECK_HOST = "_checkdns.home-assistant.io"
Expand Down
48 changes: 14 additions & 34 deletions supervisor/resolution/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@
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."""
Expand All @@ -29,44 +36,17 @@ async def issue_notifications(self):
):
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")
Expand Down
4 changes: 2 additions & 2 deletions tests/jobs/test_job_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ async def execute(self):
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]
Expand Down
2 changes: 1 addition & 1 deletion tests/resolution/check/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 6 additions & 36 deletions tests/resolution/check/test_check_free_space.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
Expand All @@ -58,11 +32,7 @@ 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
assert len(coresys.resolution.suggestions) == 0


async def test_approve(coresys: CoreSys):
Expand All @@ -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()


Expand Down
4 changes: 2 additions & 2 deletions tests/store/test_store_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
Loading