diff --git a/homeassistant/components/recorder/backup.py b/homeassistant/components/recorder/backup.py new file mode 100644 index 00000000000..cec9f85748b --- /dev/null +++ b/homeassistant/components/recorder/backup.py @@ -0,0 +1,28 @@ +"""Backup platform for the Recorder integration.""" +from logging import getLogger + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from . import Recorder +from .const import DATA_INSTANCE +from .util import async_migration_in_progress + +_LOGGER = getLogger(__name__) + + +async def async_pre_backup(hass: HomeAssistant) -> None: + """Perform operations before a backup starts.""" + _LOGGER.info("Backup start notification, locking database for writes") + instance: Recorder = hass.data[DATA_INSTANCE] + if async_migration_in_progress(hass): + raise HomeAssistantError("Database migration in progress") + await instance.lock_database() + + +async def async_post_backup(hass: HomeAssistant) -> None: + """Perform operations after a backup finishes.""" + instance: Recorder = hass.data[DATA_INSTANCE] + _LOGGER.info("Backup end notification, releasing write lock") + if not instance.unlock_database(): + raise HomeAssistantError("Could not release database write lock") diff --git a/tests/components/recorder/test_backup.py b/tests/components/recorder/test_backup.py new file mode 100644 index 00000000000..4667691add8 --- /dev/null +++ b/tests/components/recorder/test_backup.py @@ -0,0 +1,69 @@ +"""Test backup platform for the Recorder integration.""" + + +from unittest.mock import patch + +import pytest + +from homeassistant.components.recorder.backup import async_post_backup, async_pre_backup +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from tests.common import async_init_recorder_component + + +async def test_async_pre_backup(hass: HomeAssistant) -> None: + """Test pre backup.""" + await async_init_recorder_component(hass) + + with patch( + "homeassistant.components.recorder.backup.Recorder.lock_database" + ) as lock_mock: + await async_pre_backup(hass) + assert lock_mock.called + + +async def test_async_pre_backup_with_timeout(hass: HomeAssistant) -> None: + """Test pre backup with timeout.""" + await async_init_recorder_component(hass) + + with patch( + "homeassistant.components.recorder.backup.Recorder.lock_database", + side_effect=TimeoutError(), + ) as lock_mock, pytest.raises(TimeoutError): + await async_pre_backup(hass) + assert lock_mock.called + + +async def test_async_pre_backup_with_migration(hass: HomeAssistant) -> None: + """Test pre backup with migration.""" + await async_init_recorder_component(hass) + + with patch( + "homeassistant.components.recorder.backup.async_migration_in_progress", + return_value=True, + ), pytest.raises(HomeAssistantError): + await async_pre_backup(hass) + + +async def test_async_post_backup(hass: HomeAssistant) -> None: + """Test post backup.""" + await async_init_recorder_component(hass) + + with patch( + "homeassistant.components.recorder.backup.Recorder.unlock_database" + ) as unlock_mock: + await async_post_backup(hass) + assert unlock_mock.called + + +async def test_async_post_backup_failure(hass: HomeAssistant) -> None: + """Test post backup failure.""" + await async_init_recorder_component(hass) + + with patch( + "homeassistant.components.recorder.backup.Recorder.unlock_database", + return_value=False, + ) as unlock_mock, pytest.raises(HomeAssistantError): + await async_post_backup(hass) + assert unlock_mock.called