From 1bf5ff4739d31b3d53260e82131e835d2a65530c Mon Sep 17 00:00:00 2001 From: "Bryn M. Reeves" Date: Thu, 8 Jan 2026 16:45:41 +0000 Subject: [PATCH] Update to upstream 0.7.0 Resolves: RHEL-137376 Signed-off-by: Bryn M. Reeves --- .gitignore | 1 + ...dful-of-tests-in-RH-CI-environments-.patch | 112 +++++++++ ...create_snapshot_set_no_provider-if-i.patch | 36 --- ...ELINE-policy-retention-indexing-when.patch | 59 ----- ...add-GcPolicyParamsTimeline-progressi.patch | 238 ------------------ snapm.spec | 7 +- sources | 2 +- tests/upstream/run-unit-tests.sh | 2 +- 8 files changed, 118 insertions(+), 339 deletions(-) create mode 100644 0001-tests-skip-a-handful-of-tests-in-RH-CI-environments-.patch delete mode 100644 0001-tests-skip-test_create_snapshot_set_no_provider-if-i.patch delete mode 100644 0002-schedule-fix-TIMELINE-policy-retention-indexing-when.patch delete mode 100644 0003-container_tests-add-GcPolicyParamsTimeline-progressi.patch diff --git a/.gitignore b/.gitignore index 814ccd0..2a90479 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /snapm-0.4.3.tar.gz /snapm-0.5.1.tar.gz /snapm-0.5.2.tar.gz +/snapm-0.7.0.tar.gz diff --git a/0001-tests-skip-a-handful-of-tests-in-RH-CI-environments-.patch b/0001-tests-skip-a-handful-of-tests-in-RH-CI-environments-.patch new file mode 100644 index 0000000..6ad3f18 --- /dev/null +++ b/0001-tests-skip-a-handful-of-tests-in-RH-CI-environments-.patch @@ -0,0 +1,112 @@ +From 5d363f97d4ccea2a53c7da48ff9f18a3dd9da274 Mon Sep 17 00:00:00 2001 +From: "Bryn M. Reeves" +Date: Thu, 8 Jan 2026 18:24:16 +0000 +Subject: [PATCH] tests: skip a handful of tests in RH CI environments #915 + +Resolves: #915 + +Signed-off-by: Bryn M. Reeves +--- + tests/__init__.py | 7 +++++++ + tests/test_command.py | 4 +++- + tests/test_mounts.py | 4 +++- + tests/test_plugin.py | 3 ++- + 4 files changed, 15 insertions(+), 3 deletions(-) + +diff --git a/tests/__init__.py b/tests/__init__.py +index fa39b9cb..989fc87f 100644 +--- a/tests/__init__.py ++++ b/tests/__init__.py +@@ -101,3 +101,10 @@ def have_root(): + and ``False`` otherwise. + """ + return os.geteuid() == 0 and os.getegid() == 0 ++ ++ ++def in_rh_ci(): ++ """Return ``True`` if we appear to be running in CentOS/RHEL CI, ++ and ``False`` otherwise. ++ """ ++ return os.environ.get("IN_RH_CI", None) is not None +diff --git a/tests/test_command.py b/tests/test_command.py +index c1e547fb..4319ecd3 100644 +--- a/tests/test_command.py ++++ b/tests/test_command.py +@@ -18,7 +18,7 @@ from snapm.report import ReportOpts + import snapm.manager + import boom + +-from tests import MockArgs, have_root, BOOT_ROOT_TEST ++from tests import MockArgs, have_root, BOOT_ROOT_TEST, in_rh_ci + + from ._util import LvmLoopBacked, StratisLoopBacked + +@@ -1079,6 +1079,7 @@ class CommandTests(CommandTestsBase): + args += ["snapset", "delete", "hourly.0"] + self.assertEqual(command.main(args), 0) + ++ @unittest.skipIf(in_rh_ci(), "Tests running in RH CI pipeline") + def test_main_snapset_diff(self): + """Test 'snapm snapset diff' command.""" + self.manager.create_snapshot_set("testset0", self.mount_points()) +@@ -1110,6 +1111,7 @@ class CommandTests(CommandTestsBase): + self.manager.delete_snapshot_sets(snapm.Selection(name="testset0")) + self.manager.delete_snapshot_sets(snapm.Selection(name="testset1")) + ++ @unittest.skipIf(in_rh_ci(), "Tests running in RH CI pipeline") + def test_main_snapset_diffreport(self): + """Test 'snapm snapset diffreport' command.""" + self.manager.create_snapshot_set("testset0", self.mount_points()) +diff --git a/tests/test_mounts.py b/tests/test_mounts.py +index d6c7a5a3..db249341 100644 +--- a/tests/test_mounts.py ++++ b/tests/test_mounts.py +@@ -24,7 +24,7 @@ import snapm.manager._mounts as mounts + import snapm.manager + from snapm.manager.plugins import format_snapshot_name, encode_mount_point + +-from tests import have_root, is_redhat ++from tests import have_root, is_redhat, in_rh_ci + from ._util import LoopBackDevices, LvmLoopBacked + + ETC_FSTAB = "/etc/fstab" +@@ -587,6 +587,7 @@ class MountsTests(MountsTestsBase): + # 4. Check that the mount was still discovered (it's just a warning) + self.assertEqual(len(new_mounts._mounts), 1) + ++ @unittest.skipIf(in_rh_ci(), "Tests running in RH CI pipeline") + def test_discover_incomplete_api_mounts(self): + """ + Tests that discovery logs warnings for missing API mounts. +@@ -626,6 +627,7 @@ class MountsTestsExec(MountsTestsBase): + """ + need_exec = True + ++ @unittest.skipIf(in_rh_ci(), "Tests running in RH CI pipeline") + def test_mount_exec(self): + """ + Tests running a command chrooted inside a mounted snapshot set. +diff --git a/tests/test_plugin.py b/tests/test_plugin.py +index bd4fec08..7532a2fb 100644 +--- a/tests/test_plugin.py ++++ b/tests/test_plugin.py +@@ -14,7 +14,7 @@ log = logging.getLogger() + + import snapm.manager.plugins as plugins + +-from tests import MockPlugin ++from tests import MockPlugin, in_rh_ci + + + def _find_device_mounts(): +@@ -85,6 +85,7 @@ class PluginTests(unittest.TestCase): + for name, origin in snapshot_names.items(): + self.assertEqual(None, plugins.parse_snapshot_name(name, origin)) + ++ @unittest.skipIf(in_rh_ci(), "Tests running in RH CI pipeline") + def test_device_from_mount_point(self): + mounts = _find_device_mounts() + for mount in mounts: +-- +2.52.0 + diff --git a/0001-tests-skip-test_create_snapshot_set_no_provider-if-i.patch b/0001-tests-skip-test_create_snapshot_set_no_provider-if-i.patch deleted file mode 100644 index c33c565..0000000 --- a/0001-tests-skip-test_create_snapshot_set_no_provider-if-i.patch +++ /dev/null @@ -1,36 +0,0 @@ -From a25c448e741870d8baaff2d32e7afb47fc031857 Mon Sep 17 00:00:00 2001 -From: "Bryn M. Reeves" -Date: Wed, 5 Nov 2025 11:16:23 +0000 -Subject: [PATCH] tests: skip test_create_snapshot_set_no_provider if - !ismount("/boot") - -Resolves: #588 - -Signed-off-by: Bryn M. Reeves ---- - tests/test_manager.py | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/tests/test_manager.py b/tests/test_manager.py -index 62c0603..5dc8ea4 100644 ---- a/tests/test_manager.py -+++ b/tests/test_manager.py -@@ -8,6 +8,7 @@ - import unittest - import logging - import os -+import os.path - from uuid import UUID - from json import loads - import tempfile -@@ -506,6 +507,7 @@ class ManagerTests(unittest.TestCase): - with self.assertRaises(snapm.SnapmPathError) as cm: - self.manager.create_snapshot_set("testset0", [non_mount]) - -+ @unittest.skipIf(not os.path.ismount("/boot"), "no suitable mount path") - def test_create_snapshot_set_no_provider(self): - with self.assertRaises(snapm.SnapmNoProviderError): - self.manager.create_snapshot_set("testset0", ["/boot"]) --- -2.51.0 - diff --git a/0002-schedule-fix-TIMELINE-policy-retention-indexing-when.patch b/0002-schedule-fix-TIMELINE-policy-retention-indexing-when.patch deleted file mode 100644 index d9b2079..0000000 --- a/0002-schedule-fix-TIMELINE-policy-retention-indexing-when.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 4d0c9d55e1c53274527c2f449b83a858ae9bcddd Mon Sep 17 00:00:00 2001 -From: "Bryn M. Reeves" -Date: Tue, 18 Nov 2025 23:49:31 +0000 -Subject: [PATCH 1/2] schedule: fix TIMELINE policy retention indexing when - keep_x > len(x) - -Resolves: #606 - -Signed-off-by: Bryn M. Reeves ---- - snapm/manager/_schedule.py | 20 ++++++++++++++------ - 1 file changed, 14 insertions(+), 6 deletions(-) - -diff --git a/snapm/manager/_schedule.py b/snapm/manager/_schedule.py -index 2a65811..fd382a5 100644 ---- a/snapm/manager/_schedule.py -+++ b/snapm/manager/_schedule.py -@@ -431,24 +431,32 @@ class GcPolicyParamsTimeline(GcPolicyParams): - # Build a set of snapshots that should be KEPT by each category - kept_by_category = { - "yearly": set( -- yearly[len(yearly) - self.keep_yearly :] if self.keep_yearly else [] -+ yearly[max(len(yearly) - self.keep_yearly, 0) :] -+ if self.keep_yearly -+ else [] - ), - "quarterly": set( -- quarterly[len(quarterly) - self.keep_quarterly :] -+ quarterly[max(len(quarterly) - self.keep_quarterly, 0) :] - if self.keep_quarterly - else [] - ), - "monthly": set( -- monthly[len(monthly) - self.keep_monthly :] if self.keep_monthly else [] -+ monthly[max(len(monthly) - self.keep_monthly, 0) :] -+ if self.keep_monthly -+ else [] - ), - "weekly": set( -- weekly[len(weekly) - self.keep_weekly :] if self.keep_weekly else [] -+ weekly[max(len(weekly) - self.keep_weekly, 0) :] -+ if self.keep_weekly -+ else [] - ), - "daily": set( -- daily[len(daily) - self.keep_daily :] if self.keep_daily else [] -+ daily[max(len(daily) - self.keep_daily, 0) :] if self.keep_daily else [] - ), - "hourly": set( -- hourly[len(hourly) - self.keep_hourly :] if self.keep_hourly else [] -+ hourly[max(len(hourly) - self.keep_hourly, 0) :] -+ if self.keep_hourly -+ else [] - ), - } - --- -2.51.0 - diff --git a/0003-container_tests-add-GcPolicyParamsTimeline-progressi.patch b/0003-container_tests-add-GcPolicyParamsTimeline-progressi.patch deleted file mode 100644 index 147a3b2..0000000 --- a/0003-container_tests-add-GcPolicyParamsTimeline-progressi.patch +++ /dev/null @@ -1,238 +0,0 @@ -From f9a0dec1289608347f17980e67245d639c7811c0 Mon Sep 17 00:00:00 2001 -From: "Bryn M. Reeves" -Date: Wed, 19 Nov 2025 20:47:34 +0000 -Subject: [PATCH 2/2] container_tests: add GcPolicyParamsTimeline progressive - test - -Resolves: #608 - -Signed-off-by: Bryn M. Reeves ---- - container_tests/tests/__init__.py | 11 ++ - container_tests/tests/test_schedule.py | 185 +++++++++++++++++++++++++ - 2 files changed, 196 insertions(+) - create mode 100644 container_tests/tests/__init__.py - -diff --git a/container_tests/tests/__init__.py b/container_tests/tests/__init__.py -new file mode 100644 -index 0000000..028e43c ---- /dev/null -+++ b/container_tests/tests/__init__.py -@@ -0,0 +1,11 @@ -+import logging -+ -+log = logging.getLogger() -+log.setLevel(logging.DEBUG) -+formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') -+file_handler = logging.FileHandler("test.log") -+file_handler.setFormatter(formatter) -+console_handler = logging.StreamHandler() -+console_handler.setFormatter(formatter) -+log.addHandler(file_handler) -+log.addHandler(console_handler) -diff --git a/container_tests/tests/test_schedule.py b/container_tests/tests/test_schedule.py -index 314518b..6d7522c 100644 ---- a/container_tests/tests/test_schedule.py -+++ b/container_tests/tests/test_schedule.py -@@ -100,6 +100,12 @@ def get_eighteen_months(): - - - class GcPolicyTests(unittest.TestCase): -+ def setUp(self): -+ log.debug("Preparing %s", self._testMethodName) -+ -+ def tearDown(self): -+ log.debug("Tearing down %s", self._testMethodName) -+ - def test_gcpolicy_bad_name(self): - with self.assertRaises(snapm.SnapmArgumentError) as cm: - gcp = GcPolicy("", GcPolicyType.ALL, {}) -@@ -490,6 +496,185 @@ class GcPolicyTests(unittest.TestCase): - self.assertEqual(to_delete[0], snapshot_sets[0], - "Should delete the oldest snapshot set") - -+ def test_GcPolicyParamsTimeline_progressive_hourly_3_months(self): -+ """ -+ Regression test for bug where snapshot sets are incorrectly garbage -+ collected due to a mis-calculation of the retention index when the -+ keep_x parameter for category x is greater than the number of snapshot -+ sets in that category. -+ -+ Generates a sequence of MockSnapshotSet objects at hourly intervals, -+ and applies garbage collection progressively at the end of each -+ interval. This accurately reflects how the GcPolicy applies in real -+ world use. -+ """ -+ params = { -+ "keep_yearly": 0, -+ "keep_quarterly": 1, -+ "keep_monthly": 3, -+ "keep_weekly": 4, -+ "keep_daily": 7, -+ "keep_hourly": 12 -+ } -+ -+ gcp = GcPolicy("test", GcPolicyType.TIMELINE, params) -+ -+ base = datetime(2024, 1, 1, 0, 0, 0) -+ -+ # first timestamp -+ start_ts = int(base.timestamp()) -+ -+ # seconds in three months -+ delta_ts = int(3 * 30.44 * 24 * 3600) -+ -+ snapshot_sets = [] -+ -+ for index, ts in enumerate(range(start_ts, start_ts + delta_ts, 3600)): -+ snapshot_sets.append(MockSnapshotSet(f"hourly.{index}", ts)) -+ to_delete = gcp.evaluate(snapshot_sets) -+ for td in to_delete: -+ snapshot_sets.remove(td) -+ log.debug("Nr snapshot sets (%d): %d", index, len(snapshot_sets)) -+ -+ # First 12 hours (12 hourly + 1 daily): 0-12 hours -+ if index <= 12: -+ self.assertEqual(len(snapshot_sets), index + 1) -+ -+ # First 1 day 11 hours (12 hourly + 1 daily): 14-35 hours -+ if index > 12 and index <= 35: -+ self.assertEqual(len(snapshot_sets), 13) -+ -+ # First 2 days 11 hours (12 hourly + 2 daily): 36-59 hours -+ if index > 35 and index <= 59: -+ self.assertEqual(len(snapshot_sets), 14) -+ -+ # First 3 days 11 hours (12 hourly + 3 daily): 60-83 hours -+ if index > 59 and index <= 83: -+ self.assertEqual(len(snapshot_sets), 15) -+ -+ # First 4 days 11 hours (12 hourly + 4 daily): 84-107 hours -+ if index > 83 and index <= 107: -+ self.assertEqual(len(snapshot_sets), 16) -+ -+ # First 5 days 11 hours (12 hourly + 5 daily): 108-131 hours -+ if index > 107 and index <= 131: -+ self.assertEqual(len(snapshot_sets), 17) -+ -+ # First 6 days 11 hours (12 hourly + 6 daily): 132-155 hours -+ if index > 131 and index <= 155: -+ self.assertEqual(len(snapshot_sets), 18) -+ -+ # First 7 days 11 hours (12 hourly + 7 daily + 1 weekly): 156-179 hours -+ if index > 155 and index <= 179: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 7 days 23 hours (12 hourly + 7 daily + 1 weekly - dailies begin to expire): 180-191 hours -+ if index > 179 and index <= 191: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 8 days 11 hours (12 hourly + 7 daily) + 1 weekly: 192-203 hours -+ if index > 191 and index <= 203: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 8 days 23 hours (12 hourly + 7 daily) + 1 weekly: 204-215 hours -+ if index > 203 and index <= 215: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 9 days 11 hours (12 hourly + 7 daily) + 1 weekly: 216-227 hours -+ if index > 215 and index <= 227: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 9 days 23 hours (12 hourly + 7 daily) + 1 weekly: 228-239 hours -+ if index > 227 and index <= 239: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 10 days 11 hours (12 hourly + 7 daily) + 1 weekly: 240-251 hours -+ if index > 239 and index <= 251: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 10 days 23 hours (12 hourly + 7 daily + 1 weekly): 252-263 hours -+ if index > 251 and index <= 263: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 11 days 11 hours (12 hourly + 7 daily + 1 weekly): 264-275 hours -+ if index > 263 and index <= 275: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 11 days 23 hours (12 hourly + 7 daily + 1 weekly): 276-287 hours -+ if index > 275 and index <= 287: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 12 days 11 hours (12 hourly + 7 daily + 1 weekly): 288-299 hours -+ if index > 287 and index <= 299: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 12 days 23 hours (12 hourly + 7 daily + 1 weekly): 300-311 hours -+ if index > 299 and index <= 311: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 13 days 11 hours (12 hourly + 7 daily + 1 weekly): 312-323 hours -+ if index > 311 and index <= 323: -+ self.assertEqual(len(snapshot_sets), 19) -+ -+ # First 14 days 11 hours (12 hourly + 7 daily + 2 weekly): 324-347 hours -+ if index > 323 and index <= 347: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 14 days 23 hours (12 hourly + 7 daily + 2 weekly): 348-359 hours -+ if index > 347 and index <= 359: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 15 days 11 hours (12 hourly + 7 daily + 2 weekly): 360-371 hours -+ if index > 359 and index <= 371: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 15 days 23 hours (12 hourly + 7 daily + 2 weekly): 372-383 hours -+ if index > 371 and index <= 383: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 16 days 11 hours (12 hourly + 7 daily + 2 weekly): 384-395 hours -+ if index > 383 and index <= 395: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 16 days 23 hours (12 hourly + 7 daily + 2 weekly): 396-407 hours -+ if index > 395 and index <= 407: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 17 days 11 hours (12 hourly + 7 daily + 2 weekly): 408-419 hours -+ if index > 407 and index <= 419: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 17 days 23 hours (12 hourly + 7 daily + 2 weekly): 420-431 hours -+ if index > 419 and index <= 431: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 18 days 11 hours (12 hourly + 7 daily + 2 weekly): 432-443 hours -+ if index > 431 and index <= 443: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 18 days 23 hours (12 hourly + 7 daily + 2 weekly): 444-455 hours -+ if index > 443 and index <= 455: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 19 days 11 hours (12 hourly + 7 daily + 2 weekly): 456-467 hours -+ if index > 455 and index <= 467: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 19 days 23 hours (12 hourly + 7 daily + 2 weekly): 468-479 hours -+ if index > 467 and index <= 479: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ # First 20 days 11 hours (12 hourly + 7 daily + 2 weekly): 480-491 hours -+ if index > 479 and index <= 491: -+ self.assertEqual(len(snapshot_sets), 20) -+ -+ # First 21 days 11 hours (12 hourly + 7 daily + 3 weekly): 492-515 hours -+ if index > 491 and index <= 515: -+ self.assertEqual(len(snapshot_sets), 21) -+ -+ if index > 515: -+ self.assertIn(len(snapshot_sets), (21, 22, 23, 24, 25)) -+ - def test_GcPolicy__str__(self): - gcp = GcPolicy("test", GcPolicyType.ALL, {}) - xstr = "Name: test\nType: All\nParams: " --- -2.51.0 - diff --git a/snapm.spec b/snapm.spec index 40ad5ac..8dd195f 100644 --- a/snapm.spec +++ b/snapm.spec @@ -1,16 +1,14 @@ %global summary A set of tools for managing snapshots Name: snapm -Version: 0.5.2 +Version: 0.7.0 Release: %autorelease Summary: %{summary} License: Apache-2.0 URL: https://github.com/snapshotmanager/%{name} Source0: %{url}/archive/v%{version}/%{name}-%{version}.tar.gz -Patch1: 0001-tests-skip-test_create_snapshot_set_no_provider-if-i.patch -Patch2: 0002-schedule-fix-TIMELINE-policy-retention-indexing-when.patch -Patch3: 0003-container_tests-add-GcPolicyParamsTimeline-progressi.patch +Patch1: 0001-tests-skip-a-handful-of-tests-in-RH-CI-environments-.patch BuildArch: noarch @@ -28,6 +26,7 @@ BuildRequires: libfaketime Requires: python3-snapm = %{version}-%{release} Recommends: boom-boot +Recommends: python3-file-magic %package -n python3-snapm Summary: %{summary} diff --git a/sources b/sources index a6f3877..06f9b84 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (snapm-0.5.2.tar.gz) = 572b52d6d5e734df69b78e274c200456273fa1e6bb65dc28d55b99fe64a015f32c4811bcff8f682b7a3eea98e320bb63c08c10590c166d953eb343c93c842f3a +SHA512 (snapm-0.7.0.tar.gz) = 13f88538070690bd4a051968f6a23abfc65c5bc5e30ed9fc75dd9cf0518c295099e237b66d704a560df5e9fb8ef5fd544ab72bd302f296be7d077feb78480368 diff --git a/tests/upstream/run-unit-tests.sh b/tests/upstream/run-unit-tests.sh index 7b3e7c0..d94c078 100755 --- a/tests/upstream/run-unit-tests.sh +++ b/tests/upstream/run-unit-tests.sh @@ -22,4 +22,4 @@ systemctl daemon-reload systemd-tmpfiles --create /usr/lib/tmpfiles.d/snapm.conf # Run tests -sudo pytest -v --log-level=debug tests/ +sudo IN_RH_CI=yes pytest -v --log-level=debug tests/