Skip to content

Commit 197ab2b

Browse files
committed
22143 FIX omd backup: Fix corrupted Event Console history in the archive
Jira: CMK-37640 Caused-by: If049b3d4f934eaedf1ee32b8a39a5905ef472850 Change-Id: Ia7a04da81616c02fb358026dd4fe710db0b1076d
1 parent 13333f8 commit 197ab2b

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

.werks/22143.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[//]: # (werk v3)
2+
# omd backup: Fix corrupted Event Console history in the archive
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-08-12T09:22:25.650928+00:00
7+
version | 2.5.0p12
8+
class | fix
9+
edition | community
10+
component | omd
11+
level | 2
12+
compatible | yes
13+
14+
`omd backup` of a running site could produce an archive whose Event Console
15+
history database is corrupted. After restoring such an archive, entries of the
16+
event history could be missing or show up twice, or the Event Console could fail
17+
to read its history at all.
18+
19+
The history database `var/mkeventd/history/history.sqlite` is archived through
20+
the sqlite backup API, which yields a complete database on its own. The
21+
write-ahead log and the shared memory file next to it were archived afterwards
22+
as ordinary files, so the archive held a snapshot plus a log belonging to a
23+
different instant, which was then replayed onto the snapshot on restore.
24+
25+
These sidecar files are now excluded from the archive. This does not repair
26+
archives that were already taken. If you depend on the event history of such an
27+
archive, check it after restoring.

omd/packages/omd/omdlib/backup.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ def get_exclude_patterns(options: BackupExclusions) -> list[str]:
197197
excludes.append("*.mk.new*")
198198
excludes.append("var/log/.liveproxyd.state.new*")
199199

200+
# The Event Console history database is archived via the sqlite backup API
201+
# (see _tar_add), which yields a complete database on its own. Its sidecar
202+
# files are copied unsynchronised and would be replayed onto a snapshot taken
203+
# at a different instant, silently losing or duplicating rows. The glob also
204+
# covers the rollback journal.
205+
excludes.append("var/mkeventd/history/history.sqlite-*")
206+
excludes.append("var/mkeventd/history/history.sqlite.*")
207+
200208
# exclude the "cache" / working directory for the Agent Bakery
201209
excludes.append("var/check_mk/agents/.files_cache/*")
202210

tests/system/singlesite/omd/test_omd.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,36 @@ def test_run_omd_backup_and_omd_restore(site: Site) -> None:
178178
restored_site.rm()
179179

180180

181+
def test_omd_backup_excludes_ec_history_sidecars(site: Site) -> None:
182+
"""Test that 'omd backup' does not archive the Event Console history sidecars."""
183+
history_dir = "var/mkeventd/history"
184+
185+
def _wal_on_disk() -> bool:
186+
site.live.query("GET eventconsolehistory\nLimit: 1\n")
187+
return site.file_exists(f"{history_dir}/history.sqlite-wal")
188+
189+
# The Event Console opens its history before daemonizing, so the sidecars of that
190+
# first connection are unlinked when the pre-fork process exits. Only a reload
191+
# reopens the database from the daemon itself, and sqlite needs an access to
192+
# materialize the files. Without a sidecar the assertions below prove nothing.
193+
site.omd("reload", "mkeventd", check=True)
194+
wait_until(_wal_on_disk, timeout=30, interval=1, condition_name="EC history WAL exists")
195+
196+
# Inside the site, because the site user writes the archive. 'tmp/*' is excluded
197+
# from the backup, so the archive cannot end up inside itself.
198+
backup_path = "tmp/backup_ec_history.tar.gz"
199+
archive = site.path(backup_path).as_posix()
200+
try:
201+
site.omd("backup", archive, check=True)
202+
members = site.run(["tar", "-tf", archive]).stdout.splitlines()
203+
finally:
204+
site.delete_file(backup_path)
205+
206+
assert f"{site.id}/{history_dir}/history.sqlite" in members
207+
assert f"{site.id}/{history_dir}/history.sqlite-wal" not in members
208+
assert f"{site.id}/{history_dir}/history.sqlite-shm" not in members
209+
210+
181211
def test_run_omd_backup_and_omd_restore_empty() -> None:
182212
"""Test that restore works on empty site directory."""
183213
package = CMKPackageInfo(version_from_env(), edition_from_env())

0 commit comments

Comments
 (0)