Skip to content

Commit 968ece0

Browse files
committed
19700: Enable site restore in Checkmk Docker containers
CMK-23809 CMK-28699 SUP-26665 Change-Id: If0bc6269a36cdbfe6ed76325199a3cc26d240038
1 parent ce7ad5d commit 968ece0

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

.werks/19700.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[//]: # (werk v3)
2+
# Enable site restore in Checkmk Docker containers
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-04-09T11:38:14.120155+00:00
7+
version | 2.6.0b1
8+
class | fix
9+
edition | community
10+
component | omd
11+
level | 1
12+
compatible | yes
13+
14+
Previously, restoring a site backup in a Checkmk Docker container could result in the following error:
15+
16+
```sh
17+
File "/omd/versions/2.3.0p41.cce/lib/python3.12/shutil.py", line 662, in _rmtree_safe_fd
18+
os.rmdir(name, dir_fd=dirfd)
19+
OSError: [Errno 16] Device or resource busy: '/omd/sites/disaster/tmp'
20+
```
21+
22+
The restore would abort partway through, leaving the site home directory nearly empty and the site inaccessible.
23+
24+
Users who cannot update can work around this issue by not using a `--tmpfs`
25+
mount at `tmp/` and retrying the restore.

omd/packages/omd/omdlib/restore.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from omdlib.contexts import SiteContext
1313
from omdlib.init_scripts import call_init_scripts
1414
from omdlib.site_paths import SitePaths
15-
from omdlib.tmpfs import unmount_tmpfs
15+
from omdlib.tmpfs import unmount_tmpfs_without_save
1616
from omdlib.user_processes import kill_site_user_processes
1717

1818

@@ -30,7 +30,8 @@ def prepare_restore_as_site_user(site: SiteContext, kill: bool, verbose: bool) -
3030
kill_site_user_processes(site.name, verbose)
3131
ok()
3232

33-
unmount_tmpfs(site.name, site_home, site.tmp_dir)
33+
# We don't need to save the `tmp/` folder, since `clear_site_home` will remove it anyway.
34+
unmount_tmpfs_without_save(site.name, site.tmp_dir, output=True, kill=kill)
3435

3536
sys.stdout.write("Deleting existing site data...")
3637
clear_site_home(Path(site_home))
@@ -77,6 +78,11 @@ def clear_site_home(site_home: Path) -> None:
7778
for entry in scaniter:
7879
if entry.name == restore_working_dir.name:
7980
continue
81+
elif entry.name == "tmp":
82+
# tmp is excluded from backups. The content is cleared by `unmount_tmpfs_without_save`,
83+
# but the directory must remain, since it may be a Docker-managed tmpfs mount point
84+
# that cannot be removed (EBUSY).
85+
continue
8086
elif entry.is_dir(follow_symlinks=False):
8187
shutil.rmtree(entry.path)
8288
else:

tests/unit/omdlib/test_restore.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,38 @@ def by_name(dir_: Directory) -> str:
209209
frozenset({Symlink(name="a link", path=Path("../up"))}),
210210
frozenset({Directory(name=".restore_working_dir")}),
211211
),
212+
(
213+
frozenset(
214+
{
215+
Directory(
216+
name="etc",
217+
files=frozenset(
218+
{File("environment", content=b"# Custom environment variables\n")}
219+
),
220+
),
221+
}
222+
),
223+
frozenset(),
224+
frozenset(
225+
{
226+
# tmp/ is skipped entirely by clear_site_home. Its contents are cleared by
227+
# unmount_tmpfs_without_save (called before clear_site_home), and the directory
228+
# itself must remain as it may be a Docker-managed tmpfs mount point (EBUSY).
229+
Directory(
230+
name="tmp",
231+
directories=frozenset(
232+
{
233+
Directory(
234+
name="run",
235+
files=frozenset({File("rrdcached.sock", content=b"")}),
236+
)
237+
}
238+
),
239+
),
240+
Directory(name=".restore_working_dir"),
241+
}
242+
),
243+
),
212244
],
213245
)
214246
def test_clear_site_home(directories: Dirs, files: Files, untouched: Dirs, tmp_path: Path) -> None:

0 commit comments

Comments
 (0)