mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Remove recorder purge protection (#19358)
This commit is contained in:
parent
9d4de2a722
commit
f9c02889b2
@ -12,50 +12,19 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def purge_old_data(instance, purge_days, repack):
|
def purge_old_data(instance, purge_days, repack):
|
||||||
"""Purge events and states older than purge_days ago."""
|
"""Purge events and states older than purge_days ago."""
|
||||||
from .models import States, Events
|
from .models import States, Events
|
||||||
from sqlalchemy import func
|
|
||||||
|
|
||||||
purge_before = dt_util.utcnow() - timedelta(days=purge_days)
|
purge_before = dt_util.utcnow() - timedelta(days=purge_days)
|
||||||
_LOGGER.debug("Purging events before %s", purge_before)
|
_LOGGER.debug("Purging events before %s", purge_before)
|
||||||
|
|
||||||
with session_scope(session=instance.get_session()) as session:
|
with session_scope(session=instance.get_session()) as session:
|
||||||
delete_states = session.query(States) \
|
deleted_rows = session.query(States) \
|
||||||
.filter((States.last_updated < purge_before))
|
.filter((States.last_updated < purge_before)) \
|
||||||
|
.delete(synchronize_session=False)
|
||||||
# For each entity, the most recent state is protected from deletion
|
|
||||||
# s.t. we can properly restore state even if the entity has not been
|
|
||||||
# updated in a long time
|
|
||||||
protected_states = session.query(func.max(States.state_id)) \
|
|
||||||
.group_by(States.entity_id).all()
|
|
||||||
|
|
||||||
protected_state_ids = tuple(state[0] for state in protected_states)
|
|
||||||
|
|
||||||
if protected_state_ids:
|
|
||||||
delete_states = delete_states \
|
|
||||||
.filter(~States.state_id.in_(protected_state_ids))
|
|
||||||
|
|
||||||
deleted_rows = delete_states.delete(synchronize_session=False)
|
|
||||||
_LOGGER.debug("Deleted %s states", deleted_rows)
|
_LOGGER.debug("Deleted %s states", deleted_rows)
|
||||||
|
|
||||||
delete_events = session.query(Events) \
|
deleted_rows = session.query(Events) \
|
||||||
.filter((Events.time_fired < purge_before))
|
.filter((Events.time_fired < purge_before)) \
|
||||||
|
.delete(synchronize_session=False)
|
||||||
# We also need to protect the events belonging to the protected states.
|
|
||||||
# Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it
|
|
||||||
# will delete the protected state when deleting its associated
|
|
||||||
# event. Also, we would be producing NULLed foreign keys otherwise.
|
|
||||||
if protected_state_ids:
|
|
||||||
protected_events = session.query(States.event_id) \
|
|
||||||
.filter(States.state_id.in_(protected_state_ids)) \
|
|
||||||
.filter(States.event_id.isnot(None)) \
|
|
||||||
.all()
|
|
||||||
|
|
||||||
protected_event_ids = tuple(state[0] for state in protected_events)
|
|
||||||
|
|
||||||
if protected_event_ids:
|
|
||||||
delete_events = delete_events \
|
|
||||||
.filter(~Events.event_id.in_(protected_event_ids))
|
|
||||||
|
|
||||||
deleted_rows = delete_events.delete(synchronize_session=False)
|
|
||||||
_LOGGER.debug("Deleted %s events", deleted_rows)
|
_LOGGER.debug("Deleted %s events", deleted_rows)
|
||||||
|
|
||||||
# Execute sqlite vacuum command to free up space on disk
|
# Execute sqlite vacuum command to free up space on disk
|
||||||
|
@ -58,23 +58,6 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
event_id=event_id + 1000
|
event_id=event_id + 1000
|
||||||
))
|
))
|
||||||
|
|
||||||
# if self._add_test_events was called, we added a special event
|
|
||||||
# that should be protected from deletion, too
|
|
||||||
protected_event_id = getattr(self, "_protected_event_id", 2000)
|
|
||||||
|
|
||||||
# add a state that is old but the only state of its entity and
|
|
||||||
# should be protected
|
|
||||||
session.add(States(
|
|
||||||
entity_id='test.rarely_updated_entity',
|
|
||||||
domain='sensor',
|
|
||||||
state='iamprotected',
|
|
||||||
attributes=json.dumps(attributes),
|
|
||||||
last_changed=eleven_days_ago,
|
|
||||||
last_updated=eleven_days_ago,
|
|
||||||
created=eleven_days_ago,
|
|
||||||
event_id=protected_event_id
|
|
||||||
))
|
|
||||||
|
|
||||||
def _add_test_events(self):
|
def _add_test_events(self):
|
||||||
"""Add a few events for testing."""
|
"""Add a few events for testing."""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -105,32 +88,19 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
time_fired=timestamp,
|
time_fired=timestamp,
|
||||||
))
|
))
|
||||||
|
|
||||||
# Add an event for the protected state
|
|
||||||
protected_event = Events(
|
|
||||||
event_type='EVENT_TEST_FOR_PROTECTED',
|
|
||||||
event_data=json.dumps(event_data),
|
|
||||||
origin='LOCAL',
|
|
||||||
created=eleven_days_ago,
|
|
||||||
time_fired=eleven_days_ago,
|
|
||||||
)
|
|
||||||
session.add(protected_event)
|
|
||||||
session.flush()
|
|
||||||
|
|
||||||
self._protected_event_id = protected_event.event_id
|
|
||||||
|
|
||||||
def test_purge_old_states(self):
|
def test_purge_old_states(self):
|
||||||
"""Test deleting old states."""
|
"""Test deleting old states."""
|
||||||
self._add_test_states()
|
self._add_test_states()
|
||||||
# make sure we start with 7 states
|
# make sure we start with 6 states
|
||||||
with session_scope(hass=self.hass) as session:
|
with session_scope(hass=self.hass) as session:
|
||||||
states = session.query(States)
|
states = session.query(States)
|
||||||
assert states.count() == 7
|
assert states.count() == 6
|
||||||
|
|
||||||
# run purge_old_data()
|
# run purge_old_data()
|
||||||
purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False)
|
purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False)
|
||||||
|
|
||||||
# we should only have 3 states left after purging
|
# we should only have 2 states left after purging
|
||||||
assert states.count() == 3
|
assert states.count() == 2
|
||||||
|
|
||||||
def test_purge_old_events(self):
|
def test_purge_old_events(self):
|
||||||
"""Test deleting old events."""
|
"""Test deleting old events."""
|
||||||
@ -139,12 +109,12 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
with session_scope(hass=self.hass) as session:
|
with session_scope(hass=self.hass) as session:
|
||||||
events = session.query(Events).filter(
|
events = session.query(Events).filter(
|
||||||
Events.event_type.like("EVENT_TEST%"))
|
Events.event_type.like("EVENT_TEST%"))
|
||||||
assert events.count() == 7
|
assert events.count() == 6
|
||||||
|
|
||||||
# run purge_old_data()
|
# run purge_old_data()
|
||||||
purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False)
|
purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False)
|
||||||
|
|
||||||
# no state to protect, now we should only have 2 events left
|
# we should only have 2 events left
|
||||||
assert events.count() == 2
|
assert events.count() == 2
|
||||||
|
|
||||||
def test_purge_method(self):
|
def test_purge_method(self):
|
||||||
@ -156,11 +126,11 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
# make sure we start with 6 states
|
# make sure we start with 6 states
|
||||||
with session_scope(hass=self.hass) as session:
|
with session_scope(hass=self.hass) as session:
|
||||||
states = session.query(States)
|
states = session.query(States)
|
||||||
assert states.count() == 7
|
assert states.count() == 6
|
||||||
|
|
||||||
events = session.query(Events).filter(
|
events = session.query(Events).filter(
|
||||||
Events.event_type.like("EVENT_TEST%"))
|
Events.event_type.like("EVENT_TEST%"))
|
||||||
assert events.count() == 7
|
assert events.count() == 6
|
||||||
|
|
||||||
self.hass.data[DATA_INSTANCE].block_till_done()
|
self.hass.data[DATA_INSTANCE].block_till_done()
|
||||||
|
|
||||||
@ -172,8 +142,8 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
self.hass.data[DATA_INSTANCE].block_till_done()
|
self.hass.data[DATA_INSTANCE].block_till_done()
|
||||||
|
|
||||||
# only purged old events
|
# only purged old events
|
||||||
assert states.count() == 5
|
assert states.count() == 4
|
||||||
assert events.count() == 5
|
assert events.count() == 4
|
||||||
|
|
||||||
# run purge method - correct service data
|
# run purge method - correct service data
|
||||||
self.hass.services.call('recorder', 'purge',
|
self.hass.services.call('recorder', 'purge',
|
||||||
@ -183,19 +153,12 @@ class TestRecorderPurge(unittest.TestCase):
|
|||||||
# Small wait for recorder thread
|
# Small wait for recorder thread
|
||||||
self.hass.data[DATA_INSTANCE].block_till_done()
|
self.hass.data[DATA_INSTANCE].block_till_done()
|
||||||
|
|
||||||
# we should only have 3 states left after purging
|
# we should only have 2 states left after purging
|
||||||
assert states.count() == 3
|
assert states.count() == 2
|
||||||
|
|
||||||
# the protected state is among them
|
# now we should only have 2 events left
|
||||||
assert 'iamprotected' in (
|
assert events.count() == 2
|
||||||
state.state for state in states)
|
|
||||||
|
|
||||||
# now we should only have 3 events left
|
|
||||||
assert events.count() == 3
|
|
||||||
|
|
||||||
# and the protected event is among them
|
|
||||||
assert 'EVENT_TEST_FOR_PROTECTED' in (
|
|
||||||
event.event_type for event in events.all())
|
|
||||||
assert not ('EVENT_TEST_PURGE' in (
|
assert not ('EVENT_TEST_PURGE' in (
|
||||||
event.event_type for event in events.all()))
|
event.event_type for event in events.all()))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user