From ad6315be5c4f5a65b26c7707b66d88c9ab8fd961 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Jun 2020 22:10:05 -0500 Subject: [PATCH] Ensure recorder runs are cleaned up during purge (#36989) Co-authored-by: Paulus Schoutsen --- homeassistant/components/recorder/purge.py | 11 +++++-- tests/components/recorder/test_purge.py | 38 ++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index befbe444220..78d92b8b65e 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import SQLAlchemyError import homeassistant.util.dt as dt_util -from .models import Events, States +from .models import Events, RecorderRuns, States from .util import session_scope _LOGGER = logging.getLogger(__name__) @@ -33,6 +33,13 @@ def purge_old_data(instance, purge_days, repack): ) _LOGGER.debug("Deleted %s events", deleted_rows) + deleted_rows = ( + session.query(RecorderRuns) + .filter(RecorderRuns.start < purge_before) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) + if repack: # Execute sqlite or postgresql vacuum command to free up space on disk if instance.engine.driver in ("pysqlite", "postgresql"): @@ -41,7 +48,7 @@ def purge_old_data(instance, purge_days, repack): # Optimize mysql / mariadb tables to free up space on disk elif instance.engine.driver == "mysqldb": _LOGGER.debug("Optimizing SQL DB to free space") - instance.engine.execute("OPTIMIZE TABLE states, events") + instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") except SQLAlchemyError as err: _LOGGER.warning("Error purging history: %s.", err) diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index ac59ca5e2d7..05a184a8608 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -5,9 +5,10 @@ import unittest from homeassistant.components import recorder from homeassistant.components.recorder.const import DATA_INSTANCE -from homeassistant.components.recorder.models import Events, States +from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope +from homeassistant.util import dt as dt_util from tests.async_mock import patch from tests.common import get_test_home_assistant, init_recorder_component @@ -94,6 +95,32 @@ class TestRecorderPurge(unittest.TestCase): ) ) + def _add_test_recorder_runs(self): + """Add a few recorder_runs for testing.""" + now = datetime.now() + five_days_ago = now - timedelta(days=5) + eleven_days_ago = now - timedelta(days=11) + + self.hass.block_till_done() + self.hass.data[DATA_INSTANCE].block_till_done() + + with recorder.session_scope(hass=self.hass) as session: + for rec_id in range(6): + if rec_id < 2: + timestamp = eleven_days_ago + elif rec_id < 4: + timestamp = five_days_ago + else: + timestamp = now + + session.add( + RecorderRuns( + start=timestamp, + created=dt_util.utcnow(), + end=timestamp + timedelta(days=1), + ) + ) + def test_purge_old_states(self): """Test deleting old states.""" self._add_test_states() @@ -127,6 +154,7 @@ class TestRecorderPurge(unittest.TestCase): service_data = {"keep_days": 4} self._add_test_events() self._add_test_states() + self._add_test_recorder_runs() # make sure we start with 6 states with session_scope(hass=self.hass) as session: @@ -136,6 +164,9 @@ class TestRecorderPurge(unittest.TestCase): events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) assert events.count() == 6 + recorder_runs = session.query(RecorderRuns) + assert recorder_runs.count() == 7 + self.hass.data[DATA_INSTANCE].block_till_done() # run purge method - no service data, use defaults @@ -162,6 +193,9 @@ class TestRecorderPurge(unittest.TestCase): # now we should only have 2 events left assert events.count() == 2 + # now we should only have 3 recorder runs left + assert recorder_runs.count() == 3 + assert not ( "EVENT_TEST_PURGE" in (event.event_type for event in events.all()) ) @@ -175,6 +209,6 @@ class TestRecorderPurge(unittest.TestCase): self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() assert ( - mock_logger.debug.mock_calls[3][1][0] + mock_logger.debug.mock_calls[4][1][0] == "Vacuuming SQL DB to free space" )