From 17673c5bf101bee1f744981b92acafdb4a14b289 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Wed, 5 Feb 2020 00:18:39 +0100 Subject: [PATCH] pmb.config.workdir: save/check chroot init date (!1878) Whenever initializing new chroots, save the date in $WORK/workdir.cfg. Add pmb.config.workdir.chroots_outdated() to check if it's time to zap the chroots or not (since we don't update them automatically). Mark them as outdated after two days. This will be the first check in "pmbootstrap status" (future patches). Related: #1829 --- pmb/chroot/init.py | 3 ++ pmb/chroot/zap.py | 4 ++ pmb/config/__init__.py | 4 ++ pmb/config/workdir.py | 87 ++++++++++++++++++++++++++++++ test/test_config_workdir.py | 103 ++++++++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 pmb/config/workdir.py create mode 100644 test/test_config_workdir.py diff --git a/pmb/chroot/init.py b/pmb/chroot/init.py index 0347f713..245ea576 100644 --- a/pmb/chroot/init.py +++ b/pmb/chroot/init.py @@ -8,6 +8,7 @@ import filecmp import pmb.chroot import pmb.chroot.apk_static import pmb.config +import pmb.config.workdir import pmb.helpers.repo import pmb.helpers.run import pmb.parse.arch @@ -73,6 +74,8 @@ def init(args, suffix="native"): copy_resolv_conf(args, suffix) pmb.chroot.apk.update_repository_list(args, suffix) + pmb.config.workdir.chroot_save_date(args, suffix) + # Install alpine-base pmb.helpers.repo.update(args, arch) pmb.chroot.apk_static.run(args, ["--no-progress", "--root", chroot, diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index 1d873bd9..29951fe8 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -6,6 +6,7 @@ import math import os import pmb.chroot +import pmb.config.workdir import pmb.helpers.pmaports import pmb.helpers.run import pmb.parse.apkindex @@ -72,6 +73,9 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, if not dry: pmb.helpers.run.root(args, ["rm", "-rf", match]) + # Remove config init dates for deleted chroots + pmb.config.workdir.clean(args) + # Chroots were zapped, so no repo lists exist anymore args.cache["apk_repository_list_updated"].clear() diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 618d11b8..7a214869 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -148,6 +148,10 @@ chroot_device_nodes = [ # You can force-update them with 'pmbootstrap update'. apkindex_retention_time = 4 + +# When chroot is considered outdated (in seconds) +chroot_outdated = 3600 * 24 * 2 + # # BUILD # diff --git a/pmb/config/workdir.py b/pmb/config/workdir.py new file mode 100644 index 00000000..282341c8 --- /dev/null +++ b/pmb/config/workdir.py @@ -0,0 +1,87 @@ +# Copyright 2020 Oliver Smith +# SPDX-License-Identifier: GPL-3.0-or-later +""" Save, read, verify workdir state related information in $WORK/workdir.cfg, + for example the init dates of the chroots. This is not saved in + pmbootstrap.cfg, because pmbootstrap.cfg is not tied to a specific work + dir. """ +import configparser +import os +import time + +import pmb.config + + +def chroot_save_date(args, suffix): + """ Save the chroot initialization date in $WORK/workdir.cfg. """ + # Read existing cfg + cfg = configparser.ConfigParser() + path = args.work + "/workdir.cfg" + if os.path.isfile(path): + cfg.read(path) + + # Set current date for chroot suffix + key = "chroot-init-dates" + if key not in cfg: + cfg[key] = {} + cfg[key][suffix] = str(int(time.time())) + + # Write back + with open(path, "w") as handle: + cfg.write(handle) + + +def chroots_outdated(args): + """ Check if init dates from workdir.cfg indicate that any chroot is + outdated. + :returns: True if any of the chroots are outdated and should be zapped, + False otherwise """ + # Skip if workdir.cfg doesn't exist + path = args.work + "/workdir.cfg" + if not os.path.exists(path): + return False + + cfg = configparser.ConfigParser() + cfg.read(path) + key = "chroot-init-dates" + if key not in cfg: + return False + + date_outdated = time.time() - pmb.config.chroot_outdated + for suffix in cfg[key]: + date_init = int(cfg[key][suffix]) + if date_init <= date_outdated: + return True + return False + + +def clean(args): + """ Remove obsolete data data from workdir.cfg. + :returns: None if workdir does not exist, + True if config was rewritten, + False if config did not change """ + # Skip if workdir.cfg doesn't exist + path = args.work + "/workdir.cfg" + if not os.path.exists(path): + return None + + # Read + cfg = configparser.ConfigParser() + cfg.read(path) + + # Remove entries for deleted chroots + key = "chroot-init-dates" + changed = False + if key in cfg: + for suffix in cfg[key]: + path_suffix = args.work + "/chroot_" + suffix + if os.path.exists(path_suffix): + continue + changed = True + del cfg[key][suffix] + + # Write back + if changed: + with open(path, "w") as handle: + cfg.write(handle) + + return changed diff --git a/test/test_config_workdir.py b/test/test_config_workdir.py new file mode 100644 index 00000000..3543412f --- /dev/null +++ b/test/test_config_workdir.py @@ -0,0 +1,103 @@ +# Copyright 2020 Oliver Smith +# SPDX-License-Identifier: GPL-3.0-or-later +""" Test pmb/config/workdir.py """ +import os +import pytest +import sys +import time + +import pmb_test # noqa +import pmb.config +import pmb.config.workdir + + +@pytest.fixture +def args(request): + import pmb.parse + sys.argv = ["pmbootstrap", "init"] + args = pmb.parse.arguments() + args.log = args.work + "/log_testsuite.txt" + pmb.helpers.logging.init(args) + request.addfinalizer(args.logfd.close) + return args + + +def test_chroot_save_date(args, tmpdir, monkeypatch): + # Override time.time() + def fake_time(): + return 1234567890.1234 + monkeypatch.setattr(time, "time", fake_time) + + args.work = str(tmpdir) + func = pmb.config.workdir.chroot_save_date + func(args, "native") + + expected = "[chroot-init-dates]\nnative = 1234567890\n\n" + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == expected + + # Write again (different code path) + func(args, "buildroot_armhf") + expected = ("[chroot-init-dates]\nnative = 1234567890\n" + "buildroot_armhf = 1234567890\n\n") + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == expected + + +def test_chroots_outdated(args, tmpdir, monkeypatch): + args.work = str(tmpdir) + + # Override time.time(): now is "100" + def fake_time(): + return 100.0 + monkeypatch.setattr(time, "time", fake_time) + + # workdir.cfg does not exist + func = pmb.config.workdir.chroots_outdated + assert func(args) is False + + # workdir.cfg is empty file + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write("") + assert func(args) is False + + # Write fake workdir.cfg: native was created at "90" + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write("[chroot-init-dates]\nnative = 90\n\n") + + # Outdated (date_outdated: 90) + monkeypatch.setattr(pmb.config, "chroot_outdated", 10) + assert func(args) is True + + # Not outdated (date_outdated: 89) + monkeypatch.setattr(pmb.config, "chroot_outdated", 11) + assert func(args) is False + + +def test_clean(args, tmpdir): + args.work = str(tmpdir) + + # 0. workdir.cfg does not exist + func = pmb.config.workdir.clean + assert func(args) is None + + # Write fake workdir.cfg + cfg_fake = "[chroot-init-dates]\nnative = 1337\n\n" + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write(cfg_fake) + + # 1. chroot_native dir exists + os.makedirs(args.work + "/chroot_native") + assert func(args) is False + + # workdir.cfg: unchanged + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == cfg_fake + + # 2. chroot_native dir does not exist + os.rmdir(args.work + "/chroot_native") + assert func(args) is True + + # workdir.cfg: "native" entry removed + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == "[chroot-init-dates]\n\n"