Skip to content

Commit 01911a4

Browse files
authored
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
1 parent 857dae7 commit 01911a4

File tree

7 files changed

+30
-106
lines changed

7 files changed

+30
-106
lines changed

supervisor/resolution/checks/free_space.py

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
"""Helpers to check and fix issues with free space."""
22

3-
from ...backups.const import BackupType
43
from ...const import CoreState
54
from ...coresys import CoreSys
6-
from ..const import (
7-
MINIMUM_FREE_SPACE_THRESHOLD,
8-
MINIMUM_FULL_BACKUPS,
9-
ContextType,
10-
IssueType,
11-
SuggestionType,
12-
)
5+
from ..const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
136
from .base import CheckBase
147

158

@@ -23,31 +16,12 @@ class CheckFreeSpace(CheckBase):
2316

2417
async def run_check(self) -> None:
2518
"""Run check if not affected by issue."""
26-
if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD:
27-
return
28-
29-
suggestions: list[SuggestionType] = []
30-
if (
31-
len(
32-
[
33-
backup
34-
for backup in self.sys_backups.list_backups
35-
if backup.sys_type == BackupType.FULL
36-
]
37-
)
38-
> MINIMUM_FULL_BACKUPS
39-
):
40-
suggestions.append(SuggestionType.CLEAR_FULL_BACKUP)
41-
42-
self.sys_resolution.create_issue(
43-
IssueType.FREE_SPACE, ContextType.SYSTEM, suggestions=suggestions
44-
)
19+
if await self.approve_check():
20+
self.sys_resolution.create_issue(IssueType.FREE_SPACE, ContextType.SYSTEM)
4521

4622
async def approve_check(self, reference: str | None = None) -> bool:
4723
"""Approve check if it is affected by issue."""
48-
if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD:
49-
return False
50-
return True
24+
return await self.sys_host.info.free_space() <= MINIMUM_FREE_SPACE_THRESHOLD
5125

5226
@property
5327
def issue(self) -> IssueType:

supervisor/resolution/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
SCHEDULED_HEALTHCHECK = 3600
1111

12-
MINIMUM_FREE_SPACE_THRESHOLD = 1
12+
MINIMUM_FREE_SPACE_THRESHOLD = 2
1313
MINIMUM_FULL_BACKUPS = 2
1414

1515
DNS_CHECK_HOST = "_checkdns.home-assistant.io"

supervisor/resolution/notify.py

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@
1010
from ..exceptions import HomeAssistantAPIError
1111
from .checks.core_security import SecurityReference
1212
from .const import ContextType, IssueType
13+
from .data import Issue
1314

1415
_LOGGER: logging.Logger = logging.getLogger(__name__)
1516

17+
ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 = Issue(
18+
IssueType.SECURITY,
19+
ContextType.CORE,
20+
reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5,
21+
)
22+
1623

1724
class ResolutionNotify(CoreSysAttributes):
1825
"""Notify class for resolution."""
@@ -29,44 +36,17 @@ async def issue_notifications(self):
2936
):
3037
return
3138

32-
messages = []
33-
34-
for issue in self.sys_resolution.issues:
35-
if issue.type == IssueType.FREE_SPACE:
36-
messages.append(
37-
{
38-
"title": "Available space is less than 1GB!",
39-
"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.",
40-
"notification_id": "supervisor_issue_free_space",
41-
}
42-
)
43-
if issue.type == IssueType.SECURITY and issue.context == ContextType.CORE:
44-
if (
45-
issue.reference
46-
== SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5
47-
):
48-
messages.append(
49-
{
50-
"title": "Security notification",
51-
"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).",
52-
"notification_id": "supervisor_update_home_assistant_2021_1_5",
53-
}
54-
)
55-
if issue.type == IssueType.PWNED and issue.context == ContextType.ADDON:
56-
messages.append(
57-
{
58-
"title": f"Insecure secrets in {issue.reference}",
59-
"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.",
60-
"notification_id": f"supervisor_issue_pwned_{issue.reference}",
61-
}
62-
)
63-
64-
for message in messages:
39+
# This one issue must remain a persistent notification rather then a repair because repairs didn't exist in HA 2021.1.5
40+
if ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 in self.sys_resolution.issues:
6541
try:
6642
async with self.sys_homeassistant.api.make_request(
6743
"post",
6844
"api/services/persistent_notification/create",
69-
json=message,
45+
json={
46+
"title": "Security notification",
47+
"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).",
48+
"notification_id": "supervisor_update_home_assistant_2021_1_5",
49+
},
7050
) as resp:
7151
if resp.status in (200, 201):
7252
_LOGGER.debug("Successfully created persistent_notification")

tests/jobs/test_job_decorator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ async def execute(self):
139139
return True
140140

141141
test = TestClass(coresys)
142-
with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))):
142+
with patch("shutil.disk_usage", return_value=(42, 42, (2048.0**3))):
143143
assert await test.execute()
144144

145-
with patch("shutil.disk_usage", return_value=(42, 42, (512.0**3))):
145+
with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))):
146146
assert not await test.execute()
147147

148148
coresys.jobs.ignore_conditions = [JobCondition.FREE_SPACE]

tests/resolution/check/test_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def test_if_check_cleanup_issue(coresys: CoreSys):
7070

7171
assert free_space in coresys.resolution.issues
7272

73-
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
73+
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
7474
await coresys.resolution.check.check_system()
7575

7676
assert free_space not in coresys.resolution.issues

tests/resolution/check/test_check_free_space.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,12 @@
11
"""Test check free space fixup."""
22

33
# pylint: disable=import-error,protected-access
4-
from unittest.mock import MagicMock, PropertyMock, patch
4+
from unittest.mock import patch
55

6-
import pytest
7-
8-
from supervisor.backups.const import BackupType
96
from supervisor.const import CoreState
107
from supervisor.coresys import CoreSys
118
from supervisor.resolution.checks.free_space import CheckFreeSpace
12-
from supervisor.resolution.const import IssueType, SuggestionType
13-
14-
15-
@pytest.fixture(name="suggestion")
16-
async def fixture_suggestion(
17-
coresys: CoreSys, request: pytest.FixtureRequest
18-
) -> SuggestionType | None:
19-
"""Set up test for suggestion."""
20-
if request.param == SuggestionType.CLEAR_FULL_BACKUP:
21-
backup = MagicMock()
22-
backup.sys_type = BackupType.FULL
23-
with patch.object(
24-
type(coresys.backups),
25-
"list_backups",
26-
new=PropertyMock(return_value=[backup, backup, backup]),
27-
):
28-
yield SuggestionType.CLEAR_FULL_BACKUP
29-
else:
30-
yield request.param
9+
from supervisor.resolution.const import IssueType
3110

3211

3312
async def test_base(coresys: CoreSys):
@@ -37,19 +16,14 @@ async def test_base(coresys: CoreSys):
3716
assert free_space.enabled
3817

3918

40-
@pytest.mark.parametrize(
41-
"suggestion",
42-
[None, SuggestionType.CLEAR_FULL_BACKUP],
43-
indirect=True,
44-
)
45-
async def test_check(coresys: CoreSys, suggestion: SuggestionType | None):
19+
async def test_check(coresys: CoreSys):
4620
"""Test check."""
4721
free_space = CheckFreeSpace(coresys)
4822
await coresys.core.set_state(CoreState.RUNNING)
4923

5024
assert len(coresys.resolution.issues) == 0
5125

52-
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
26+
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
5327
await free_space.run_check()
5428

5529
assert len(coresys.resolution.issues) == 0
@@ -58,11 +32,7 @@ async def test_check(coresys: CoreSys, suggestion: SuggestionType | None):
5832
await free_space.run_check()
5933

6034
assert coresys.resolution.issues[-1].type == IssueType.FREE_SPACE
61-
62-
if suggestion:
63-
assert coresys.resolution.suggestions[-1].type == suggestion
64-
else:
65-
assert len(coresys.resolution.suggestions) == 0
35+
assert len(coresys.resolution.suggestions) == 0
6636

6737

6838
async def test_approve(coresys: CoreSys):
@@ -73,7 +43,7 @@ async def test_approve(coresys: CoreSys):
7343
with patch("shutil.disk_usage", return_value=(1, 1, 1)):
7444
assert await free_space.approve_check()
7545

76-
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
46+
with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))):
7747
assert not await free_space.approve_check()
7848

7949

tests/store/test_store_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ async def test_update_unavailable_addon(
170170
"version",
171171
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
172172
),
173-
patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))),
173+
patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))),
174174
):
175175
with pytest.raises(AddonNotSupportedError):
176176
await coresys.addons.update("local_ssh", backup=True)
@@ -226,7 +226,7 @@ async def test_install_unavailable_addon(
226226
"version",
227227
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
228228
),
229-
patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))),
229+
patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))),
230230
pytest.raises(AddonNotSupportedError),
231231
):
232232
await coresys.addons.install("local_ssh")

0 commit comments

Comments
 (0)