Remove support for live schema migration of old recorder databases (#122399)

* Remove support for live schema migration of old recorder databases

* Update test
This commit is contained in:
Erik Montnemery 2024-07-29 15:52:18 +02:00 committed by GitHub
parent 9514a38320
commit ea75c8864f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 19 deletions

View File

@ -119,7 +119,10 @@ from .util import (
if TYPE_CHECKING: if TYPE_CHECKING:
from . import Recorder from . import Recorder
LIVE_MIGRATION_MIN_SCHEMA_VERSION = 0 # Live schema migration supported starting from schema version 42 or newer
# Schema version 41 was introduced in HA Core 2023.4
# Schema version 42 was introduced in HA Core 2023.11
LIVE_MIGRATION_MIN_SCHEMA_VERSION = 42
MIGRATION_NOTE_OFFLINE = ( MIGRATION_NOTE_OFFLINE = (
"Note: this may take several hours on large databases and slow machines. " "Note: this may take several hours on large databases and slow machines. "

View File

@ -87,10 +87,23 @@ async def test_schema_update_calls(
call(instance, hass, engine, session_maker, version + 1, 0) call(instance, hass, engine, session_maker, version + 1, 0)
for version in range(db_schema.SCHEMA_VERSION) for version in range(db_schema.SCHEMA_VERSION)
] ]
status = migration.SchemaValidationStatus(0, True, set(), 0)
assert migrate_schema.mock_calls == [ assert migrate_schema.mock_calls == [
call(instance, hass, engine, session_maker, status, 0), call(
call(instance, hass, engine, session_maker, status, db_schema.SCHEMA_VERSION), instance,
hass,
engine,
session_maker,
migration.SchemaValidationStatus(0, True, set(), 0),
42,
),
call(
instance,
hass,
engine,
session_maker,
migration.SchemaValidationStatus(42, True, set(), 0),
db_schema.SCHEMA_VERSION,
),
] ]
@ -117,7 +130,9 @@ async def test_migration_in_progress(
new=create_engine_test, new=create_engine_test,
), ),
): ):
await async_setup_recorder_instance(hass, wait_recorder=False) await async_setup_recorder_instance(
hass, wait_recorder=False, wait_recorder_setup=False
)
await hass.async_add_executor_job(instrument_migration.migration_started.wait) await hass.async_add_executor_job(instrument_migration.migration_started.wait)
assert recorder.util.async_migration_in_progress(hass) is True assert recorder.util.async_migration_in_progress(hass) is True
@ -129,8 +144,25 @@ async def test_migration_in_progress(
assert recorder.get_instance(hass).schema_version == SCHEMA_VERSION assert recorder.get_instance(hass).schema_version == SCHEMA_VERSION
@pytest.mark.parametrize(
(
"func_to_patch",
"expected_setup_result",
"expected_pn_create",
"expected_pn_dismiss",
),
[
("migrate_schema_non_live", False, 1, 0),
("migrate_schema_live", True, 2, 1),
],
)
async def test_database_migration_failed( async def test_database_migration_failed(
hass: HomeAssistant, async_setup_recorder_instance: RecorderInstanceGenerator hass: HomeAssistant,
async_setup_recorder_instance: RecorderInstanceGenerator,
func_to_patch: str,
expected_setup_result: bool,
expected_pn_create: int,
expected_pn_dismiss: int,
) -> None: ) -> None:
"""Test we notify if the migration fails.""" """Test we notify if the migration fails."""
assert recorder.util.async_migration_in_progress(hass) is False assert recorder.util.async_migration_in_progress(hass) is False
@ -141,7 +173,7 @@ async def test_database_migration_failed(
new=create_engine_test, new=create_engine_test,
), ),
patch( patch(
"homeassistant.components.recorder.migration._apply_update", f"homeassistant.components.recorder.migration.{func_to_patch}",
side_effect=ValueError, side_effect=ValueError,
), ),
patch( patch(
@ -153,7 +185,9 @@ async def test_database_migration_failed(
side_effect=pn.dismiss, side_effect=pn.dismiss,
) as mock_dismiss, ) as mock_dismiss,
): ):
await async_setup_recorder_instance(hass, wait_recorder=False) await async_setup_recorder_instance(
hass, wait_recorder=False, expected_setup_result=expected_setup_result
)
hass.states.async_set("my.entity", "on", {}) hass.states.async_set("my.entity", "on", {})
hass.states.async_set("my.entity", "off", {}) hass.states.async_set("my.entity", "off", {})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -161,8 +195,8 @@ async def test_database_migration_failed(
await hass.async_block_till_done() await hass.async_block_till_done()
assert recorder.util.async_migration_in_progress(hass) is False assert recorder.util.async_migration_in_progress(hass) is False
assert len(mock_create.mock_calls) == 2 assert len(mock_create.mock_calls) == expected_pn_create
assert len(mock_dismiss.mock_calls) == 1 assert len(mock_dismiss.mock_calls) == expected_pn_dismiss
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"]) @pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@ -346,7 +380,7 @@ async def test_events_during_migration_are_queued(
), ),
): ):
await async_setup_recorder_instance( await async_setup_recorder_instance(
hass, {"commit_interval": 0}, wait_recorder=False hass, {"commit_interval": 0}, wait_recorder=False, wait_recorder_setup=False
) )
await hass.async_add_executor_job(instrument_migration.migration_started.wait) await hass.async_add_executor_job(instrument_migration.migration_started.wait)
assert recorder.util.async_migration_in_progress(hass) is True assert recorder.util.async_migration_in_progress(hass) is True
@ -389,7 +423,7 @@ async def test_events_during_migration_queue_exhausted(
), ),
): ):
await async_setup_recorder_instance( await async_setup_recorder_instance(
hass, {"commit_interval": 0}, wait_recorder=False hass, {"commit_interval": 0}, wait_recorder=False, wait_recorder_setup=False
) )
await hass.async_add_executor_job(instrument_migration.migration_started.wait) await hass.async_add_executor_job(instrument_migration.migration_started.wait)
assert recorder.util.async_migration_in_progress(hass) is True assert recorder.util.async_migration_in_progress(hass) is True
@ -421,7 +455,15 @@ async def test_events_during_migration_queue_exhausted(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("start_version", "live"), ("start_version", "live"),
[(0, True), (9, True), (16, True), (18, True), (22, True), (25, True), (43, True)], [
(0, False),
(9, False),
(16, False),
(18, False),
(22, False),
(25, False),
(43, True),
],
) )
async def test_schema_migrate( async def test_schema_migrate(
hass: HomeAssistant, hass: HomeAssistant,
@ -500,7 +542,9 @@ async def test_schema_migrate(
"homeassistant.components.recorder.Recorder._pre_process_startup_events", "homeassistant.components.recorder.Recorder._pre_process_startup_events",
), ),
): ):
await async_setup_recorder_instance(hass, wait_recorder=False) await async_setup_recorder_instance(
hass, wait_recorder=False, wait_recorder_setup=live
)
await hass.async_add_executor_job(instrument_migration.migration_started.wait) await hass.async_add_executor_job(instrument_migration.migration_started.wait)
assert recorder.util.async_migration_in_progress(hass) is True assert recorder.util.async_migration_in_progress(hass) is True
await recorder_helper.async_wait_recorder(hass) await recorder_helper.async_wait_recorder(hass)

View File

@ -2555,7 +2555,9 @@ async def test_recorder_info_migration_queue_exhausted(
recorder.core, "MIN_AVAILABLE_MEMORY_FOR_QUEUE_BACKLOG", sys.maxsize recorder.core, "MIN_AVAILABLE_MEMORY_FOR_QUEUE_BACKLOG", sys.maxsize
), ),
): ):
async with async_test_recorder(hass, wait_recorder=False): async with async_test_recorder(
hass, wait_recorder=False, wait_recorder_setup=False
):
await hass.async_add_executor_job( await hass.async_add_executor_job(
instrument_migration.migration_started.wait instrument_migration.migration_started.wait
) )

View File

@ -1399,6 +1399,7 @@ async def _async_init_recorder_component(
db_url: str | None = None, db_url: str | None = None,
*, *,
expected_setup_result: bool, expected_setup_result: bool,
wait_setup: bool,
) -> None: ) -> None:
"""Initialize the recorder asynchronously.""" """Initialize the recorder asynchronously."""
# pylint: disable-next=import-outside-toplevel # pylint: disable-next=import-outside-toplevel
@ -1416,10 +1417,14 @@ async def _async_init_recorder_component(
setup_task = asyncio.ensure_future( setup_task = asyncio.ensure_future(
async_setup_component(hass, recorder.DOMAIN, {recorder.DOMAIN: config}) async_setup_component(hass, recorder.DOMAIN, {recorder.DOMAIN: config})
) )
if wait_setup:
# Wait for recorder integration to setup # Wait for recorder integration to setup
setup_result = await setup_task setup_result = await setup_task
assert setup_result == expected_setup_result assert setup_result == expected_setup_result
assert (recorder.DOMAIN in hass.config.components) == expected_setup_result assert (recorder.DOMAIN in hass.config.components) == expected_setup_result
else:
# Wait for recorder to connect to the database
await recorder_helper.async_wait_recorder(hass)
_LOGGER.info( _LOGGER.info(
"Test recorder successfully started, database location: %s", "Test recorder successfully started, database location: %s",
config[recorder.CONF_DB_URL], config[recorder.CONF_DB_URL],
@ -1585,6 +1590,7 @@ async def async_test_recorder(
*, *,
expected_setup_result: bool = True, expected_setup_result: bool = True,
wait_recorder: bool = True, wait_recorder: bool = True,
wait_recorder_setup: bool = True,
) -> AsyncGenerator[recorder.Recorder]: ) -> AsyncGenerator[recorder.Recorder]:
"""Setup and return recorder instance.""" # noqa: D401 """Setup and return recorder instance.""" # noqa: D401
await _async_init_recorder_component( await _async_init_recorder_component(
@ -1592,6 +1598,7 @@ async def async_test_recorder(
config, config,
recorder_db_url, recorder_db_url,
expected_setup_result=expected_setup_result, expected_setup_result=expected_setup_result,
wait_setup=wait_recorder_setup,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
instance = hass.data[recorder.DATA_INSTANCE] instance = hass.data[recorder.DATA_INSTANCE]
@ -1621,6 +1628,7 @@ async def async_setup_recorder_instance(
*, *,
expected_setup_result: bool = True, expected_setup_result: bool = True,
wait_recorder: bool = True, wait_recorder: bool = True,
wait_recorder_setup: bool = True,
) -> AsyncGenerator[recorder.Recorder]: ) -> AsyncGenerator[recorder.Recorder]:
"""Set up and return recorder instance.""" """Set up and return recorder instance."""
@ -1630,6 +1638,7 @@ async def async_setup_recorder_instance(
config, config,
expected_setup_result=expected_setup_result, expected_setup_result=expected_setup_result,
wait_recorder=wait_recorder, wait_recorder=wait_recorder,
wait_recorder_setup=wait_recorder_setup,
) )
) )