Skip to content

Conversation

@bmr-cymru
Copy link
Contributor

@bmr-cymru bmr-cymru commented Dec 29, 2025

This pull request lays the groundwork for adding the Btrfs plugin (and eventually, the plugin itself).

  • Add plugin priority mechanism
  • Relax historical restriction in Lvm2Cow.can_snapshot()
  • Add plugin debug subsystem

Edit: Let's keep this PR for just the pre-req work and have a new clean slate for the plugin itself. It makes the branch name a bit misleading, but we can live with that.

Resolves: #552
Resolves: #834
Resolves: #835
Resolves: #836
Resolves: #837
Resolves: #838
Resolves: #839
Resolves: #841
Resolves: #842
Resolves: #845
Resolves: #846
Resolves: #847
Resolves: #850

Summary by CodeRabbit

  • New Features

    • Plugins are chosen by numeric priority when multiple can snapshot the same source; priority is shown in plugin listings and debug output.
  • Documentation

    • Manual, user guide and commented config examples added to document PluginPriority and how to override it.
  • Behaviour

    • Deterministic default priorities applied for LVM/Stratis backends when unspecified; no active config changes by default.
  • Tests

    • Tests extended with mocks and new cases covering priority handling and selection.
  • Chores

    • Helper script added to ease running from a source checkout.

✏️ Tip: You can customize this high-level summary in your review settings.

@bmr-cymru bmr-cymru self-assigned this Dec 29, 2025
@bmr-cymru bmr-cymru added enhancement New feature or request UI/UX User interface and experience Debugging Issues affecting debugging LVM2 Btrfs labels Dec 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Walkthrough

Adds plugin-priority support across code, docs and tests: Snapshot and Plugin become ABCs; plugins read/configure numeric priorities (with static defaults for lvm2/stratis); Manager resolves the highest-priority provider per source; CLI/reporting and debug expose plugin diagnostics; sample configs/docs updated.

Changes

Cohort / File(s) Summary
Plugin configuration files
etc/snapm/plugins.d/lvm2-cow.conf, etc/snapm/plugins.d/lvm2-thin.conf, etc/snapm/plugins.d/stratis.conf
Add commented [Priority] example blocks and PluginPriority sample entries (comments only).
Manual pages
man/man5/snapm-plugins.d.5, man/man8/snapm.8
Document PluginPriority key and plugin selection behaviour; extend debug class listing to include fsdiff and plugin.
Core debug & Snapshot ABC
snapm/_snapm.py
Add SNAPM_DEBUG_PLUGIN / SNAPM_SUBSYSTEM_PLUGIN, map debug masks; convert Snapshot to an ABC with abstract properties/methods; update exports and mount-list debug handling.
CLI / reporting
snapm/command.py
Add plugin priority FieldType, include priority in default plugin report fields, and accept "plugin" in debug token parsing.
Manager selection logic
snapm/manager/_manager.py
Load per-plugin config path, gather multiple providers per source, resolve each source to the single highest-priority provider (helper added), raise if no provider, and use resolved provider in downstream flows.
Plugin base & limits
snapm/manager/plugins/_plugin.py
Add PLUGIN_NO_PRIORITY; parse/validate [Priority]PluginPriority with warnings (including config path); harden PluginLimits parsing; convert Plugin to an ABC; export PLUGIN_NO_PRIORITY and PluginLimits.
LVM2 plugin
snapm/manager/plugins/lvm2.py
Add LVM2_COW_STATIC_PRIORITY = 10, LVM2_THIN_STATIC_PRIORITY = 15; default to static priority when unspecified; initialise origins/pools tracking; relax can_snapshot() restrictions.
Stratis plugin
snapm/manager/plugins/stratis.py
Add STRATIS_STATIC_PRIORITY = 20; default to static priority when unspecified.
Tests: snapshot & plugin ABCs
tests/test_snapm.py, tests/test_plugin.py, tests/__init__.py
Update test fixtures to satisfy ABC contracts; add MockPlugin test helper; assert base Plugin cannot be instantiated; add tests for priority parsing, static defaults and PluginLimits fallback.
Tests: reduced device footprint & adjustments
tests/test_command.py, tests/test_boot.py, tests/test_manager.py, tests/test_scheduler.py
Reduce number of test volumes across suites; add manager/plugin-priority focused tests and mocks; adjust expectations and iteration counts in affected tests.
Documentation
doc/user_guide.rst
Document priority feature, list defaults (lvm2-cow=10, lvm2-thin=15, stratis=20), show override examples, add troubleshooting guidance and update plugin-list output examples.
Helpers & scripts
scripts/setpaths.sh
Add developer script to set PATH and PYTHONPATH for running from a checkout.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Manager
    participant Config as "plugins.d/*.conf"
    participant PluginA as "Plugin A"
    participant PluginB as "Plugin B"

    Note over Manager,Config: Load per-plugin configs and gather candidate providers
    User->>Manager: Request operation for source X
    Manager->>PluginA: can_snapshot(source X)?
    PluginA-->>Manager: True
    Manager->>PluginB: can_snapshot(source X)?
    PluginB-->>Manager: True
    Manager->>Config: Read PluginPriority for A and B
    Manager->>Manager: Compare priorities (A vs B)
    alt B has higher priority
        Manager->>PluginB: perform operation on source X
        PluginB-->>Manager: Success
    else A higher or only A
        Manager->>PluginA: perform operation on source X
        PluginA-->>Manager: Success
    end
    Manager->>User: Return result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Testing

Poem

🐰 I hopped through configs, sniffed each file,

I ranked the plugins, tiny hop, big smile.
LVM and Stratis line up in a row,
Manager picks the highest and off we go.
Carrot-powered snapshots — quick and agile.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding prerequisite features and fixes for a btrfs plugin, including plugin priority mechanisms, ABC abstractions, and debug subsystems.
Linked Issues check ✅ Passed All linked issue objectives are met: plugin priority system implemented (#836, #837, #839), Snapshot and Plugin made ABCs (#834, #835), plugin debug subsystem added (#841), configuration handling enhanced (#842), test coverage reduced (#552), test coverage for priorities added (#845), user guide documented (#846), setpaths.sh added (#847), and _requested_provider removal (#850).
Out of Scope Changes check ✅ Passed All changes are in-scope: configuration files document priority overrides, manual pages document priority mechanism, code implements ABC classes, priority logic, debug subsystem, and configuration validation—all prerequisites for btrfs plugin addition.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo dnf install -y 'dnf*-command(copr)'
  • dnf copr enable packit/snapshotmanager-snapm-843
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
snapm/manager/_manager.py (1)

931-981: Sources without a provider are silently dropped instead of raising SnapmNoProviderError

The new multi-provider logic and get_highest_priority() helper are a good fit for the priority model, but there’s a subtle regression in how missing providers are handled:

  • provider_map is a defaultdict(list), and you only touch provider_map[source] inside if plugin.can_snapshot(source): provider_map[source].append(plugin).
  • If no plugin claims a given source, that key is never created.
  • The verification loop iterates for source in provider_map:, so sources with no providers are completely skipped and never trigger SnapmNoProviderError.
  • The returned mapping omits those sources entirely, so later code (e.g. create_snapshot_set) just never processes them, resulting in silent partial snapshot sets.

The contract in the docstring (“verify that a provider exists for each source present”) suggests we should fail if any requested source has no provider.

You can fix this by validating against the full sources list, and then selecting the highest-priority provider:

Proposed fix for provider selection and verification
-        def get_highest_priority(providers):
+        def get_highest_priority(providers):
             if not providers:
-                raise ValueError("Empty list")
+                raise ValueError("Empty provider list")

             max_priority = max(prov.priority for prov in providers)
             candidates = [prov for prov in providers if prov.priority == max_priority]

             if len(candidates) > 1:
                 names = ",".join(prov.name for prov in candidates)
                 raise SnapmNoProviderError(
                     f"Multiple providers with priority {max_priority}: {names}"
                 )

             return candidates[0]

         # Initialise provider mapping.
         provider_map = defaultdict(list)
@@
-        # Verify each mount point has a provider plugin
-        for source in provider_map:
-            if provider_map[source] == []:
-                raise SnapmNoProviderError(
-                    f"Could not find snapshot provider for {source}"
-                )
-
-        # Select highest priority provider for each source
-        return {
-            src: get_highest_priority(providers)
-            for src, providers in provider_map.items()
-        }
+        # Verify each requested source has at least one provider, then
+        # select the highest-priority provider per source.
+        resolved = {}
+        for source in sources:
+            providers = provider_map.get(source, [])
+            if not providers:
+                raise SnapmNoProviderError(
+                    f"Could not find snapshot provider for {source}"
+                )
+            resolved[source] = get_highest_priority(providers)
+
+        return resolved

This restores the expected behaviour: every requested source must have exactly one effective provider after priority resolution, or the operation fails clearly.

🧹 Nitpick comments (3)
snapm/manager/plugins/_plugin.py (2)

82-108: Improve robustness when logging invalid PluginLimits values

The new try/except around getint() with a warning that includes the offending value and cfg.plugin_conf_file is a nice usability improvement. However, this assumes cfg always has a plugin_conf_file attribute; if PluginLimits is ever constructed with a plain ConfigParser (e.g. in tests or helper code), this will raise AttributeError instead of warning cleanly.

You could make this more robust by falling back when the attribute is missing:

Suggested defensive tweak
-                    _log_warn(
-                        "Ignoring invalid plugin %s value in %s: %s",
-                        _PLUGIN_CFG_SNAPS_PER_ORIGIN,
-                        cfg.plugin_conf_file,
-                        str(value),
-                    )
+                    conf_name = getattr(cfg, "plugin_conf_file", "<unknown>")
+                    _log_warn(
+                        "Ignoring invalid plugin %s value in %s: %s",
+                        _PLUGIN_CFG_SNAPS_PER_ORIGIN,
+                        conf_name,
+                        str(value),
+                    )

(and similarly for _PLUGIN_CFG_SNAPS_PER_POOL).


48-56: Priority configuration and validation look sound, with a small caveat

The priority configuration path is well-structured: you read PluginPriority, attempt getint(), fall back to the raw value on ValueError, then accept only positive integers, treating 0, negatives, and non-ints as invalid and logging before resetting to PLUGIN_NO_PRIORITY. Exporting PLUGIN_NO_PRIORITY and documenting it as a sentinel for subclasses is also clear.

One small robustness point: like PluginLimits, the invalid-priority warning uses plugin_cfg.plugin_conf_file, which relies on the caller having set that attribute (Manager does, but ad-hoc users of Plugin may not). If you expect Plugin to be constructed in tests or tooling with a bare ConfigParser, the same getattr(..., "<unknown>") pattern as suggested for PluginLimits would avoid surprising AttributeErrors.

Otherwise, the semantics mesh cleanly with the Manager’s new highest-priority selection logic.

Also applies to: 120-154, 542-542

snapm/manager/_manager.py (1)

899-917: Plugin config path/metadata handling is good; consider always defining plugin_conf_file

Using plugin_conf_file = f"{plugin_name}.conf" and plugin_conf_path = join(_PLUGINS_D_PATH, plugin_conf_file) makes the logging clearer, and attaching cfg.plugin_conf_file when a config file exists enables nicer diagnostics in PluginLimits and the priority parsing.

For consistency and robustness, you might want to set cfg.plugin_conf_file unconditionally (e.g. before the exists() check, or defaulting it if the file is absent) so downstream code can always rely on the attribute being present, even when a plugin has no on-disk config:

Example tweak
-        cfg = ConfigParser()
-
-        if exists(plugin_conf_path):
-            _log_debug("Loading plugin configuration from '%s'", plugin_conf_path)
-            cfg.read([plugin_conf_path])
-            cfg.plugin_conf_file = plugin_conf_file  # for debugging
+        cfg = ConfigParser()
+        # Always record the logical config file name for logging.
+        cfg.plugin_conf_file = plugin_conf_file
+
+        if exists(plugin_conf_path):
+            _log_debug("Loading plugin configuration from '%s'", plugin_conf_path)
+            cfg.read([plugin_conf_path])

This would dovetail nicely with the new warnings in PluginLimits and Plugin.__init__().

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65960fe and 284ac8d.

📒 Files selected for processing (12)
  • etc/snapm/plugins.d/lvm2-cow.conf
  • etc/snapm/plugins.d/lvm2-thin.conf
  • etc/snapm/plugins.d/stratis.conf
  • man/man5/snapm-plugins.d.5
  • man/man8/snapm.8
  • snapm/_snapm.py
  • snapm/command.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/plugins/lvm2.py
  • snapm/manager/plugins/stratis.py
  • tests/test_command.py
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • etc/snapm/plugins.d/lvm2-thin.conf
  • etc/snapm/plugins.d/lvm2-cow.conf
  • snapm/manager/_manager.py
  • snapm/manager/plugins/lvm2.py
📚 Learning: 2025-09-14T17:31:58.457Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Applied to files:

  • etc/snapm/plugins.d/lvm2-thin.conf
  • etc/snapm/plugins.d/stratis.conf
  • man/man8/snapm.8
  • etc/snapm/plugins.d/lvm2-cow.conf
  • man/man5/snapm-plugins.d.5
  • snapm/manager/plugins/lvm2.py
📚 Learning: 2025-12-22T10:31:24.641Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 773
File: snapm.spec:148-149
Timestamp: 2025-12-22T10:31:24.641Z
Learning: In the snapm project's snapm.spec file, do not flag typos, formatting issues, or other problems in the %changelog section, as these entries are auto-generated from git commit messages and represent the historical record. Modifying them would require rewriting git history, which the project avoids.

Applied to files:

  • etc/snapm/plugins.d/stratis.conf
  • man/man8/snapm.8
  • snapm/_snapm.py
  • etc/snapm/plugins.d/lvm2-cow.conf
📚 Learning: 2025-09-10T23:42:35.992Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: In the snapm project, changelog entries in snapm.spec are auto-generated from git log using `git log --oneline v0.4.0..v0.5.0 | sed  -e 's/^[a-f0-9]* /- /' -e 's/%//'` and should not be manually edited to avoid rewriting git history.

Applied to files:

  • etc/snapm/plugins.d/stratis.conf
  • man/man8/snapm.8
  • etc/snapm/plugins.d/lvm2-cow.conf
  • man/man5/snapm-plugins.d.5
📚 Learning: 2025-08-25T17:19:19.563Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Applied to files:

  • etc/snapm/plugins.d/stratis.conf
  • man/man8/snapm.8
  • man/man5/snapm-plugins.d.5
📚 Learning: 2025-08-22T18:27:31.307Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-22T18:27:31.307Z
Learning: The snapm project only supports GNU Groff markup for man pages and does not support other troff toolchains. All man page formatting should follow GNU Groff standards and practices.

Applied to files:

  • man/man8/snapm.8
📚 Learning: 2025-08-26T13:22:28.829Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T13:22:28.829Z
Learning: In the snapm project's man pages, single `.` lines are intentional spacers used to separate and improve readability between macro definitions and section headers like `.SH`/`.SS`. These should not be flagged as formatting errors or suggested for removal.

Applied to files:

  • man/man8/snapm.8
📚 Learning: 2025-09-10T23:42:35.992Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Applied to files:

  • man/man8/snapm.8
📚 Learning: 2025-11-12T21:27:50.414Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 596
File: snapm/_snapm.py:36-44
Timestamp: 2025-11-12T21:27:50.414Z
Learning: In the snapm codebase, the numeric values of SNAPM_DEBUG_* constants (e.g., SNAPM_DEBUG_MANAGER, SNAPM_DEBUG_MOUNTS, SNAPM_DEBUG_SCHEDULE) are not guaranteed to remain stable across versions. Consumers should always use the symbolic constant names rather than persisting numeric masks. SNAPM_DEBUG_ALL is designed to change over time as new subsystems are added, and repurposing or reassigning bit positions for debug flags is acceptable and intentional.

Applied to files:

  • man/man8/snapm.8
  • snapm/_snapm.py
  • snapm/command.py
  • tests/test_command.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-08-23T22:16:04.019Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 400
File: man/man8/snapm.8:11-12
Timestamp: 2025-08-23T22:16:04.019Z
Learning: In man pages for the snapm project, prefer backslash (`\`) continuations over multiple macro invocations when breaking long command lists across lines, as this maintains a single macro invocation while being readable in 80x25 terminals.

Applied to files:

  • man/man8/snapm.8
📚 Learning: 2025-08-22T19:34:08.396Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-22T19:34:08.396Z
Learning: The snapshotmanager organization (including snapm and boom projects) uses "SHORTMONTH DAY YEAR" format for man page headers. This format has been used by boom since 2017. Organizational consistency within snapshotmanager is prioritized over cross-project consistency when it comes to formatting standards.

Applied to files:

  • man/man8/snapm.8
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/_snapm.py
  • snapm/command.py
  • tests/test_command.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/plugins/_plugin.py
🧬 Code graph analysis (2)
snapm/command.py (1)
snapm/report.py (2)
  • FieldType (232-286)
  • report_num (397-410)
snapm/manager/_manager.py (1)
snapm/manager/plugins/_plugin.py (1)
  • _log_debug (173-177)
🪛 Ruff (0.14.10)
snapm/manager/_manager.py

933-933: Avoid specifying long messages outside the exception class

(TRY003)


940-942: Avoid specifying long messages outside the exception class

(TRY003)


973-975: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (35)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (19)
etc/snapm/plugins.d/lvm2-thin.conf (1)

3-8: Documentation addition looks good.

The commented [Priority] section with PluginPriority = 15 correctly documents the lvm2-thin static priority and provides a clear example for administrators who wish to override it.

etc/snapm/plugins.d/stratis.conf (1)

3-8: Documentation addition looks good.

The commented [Priority] section with PluginPriority = 20 correctly documents the stratis static priority and provides a clear example for administrators.

etc/snapm/plugins.d/lvm2-cow.conf (1)

3-8: Documentation addition looks good.

The commented [Priority] section with PluginPriority = 10 correctly documents the lvm2-cow static priority and provides a clear example for administrators.

tests/test_command.py (1)

209-209: Test update correctly includes the new plugin debug token.

The addition of plugin to the debug token list aligns with the new SNAPM_DEBUG_PLUGIN flag. Based on learnings, SNAPM_DEBUG_ALL is designed to evolve as new subsystems are added.

man/man5/snapm-plugins.d.5 (1)

38-44: Documentation for the Priority section is clear and accurate.

The new section correctly documents the PluginPriority key and provides a practical use case for overriding default priorities between lvm2-cow and lvm2-thin.

man/man8/snapm.8 (2)

646-646: Debug classes list correctly updated.

The addition of fsdiff and plugin to the debug classes documentation aligns with the new debug subsystems available in the codebase.


903-913: Plugin priority documentation is well-written.

The new paragraphs clearly explain the priority mechanism (higher value wins) and correctly reference snapm-plugins.d(5) for configuration override details.

snapm/manager/plugins/lvm2.py (4)

42-42: Import correctly added for plugin priority support.

The import of PLUGIN_NO_PRIORITY enables the static priority defaulting logic.


180-184: Static priority constants correctly defined.

The priority values (lvm2-cow=10, lvm2-thin=15) align with the PR objectives from issue #837, establishing clear precedence where lvm2-thin is preferred over lvm2-cow by default.


1067-1071: Lvm2Cow initialisation correctly applies static priority.

The origins dictionary initialisation and the conditional priority assignment ensure the plugin has a sensible default priority when none is configured.


1384-1385: Lvm2Thin correctly applies static priority when unconfigured.

The pattern is consistent with Lvm2Cow, ensuring a coherent priority mechanism across LVM2 plugins.

snapm/manager/plugins/stratis.py (3)

44-44: Import correctly added for plugin priority support.

The import of PLUGIN_NO_PRIORITY enables the static priority defaulting logic.


83-84: Static priority constant correctly defined.

The priority value (stratis=20) aligns with the PR objectives from issue #837.


466-467: Stratis correctly applies static priority after version checks.

The placement after check_stratisd_version() is appropriate — the priority is only set if the plugin successfully initialises. The pattern is consistent with LVM2 plugins.

snapm/manager/plugins/_plugin.py (1)

57-67: Plugin debug wrapper follows established logging pattern

The _log_debug_plugin() helper cleanly mirrors the existing subsystem debug wrappers, fixing the subsystem tag via extra={"subsystem": SNAPM_SUBSYSTEM_PLUGIN} and not attempting to merge caller-supplied extra, which matches the intended design for these helpers.
Based on learnings, this is consistent with the project’s logging conventions.

snapm/manager/_manager.py (1)

77-80: Manager debug wrapper correctly tags subsystem

_log_debug_manager() mirrors the other subsystem wrappers by hard-coding extra={"subsystem": SNAPM_SUBSYSTEM_MANAGER} and delegating to the module logger, which fits the existing logging and filtering design.
Based on learnings, this is aligned with how debug wrappers are expected to behave.

snapm/_snapm.py (1)

37-53: Plugin debug subsystem wiring is consistent and self-contained

Adding SNAPM_DEBUG_PLUGIN, including it in SNAPM_DEBUG_ALL, mapping it via _DEBUG_MASK_TO_SUBSYSTEM, and exporting both the flag and SNAPM_SUBSYSTEM_PLUGIN gives plugins first-class debug control without disturbing existing subsystems. This fits neatly with the existing debug-mask design that expects callers to use symbolic constants, not hard-coded integers.
Based on learnings, this respects the intended debug-mask semantics.

Also applies to: 56-72, 2354-2355, 2370-2371

snapm/command.py (2)

450-489: Plugin priority reporting integrates cleanly with the report system

The new FieldType for PR_PLUGIN (name="priority", dtype=REP_NUM, report_fn=lambda f, d: f.report_num(d.priority)) is a good fit: priorities are treated as numeric for display and sorting, and including "priority" in _DEFAULT_PLUGIN_FIELDS makes the new mechanism visible by default in snapm plugin list. This lines up with the base Plugin.priority attribute introduced in the plugins module.


68-69: Command-line debug options correctly expose plugin debugging

Importing SNAPM_DEBUG_PLUGIN and adding a "plugin": SNAPM_DEBUG_PLUGIN entry to set_debug()’s mask_map means users can now enable plugin-specific debug output via -d plugin (and it is also covered by -d all). This is consistent with how other subsystems are wired and relies on the symbolic constants rather than any assumed bit positions.
Based on learnings, this aligns with the intended debug-mask usage.

Also applies to: 2406-2415

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

Improve robustness when logging invalid PluginLimits values

And:

Priority configuration and validation look sound, with a small caveat

The Manager always sets plugin_conf_file for this exact reason. #notabug

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: Thank you for the clarification! I understand now—since Manager always sets plugin_conf_file on the ConfigParser before plugin initialization, the file path will indeed be available in all the warning log messages. My concern about potentially missing context in the error logs was misplaced. Appreciate you pointing this out! 👍


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
snapm/manager/_manager.py (1)

936-996: LGTM: Plugin priority resolution logic correctly implemented.

The refactored logic successfully implements per-source highest-priority provider selection:

  • Collects all capable providers per source using defaultdict(list)
  • Resolves to a single provider per source by selecting the highest numerical priority
  • Properly handles tie-breaking by raising SnapmNoProviderError when multiple providers share the maximum priority
  • Validates that every source has at least one provider

The implementation aligns with PR objectives #836, #837, and #839 for plugin priority support.

Optional: Consider moving exception messages to exception classes

Static analysis flags the exception messages as potentially better suited to exception class definitions (Ruff TRY003 at lines 948, 955-957). This is a style preference rather than a functional issue.

snapm/manager/plugins/_plugin.py (1)

126-153: Verify that treating priority=0 in configuration as invalid is intentional.

The validation logic at lines 141-145 treats priority == PLUGIN_NO_PRIORITY (which equals 0) as invalid. This means if a user explicitly sets PluginPriority=0 in the configuration, it will be rejected and reset to PLUGIN_NO_PRIORITY.

According to issue #837, priorities are tiebreakers with higher numerical values preferred. If 0 is intended to be a valid explicit priority (lowest), the validation should be adjusted. If 0 is reserved as a sentinel for "use static default", this is correct but should perhaps be documented in the configuration examples.

Additionally, the validation conditions could be simplified. The current logic:

priority == PLUGIN_NO_PRIORITY
or (isinstance(priority, int) and priority < 0)
or not isinstance(priority, int)

Could be more concise (if 0 should remain invalid):

not isinstance(priority, int) or priority <= 0

Or if 0 should be allowed:

not isinstance(priority, int) or priority < 0
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 284ac8d and 5e93934.

📒 Files selected for processing (3)
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/plugins/lvm2.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/lvm2.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/manager/plugins/_plugin.py
🪛 Ruff (0.14.10)
snapm/manager/_manager.py

948-948: Avoid specifying long messages outside the exception class

(TRY003)


955-957: Avoid specifying long messages outside the exception class

(TRY003)


991-993: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: python-basic-tests
🔇 Additional comments (13)
snapm/manager/_manager.py (4)

14-14: LGTM: Import additions support the multi-provider collection.

The defaultdict and TYPE_CHECKING imports are appropriately used for collecting multiple providers per source and providing forward-declared type hints without circular imports.

Also applies to: 21-21


70-72: LGTM: Proper use of TYPE_CHECKING for forward references.

The TYPE_CHECKING block correctly imports Plugin for type annotations whilst avoiding circular import issues.


913-923: LGTM: Plugin config loading enhanced with file context.

The refactored logic correctly builds the plugin config path and stores the filename on the ConfigParser instance for improved warning logging, aligning with the PR's objective to include config file names in plugin configuration warnings.


1258-1258: LGTM: Correctly uses resolved providers throughout snapshot creation.

The changes properly iterate over the resolved provider map and use the selected highest-priority provider for all plugin operations (origin_from_mount_point, check_create_snapshot, create_snapshot).

Also applies to: 1269-1269, 1272-1272, 1296-1296, 1303-1303

snapm/manager/plugins/_plugin.py (5)

13-13: LGTM!

The logging imports are appropriate for the new plugin debug subsystem.

Also applies to: 15-15


48-55: LGTM!

The priority configuration constants are well-documented and follow established naming conventions.


57-68: LGTM!

The logging subsystem setup follows the established pattern in the snapm codebase where subsystem-specific wrappers hard-code the subsystem tag in the extra parameter. Based on learnings, callers should not pass their own extra kwarg to these wrappers.


84-108: LGTM!

Excellent improvement to configuration validation. The try-except blocks properly handle invalid integer values and provide clear warnings including the config file name. This aligns with issue #842's objective to enhance plugin configuration warning logging.


542-542: LGTM!

Correctly exposes PLUGIN_NO_PRIORITY in the public API, consistent with its usage across plugin implementations.

snapm/manager/plugins/lvm2.py (4)

42-42: LGTM!

The import of PLUGIN_NO_PRIORITY is necessary for the priority defaulting logic in both LVM2 plugin classes.


180-184: LGTM!

The static priority values correctly implement issue #837's specification: lvm2-cow=10 and lvm2-thin=15. This ensures that when both plugins can handle a device, the thin-provider is preferred due to its higher priority.


1067-1071: LGTM!

The implementation correctly:

  • Initialises the origins tracking dictionary for limit checking
  • Applies the static default priority when not explicitly configured

This aligns with issues #836 and #837, allowing configuration override whilst providing a sensible default priority.


1381-1385: LGTM!

Consistent implementation with Lvm2Cow, properly initialising the pools tracking dictionary and applying the static default priority when not explicitly configured.

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

Verify that treating priority=0 in configuration as invalid is intentional.
The validation logic at lines 141-145 treats priority == PLUGIN_NO_PRIORITY (which equals 0) as invalid. This means if a user explicitly sets PluginPriority=0 in the configuration, it will be rejected and reset to PLUGIN_NO_PRIORITY.

The validation is SUPPOSED to reject 0. That's an internal value that means "Plugins should define their own static priority". This is completely correct.

The conciseness suggestion I'll take, and add in a future commit.

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: Thank you for the clarification! I understand now—PLUGIN_NO_PRIORITY=0 is intentionally an internal sentinel value that signals plugins should use their own static default priority, so rejecting an explicit PluginPriority=0 configuration is the correct behavior by design.

Appreciate your note about the conciseness suggestion—looking forward to seeing that refinement in a future commit! 👍


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/test_snapm.py (1)

297-315: Clarify ConcreteSnapshot.autoactivate return value

autoactivate currently does a bare return, so evaluates to None. That fulfils the ABC for this test, but is slightly misleading given the documented boolean contract and its use elsewhere (e.g. SnapshotSet.autoactivate uses all(s.autoactivate …)). Returning an explicit False would make the intent clearer and avoid surprises if this helper is reused.

tests/test_plugin.py (1)

43-81: ConcretePlugin correctly exercises the Plugin ABC; tidy up unused argument

The local ConcretePlugin gives you a minimal concrete subclass to test Plugin.info() and the default PLUGIN_NO_PRIORITY, and test_instantiate_base_raises now correctly asserts that the abstract base cannot be instantiated directly.

Ruff’s ARG002 about the unused source parameter in can_snapshot is valid here. If you want to silence it without changing semantics, you could, for example, rename the parameter to _source or assign to _ = source inside the method.

snapm/manager/plugins/_plugin.py (1)

50-57: Plugin limits, priority parsing, and ABC conversion are well‑structured

  • PluginLimits now defensively parses MaxSnapshotsPerOrigin / MaxSnapshotsPerPool and emits clear warnings including the config filename when values are invalid, which should make misconfigured plugins much easier to diagnose.
  • PLUGIN_NO_PRIORITY as a sentinel, combined with the Priority/PluginPriority parsing and validation in Plugin.__init__, cleanly enforces the rule that only positive integers are accepted and that a configured value of 0 is treated as invalid and reset to the sentinel for subclasses to handle.
  • Turning Plugin into an ABC with all lifecycle methods marked @abstractmethod matches how the concrete plugins already behave and lets tests assert that the base cannot be instantiated directly.
  • _log_debug_plugin mirrors the existing subsystem‑specific debug wrappers and correctly tags messages with SNAPM_SUBSYSTEM_PLUGIN; as with the other wrappers, callers should avoid passing their own extra kwargs.

One minor assumption to keep in mind: both PluginLimits and the priority‑parsing path rely on plugin_cfg.plugin_conf_file being set for warning messages. Manager does this in _load_plugin_config, and the tests set it explicitly, but any new code constructing a ConfigParser for plugins will need to follow that pattern.

Also applies to: 58-69, 76-82, 83-110, 121-151, 194-349, 527-547

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e93934 and f910330.

📒 Files selected for processing (6)
  • snapm/_snapm.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/plugins/lvm2.py
  • tests/test_plugin.py
  • tests/test_snapm.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • snapm/manager/plugins/lvm2.py
  • snapm/_snapm.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-11-12T21:27:50.414Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 596
File: snapm/_snapm.py:36-44
Timestamp: 2025-11-12T21:27:50.414Z
Learning: In the snapm codebase, the numeric values of SNAPM_DEBUG_* constants (e.g., SNAPM_DEBUG_MANAGER, SNAPM_DEBUG_MOUNTS, SNAPM_DEBUG_SCHEDULE) are not guaranteed to remain stable across versions. Consumers should always use the symbolic constant names rather than persisting numeric masks. SNAPM_DEBUG_ALL is designed to change over time as new subsystems are added, and repurposing or reassigning bit positions for debug flags is acceptable and intentional.

Applied to files:

  • snapm/_snapm.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/_snapm.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
🧬 Code graph analysis (5)
snapm/_snapm.py (3)
tests/test_snapm.py (5)
  • origin (300-300)
  • devpath (304-304)
  • status (306-306)
  • size (308-308)
  • free (310-310)
snapm/manager/plugins/lvm2.py (7)
  • origin (343-344)
  • devpath (360-363)
  • status (366-375)
  • size (378-380)
  • free (383-384)
  • free (417-420)
  • free (437-439)
snapm/manager/plugins/stratis.py (5)
  • origin (225-226)
  • devpath (239-240)
  • status (243-247)
  • size (250-252)
  • free (255-259)
tests/test_plugin.py (1)
snapm/manager/plugins/_plugin.py (16)
  • Plugin (112-348)
  • discover_snapshots (195-201)
  • can_snapshot (204-212)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
  • rename_snapshot (245-255)
  • check_resize_snapshot (258-268)
  • resize_snapshot (271-281)
  • check_revert_snapshot (284-295)
  • revert_snapshot (298-307)
  • delete_snapshot (310-315)
  • activate_snapshot (318-323)
  • deactivate_snapshot (326-331)
  • set_autoactivate (334-340)
  • origin_from_mount_point (343-348)
  • info (176-180)
snapm/manager/_manager.py (3)
snapm/manager/plugins/_plugin.py (3)
  • origin_from_mount_point (343-348)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
snapm/manager/plugins/lvm2.py (7)
  • origin_from_mount_point (857-868)
  • check_create_snapshot (826-839)
  • check_create_snapshot (1238-1257)
  • check_create_snapshot (1512-1532)
  • create_snapshot (842-855)
  • create_snapshot (1259-1306)
  • create_snapshot (1534-1578)
snapm/manager/plugins/stratis.py (3)
  • origin_from_mount_point (727-735)
  • check_create_snapshot (622-654)
  • create_snapshot (657-725)
snapm/manager/plugins/_plugin.py (3)
snapm/_snapm.py (3)
  • Snapshot (1531-1892)
  • name (1126-1130)
  • name (1621-1625)
tests/test_plugin.py (5)
  • discover_snapshots (45-45)
  • delete_snapshot (61-62)
  • activate_snapshot (63-64)
  • deactivate_snapshot (65-66)
  • set_autoactivate (67-68)
snapm/manager/plugins/lvm2.py (7)
  • discover_snapshots (806-812)
  • discover_snapshots (1073-1124)
  • discover_snapshots (1387-1433)
  • delete_snapshot (870-882)
  • activate_snapshot (973-980)
  • deactivate_snapshot (982-989)
  • set_autoactivate (991-1009)
tests/test_snapm.py (3)
snapm/_snapm.py (14)
  • origin (1659-1662)
  • origin_options (1666-1669)
  • devpath (1716-1719)
  • status (1211-1224)
  • status (1723-1727)
  • SnapStatus (925-948)
  • size (902-922)
  • size (1731-1737)
  • free (1741-1747)
  • autoactivate (1227-1233)
  • autoactivate (1236-1248)
  • autoactivate (1751-1755)
  • autoactivate (1758-1765)
  • invalidate_cache (1875-1878)
snapm/manager/plugins/lvm2.py (10)
  • origin (343-344)
  • origin_options (347-354)
  • devpath (360-363)
  • status (366-375)
  • size (378-380)
  • free (383-384)
  • free (417-420)
  • free (437-439)
  • autoactivate (389-394)
  • invalidate_cache (396-398)
snapm/manager/plugins/stratis.py (8)
  • origin (225-226)
  • origin_options (229-236)
  • devpath (239-240)
  • status (243-247)
  • size (250-252)
  • free (255-259)
  • autoactivate (264-266)
  • invalidate_cache (268-271)
🪛 Ruff (0.14.10)
tests/test_plugin.py

46-46: Unused method argument: source

(ARG002)

snapm/manager/_manager.py

948-948: Avoid specifying long messages outside the exception class

(TRY003)


955-957: Avoid specifying long messages outside the exception class

(TRY003)


991-993: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (4)
snapm/manager/plugins/lvm2.py (1)

42-42: Priority defaults and relaxed can_snapshot() semantics look consistent

The LVM2 plugins’ use of PLUGIN_NO_PRIORITY as a sentinel plus static defaults (LVM2_COW_STATIC_PRIORITY=10, LVM2_THIN_STATIC_PRIORITY=15) integrates cleanly with the new base Plugin priority parsing: invalid/zero config values get normalised back to the sentinel, and subclasses then apply their static defaults.

The relaxed Lvm2Cow.can_snapshot() (dropping the lv_attr‑type filter and only rejecting merging origins) is in line with the goal of supporting more LV types while relying on priorities to prefer thin/Stratis/Btrfs where present. _is_lvm_device() still enforces the device‑mapper major check, so this doesn’t re‑introduce the earlier dmsetup regression. Based on learnings, this behaviour looks correct.

Also applies to: 180-185, 1067-1072, 1126-1152, 1381-1386

snapm/_snapm.py (2)

38-55: Plugin debug flag and subsystem wiring are coherent

SNAPM_DEBUG_PLUGIN and SNAPM_SUBSYSTEM_PLUGIN are correctly added to the mask constants, _DEBUG_MASK_TO_SUBSYSTEM, and __all__. set_debug_mask / get_debug_mask continue to derive mask values from the mapping, so plugin debug output will participate cleanly in the existing subsystem‑filter mechanism.

Also applies to: 56-73, 2309-2372


1531-1756: Snapshot ABC conversion is sound but may affect out‑of‑tree plugins

Turning Snapshot into an ABC and marking origin, origin_options, devpath, status, size, free, autoactivate, and invalidate_cache as abstract matches how the in‑tree plugins already behave and makes the contract explicit. The existing LVM2 and Stratis snapshot classes implement all of these, so internal behaviour remains unchanged.

Do verify whether any external plugins subclass Snapshot without implementing these members; those would now fail to instantiate with a TypeError and may need to be updated accordingly.

Also applies to: 1874-1886

snapm/manager/_manager.py (1)

913-923: Priority-based provider resolution is clear; check any callers relying on _requested_provider

The manager now:

  • Loads per‑plugin config from <name>.conf under plugins.d, annotating the ConfigParser with plugin_conf_file for downstream logging.
  • Collects all candidate providers per source and then, via get_highest_priority, enforces a single highest‑priority provider for each source, raising SnapmNoProviderError if multiple plugins share the maximum priority. That makes misconfigured ties explicit rather than silently picking an arbitrary provider.
  • Wires the resolved provider map through create_snapshot_set, starting a transaction per distinct provider and consistently using the chosen provider for origin probing, pre‑check, and creation.

One thing to verify: _find_and_verify_plugins still accepts _requested_provider but no longer uses it. create_snapshot_set passes None, so there’s no change there, but if any other internal callers were previously relying on constraining the provider set by name, that hint is now effectively ignored and they will get full priority‑based selection instead.

Also applies to: 937-997, 1248-1253, 1258-1274, 1290-1297, 1318-1319

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

any new code constructing a ConfigParser for plugins will need to follow that pattern.

There isn't going to be any new code constructing a ConfigParser for plugins. The whole point is, we do that in exactly one place. That makes it maintainable. Doing otherwise would actively make things worse, and create technical debt.

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: Thank you for the clarification! I understand now—the centralization of ConfigParser construction for plugins in exactly one place (Manager) is an intentional architectural decision that keeps the codebase maintainable and prevents technical debt. Duplicating that pattern elsewhere would indeed work against that goal. I appreciate you explaining the design rationale! 👍

(_/)
(='.'=) taking notes
(")
(")


✏️ Learnings added
Learnt from: bmr-cymru
URL: https://github.com/snapshotmanager/snapm/pull/843

Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

@bmr-cymru bmr-cymru changed the title snapm: btrfs plugin work snapm: btrfs plugin pre-requisite features and fixes Dec 30, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41aa0a9 and 9dc1022.

📒 Files selected for processing (5)
  • doc/user_guide.rst
  • snapm/manager/plugins/_plugin.py
  • tests/__init__.py
  • tests/test_manager.py
  • tests/test_plugin.py
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • doc/user_guide.rst
  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-09-14T17:31:58.457Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Applied to files:

  • doc/user_guide.rst
  • tests/test_manager.py
📚 Learning: 2025-08-27T21:01:32.324Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-27T21:01:32.324Z
Learning: In the snapm project, the design philosophy is to fail fast when expected files (like systemd units or tmpfiles.d configuration) don't exist during installation steps. This is intentional behavior - if these files are missing, it indicates a fundamental issue with the build/test setup that should abort the process immediately rather than continue with an incomplete installation.

Applied to files:

  • doc/user_guide.rst
📚 Learning: 2025-09-24T02:49:21.752Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 490
File: snapm/manager/plugins/lvm2.py:618-629
Timestamp: 2025-09-24T02:49:21.752Z
Learning: When API changes affect return types (like changing from int to tuple), all supporting infrastructure needs updating including mock scripts in tests, not just the call sites that consume the API.

Applied to files:

  • tests/__init__.py
  • tests/test_plugin.py
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • tests/__init__.py
  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-08-26T19:07:42.290Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T19:07:42.290Z
Learning: In the snapm virt_tests module, verify_lvm_setup() is intended as a lightweight sanity check for essential volumes, not comprehensive validation for every storage layout. The responsibility for testing new layout kickstart fragments lies with the developer submitting them. Adding extensive validation for each layout would be redundant and create clutter.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-08-30T13:46:15.300Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-30T13:46:15.300Z
Learning: In snapm, boom is a hard requirement because snapm.manager._boot imports boom modules directly, and the Manager class imports and uses functionality from _boot including BootCache, create_snapset_boot_entry, and delete_snapset_boot_entry. The Manager class cannot be instantiated without boom being available.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-12-13T00:14:20.405Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-13T00:14:20.405Z
Learning: In the snapm codebase, snapshot set names are unique and two mounts cannot exist for the same `Selection(name=...)`. Therefore, `manager.mounts.find_mounts(Selection(name=...))` cannot return multiple mounts.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/manager/plugins/_plugin.py
🧬 Code graph analysis (3)
tests/__init__.py (1)
snapm/manager/plugins/_plugin.py (15)
  • Plugin (112-348)
  • discover_snapshots (195-201)
  • can_snapshot (204-212)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
  • rename_snapshot (245-255)
  • check_resize_snapshot (258-268)
  • resize_snapshot (271-281)
  • check_revert_snapshot (284-295)
  • revert_snapshot (298-307)
  • delete_snapshot (310-315)
  • activate_snapshot (318-323)
  • deactivate_snapshot (326-331)
  • set_autoactivate (334-340)
  • origin_from_mount_point (343-348)
tests/test_plugin.py (2)
tests/__init__.py (1)
  • MockPlugin (61-88)
snapm/manager/plugins/_plugin.py (3)
  • info (176-180)
  • Plugin (112-348)
  • PluginLimits (71-109)
snapm/manager/plugins/_plugin.py (4)
snapm/_snapm.py (3)
  • Snapshot (1531-1892)
  • name (1126-1130)
  • name (1621-1625)
tests/__init__.py (5)
  • discover_snapshots (63-63)
  • delete_snapshot (79-80)
  • activate_snapshot (81-82)
  • deactivate_snapshot (83-84)
  • set_autoactivate (85-86)
snapm/manager/plugins/lvm2.py (7)
  • discover_snapshots (806-812)
  • discover_snapshots (1073-1124)
  • discover_snapshots (1387-1433)
  • delete_snapshot (870-882)
  • activate_snapshot (973-980)
  • deactivate_snapshot (982-989)
  • set_autoactivate (991-1009)
snapm/manager/_manager.py (1)
  • set_autoactivate (1621-1639)
🪛 Ruff (0.14.10)
tests/__init__.py

64-64: Unused method argument: source

(ARG002)

tests/test_manager.py

159-159: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


160-160: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


161-161: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


410-410: Local variable cm is assigned to but never used

Remove assignment to unused variable cm

(F841)


993-993: Unused method argument: source

(ARG002)


1004-1004: Unused method argument: source

(ARG002)


1044-1044: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


1045-1045: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (30)
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: python-basic-tests
🔇 Additional comments (18)
tests/__init__.py (1)

61-89: LGTM! MockPlugin correctly fulfils the ABC contract.

The MockPlugin implementation provides a minimal concrete subclass for testing purposes. All abstract methods are properly implemented with sensible defaults (returning empty lists, False, or raising NotImplementedError).

Minor inconsistency: Line 73 uses name instead of _name for the unused parameter, unlike other methods. This is a trivial nit and does not affect functionality.

tests/test_plugin.py (5)

44-50: LGTM! Test correctly verifies plugin info and default priority.

The test properly sets up a MockPlugin with a ConfigParser and verifies both the info() method output and the default priority value (PLUGIN_NO_PRIORITY).


52-54: LGTM! Correctly verifies Plugin cannot be instantiated directly.

This test ensures the ABC enforcement is working as expected—attempting to instantiate the abstract Plugin class raises TypeError.


187-199: LGTM! Correctly tests static priority override in subclass.

The test demonstrates that a plugin subclass can set its own priority after calling super().__init__(), which is the intended pattern for plugins to provide static default priorities.


201-214: LGTM! Correctly tests configuration-driven priority override.

The test verifies that the [Priority] section with PluginPriority in the ConfigParser correctly sets the plugin's priority, demonstrating the config-driven override mechanism.


216-227: LGTM! Correctly tests PluginLimits error handling.

The test verifies that PluginLimits gracefully handles invalid integer values by falling back to 0, which matches the implementation's error handling behaviour.

doc/user_guide.rst (2)

810-868: LGTM! Comprehensive documentation for plugin priority feature.

The documentation clearly explains the priority system, default values, and how administrators can override priorities via configuration files. The examples are practical and show expected command output.


1580-1608: LGTM! Helpful troubleshooting section for plugin selection issues.

The "Wrong Plugin Selected for Snapshots" troubleshooting guide provides clear, actionable steps for diagnosing and resolving plugin selection problems.

tests/test_manager.py (4)

159-161: LGTM! Test volume sets appropriately reduced.

The reduced volume sets align with PR objective #552 to reduce test-suite runtime whilst maintaining adequate coverage.


403-415: LGTM! Test correctly overrides plugin limits for exhaustion testing.

The changes appropriately override snapshots_per_origin to allow more snapshot creation iterations before the test validates the SnapmNoSpaceError is raised on space exhaustion.


931-974: LGTM! Correctly tests priority-based plugin selection.

The test effectively validates that when multiple plugins can handle the same source, the plugin with the highest numerical priority is selected by _find_and_verify_plugins.


1044-1045: LGTM! Test volume sets appropriately reduced for thin-only tests.

The reduced volume sets for ManagerTestsThin align with the PR objective to reduce test-suite runtime.

snapm/manager/plugins/_plugin.py (6)

49-57: LGTM! Priority configuration constants and sentinel value correctly defined.

The constants for priority configuration parsing and the PLUGIN_NO_PRIORITY sentinel value are appropriately defined and documented.


58-68: LGTM! Logging setup follows established codebase patterns.

The module-level logger and _log_debug_plugin wrapper correctly implement the subsystem-specific debug logging pattern used elsewhere in the codebase.


83-109: LGTM! Robust error handling for PluginLimits configuration parsing.

The try/except blocks properly handle invalid integer values, log warnings with the configuration filename for debugging, and fall back to the default value of 0.


112-150: LGTM! Plugin ABC conversion and priority handling correctly implemented.

The Plugin class is properly converted to an ABC with priority parsing that:

  • Reads from the [Priority] section in plugin config
  • Validates the value is a positive integer (rejecting 0 and negatives per design)
  • Logs warnings with config filename for invalid values
  • Falls back to PLUGIN_NO_PRIORITY (0) for subclasses to set static defaults

This aligns with the design discussion in PR comments where PluginPriority=0 is intentionally invalid because 0 is the internal sentinel for "use plugin's static default".


194-348: LGTM! All plugin lifecycle methods correctly marked as abstract.

All 14 plugin lifecycle methods are properly decorated with @abstractmethod, ensuring concrete plugin implementations must provide implementations. The empty method bodies with docstrings are appropriate for abstract methods.


527-548: LGTM! Public exports updated to include new symbols.

PLUGIN_NO_PRIORITY and PluginLimits are correctly added to __all__, making them part of the public API.

@bmr-cymru
Copy link
Contributor Author

Everything is ready to go, except there is a 7% coverage regression for snapm.manager.plugins.lvm2:

-snapm/manager/plugins/lvm2.py                               658     79    88%   236, 241-242, 246-248, 288, 291, 354, 370, 384, 451, 466, 468, 485, 541, 548, 552-556, 573-574, 599, 648, 658, 696-697, 725-726, 738, 742-743, 747, 755, 761, 802, 812, 823, 839, 855, 862, 866, 879-880, 915-916, 968-969, 1006-1007, 1042, 1086, 1088-1089, 1117, 1148, 1180, 1206, 1231, 1235, 1254, 1287-1288, 1335-1340, 1395, 1397-1398, 1426, 1456, 1486, 1505, 1509, 1529, 1559-1560
+snapm/manager/plugins/lvm2.py                               658    123    81%   236, 241-242, 246-248, 288, 291, 354, 370, 384, 451, 466, 468, 485, 541, 548, 552-556, 573-574, 599, 648, 658, 696-697, 725-726, 738, 742-743, 747, 755, 761, 802, 812, 823, 839, 855, 862, 866, 879-880, 915-916, 968-969, 1006-1007, 1042, 1086, 1088-1089, 1117, 1148, 1180, 1206, 1231, 1235, 1254, 1287-1288, 1335-1340, 1395, 1397-1398, 1426, 1456, 1475-1490, 1504-1510, 1515-1529, 1537-1569, 1581-1587, 1592, 1609

Investigating... Most likely seems commit 41aa0a9, specifically:

diff --git a/tests/test_boot.py b/tests/test_boot.py
index 49185237..cd8cf249 100644
--- a/tests/test_boot.py
+++ b/tests/test_boot.py
@@ -83,7 +83,7 @@ class BootTestsBase(unittest.TestCase):
     Base class for boot tests.
     """
     volumes = ["root", "home", "var"]
-    thin_volumes = ["opt", "srv"]
+    thin_volumes = []
     boot_volumes = [
         ("root", "/"),
         ("home", "/home"),

Every other change in that commit leaves at least one thin volume in play...

@bmr-cymru
Copy link
Contributor Author

Long story short: It's a configuration regression caused by some testing I was doing earlier:

# cat /etc/snapm/plugins.d/lvm2-cow.conf 

[Limits]
MaxSnapshotsPerOrigin = 7

[Priority]
PluginPriority = 18
# snapm plugin list
PluginName PluginVersion PluginType       Priority
lvm2-cow   0.1.0         Lvm2CowSnapshot        18<
lvm2-thin  0.1.0         Lvm2ThinSnapshot       15
stratis    0.1.0         StratisSnapshot        20

The modified priority on the lvm2-cow plugin causes it to take precedence over lvm2-thin, even in the test suite, causing the 7% coverage drop.

The unbearable irony is that the snapset diff command would have shown this clearly, if I had remembered to use it:

root@f42-snapm-vm1:~/src/git/snapm# snapm snapset diff before-upgrade . -s /etc -x "*etc/lvm*"
Gathering paths from before-upgrade /etc: found 9604 paths
Scanned 2919 paths in 0:00:01.114697 (excluded 6685)
Gathering paths from System Root /etc: found 12754 paths
Scanned 2923 paths in 0:00:00.846675 (excluded 9831)
Computing diffs:   0% [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]
Comparing trees for '/etc'
Found 23 differences in 0:00:00.016835
Found 2 moves in 0:00:00.006149
Saved 21 records to diffcache in 0:00:00.001680
Built tree with 23 nodes
/
└── [*] etc
    ├── [*] dpkg
    │   └── [<] dpkg.cfg -> /etc/opt/dpkg.cfg
    ├── [*] fstab
    ├── [*] ld.so.cache
    ├── [<] moved.conf -> /etc/movedto.conf
    ├── [>] movedto.conf <- /etc/moved.conf
    ├── [+] newfile.conf
    ├── [*] opt
    │   └── [>] dpkg.cfg <- /etc/dpkg/dpkg.cfg
    ├── [+] quux
    │   └── [+] foo.conf
    ├── snapm
    │   ├── [*] plugins.d
    │   │   ├── [*] lvm2-cow.conf
    │   │   ├── [*] lvm2-thin.conf
    │   │   └── [*] stratis.conf
    │   └── [*] schedule.d
    │       └── [*] system-daily.json
    ├── [!] system-release
    ├── systemd
    │   └── [*] system
    │       └── [*] timers.target.wants
    └── [*] yum.repos.d
        └── [+] _copr:copr.fedorainfracloud.org:packit:snapshotmanager-snapm-843.repo
root@f42-snapm-vm1:~/src/git/snapm# snapm snapset diff before-upgrade . -o diff -s /etc/snapm/plugins.d
Gathering paths from before-upgrade /etc/snapm/plugins.d: found 4 paths
Scanned 4 paths in 0:00:00.009758 (excluded 0)
Gathering paths from System Root /etc/snapm/plugins.d: found 4 paths
Scanned 4 paths in 0:00:00.001948 (excluded 0)
Found 4 differences in 0:00:00.000576
Found 0 moves in 0:00:00.000073
Saved 4 records to diffcache in 0:00:00.001080
Found 1 content differences
diff a/etc/snapm/plugins.d/lvm2-cow.conf b/etc/snapm/plugins.d/lvm2-cow.conf
--- a/etc/snapm/plugins.d/lvm2-cow.conf 2025-11-13 15:24:04.310452
+++ b/etc/snapm/plugins.d/lvm2-cow.conf 2025-12-30 21:50:19.659313
@@ -1,2 +1,5 @@
 [Limits]
 MaxSnapshotsPerOrigin = 7
+
+[Priority]
+PluginPriority = 18

EPIC FACEPALM

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
tests/test_boot.py (1)

85-87: Thin volume list reduction looks fine; consider addressing RUF012 later

Setting thin_volumes = [] to slim the test setup aligns with the CI/runtime reduction goals and keeps LvmLoopBacked initialisation straightforward. If you want to keep Ruff happy, you could later switch volumes/thin_volumes to tuples or annotate them as ClassVar[...], but that’s purely cosmetic here.

man/man5/snapm-plugins.d.5 (1)

38-44: Priority subsection matches implementation; only tiny wording nits

The new Priority subsection correctly documents PluginPriority as a positive integer override and the “higher wins” behaviour. If you touch this again, you might want to tweak “for, e.g,” to “for example,” for slightly smoother prose, but functionally the docs look accurate.

doc/user_guide.rst (1)

1870-1883: Performance notes correctly characterise thin vs CoW trade‑offs

The new performance notes (“LVM2 thin provisioning generally offers better performance than copy‑on‑write”, separate backstores, monitoring space) are reasonable operational guidance and consistent with how the back‑ends behave. Nothing to change here unless you later want to add explicit caveats for very fragmented thin pools.

snapm/manager/plugins/_plugin.py (1)

112-349: Plugin ABC surface is clear and enforces provider contracts

Turning Plugin into an ABC with explicit @abstractmethod hooks for discovery, capability checks, snapshot lifecycle, resize, revert, activation, and origin resolution is a nice tightening of the provider contract. Existing concrete plugins that already implemented this surface should continue to work, and the new MockPlugin in tests/__init__.py cleanly fulfils the ABC for unit tests.

One small thing to keep in mind is that tests that pass a bare Mock() as plugin_cfg will end up with limits.snapshots_per_* potentially set to Mock instances. You’re not relying on those in tests today, so it’s harmless, but if future tests start asserting on limits it may be worth switching test plugin_cfgs to ConfigParser there as well.

tests/test_scheduler.py (1)

56-59: Reduced volume set keeps coverage while helping test runtime

Limiting thin_volumes to ["opt"] and stratis_volumes to ["fs1"] still exercises mixed‑backend scheduling but cuts down the combinatorial surface, which fits the stated CI/runtime goals.

If you’re planning to fix RUF012 at some point, you could convert these to tuples or ClassVar[List[str]], but that’s purely stylistic in this context.

tests/test_manager.py (2)

9-10: New testing imports are appropriate; one minor unused import

Bringing in Mock, patch, and MockPlugin is appropriate for the new priority tests. The direct Plugin import from snapm.manager.plugins appears unused in this file; leaving it is harmless, but you could drop it in a later clean‑up to avoid confusion.

Also applies to: 19-25


404-416: No‑space test tweak is reasonable but a bit heavy

Overriding p.limits.snapshots_per_origin = 100 for all plugins in test_create_snapshot_set_no_space_raises is a pragmatic way to bypass the logical per‑origin cap and drive the underlying “real no space” path. Bumping the loop to 50 iterations should make hitting the exhaustion condition more deterministic, especially after slimming the volume set.

If this test ever becomes a runtime hotspot, consider lowering the iteration cap and/or constraining it to the LVM2‑CoW provider only, but functionally the change is fine.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4dcd23 and d11a43d.

📒 Files selected for processing (19)
  • doc/user_guide.rst
  • etc/snapm/plugins.d/lvm2-cow.conf
  • etc/snapm/plugins.d/lvm2-thin.conf
  • etc/snapm/plugins.d/stratis.conf
  • man/man5/snapm-plugins.d.5
  • man/man8/snapm.8
  • snapm/_snapm.py
  • snapm/command.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/plugins/lvm2.py
  • snapm/manager/plugins/stratis.py
  • tests/__init__.py
  • tests/test_boot.py
  • tests/test_command.py
  • tests/test_manager.py
  • tests/test_plugin.py
  • tests/test_scheduler.py
  • tests/test_snapm.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • etc/snapm/plugins.d/lvm2-cow.conf
  • etc/snapm/plugins.d/stratis.conf
  • snapm/manager/plugins/lvm2.py
  • man/man8/snapm.8
  • snapm/manager/plugins/stratis.py
  • tests/test_snapm.py
  • etc/snapm/plugins.d/lvm2-thin.conf
🧰 Additional context used
🧠 Learnings (16)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.
📚 Learning: 2025-09-24T02:49:21.752Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 490
File: snapm/manager/plugins/lvm2.py:618-629
Timestamp: 2025-09-24T02:49:21.752Z
Learning: When API changes affect return types (like changing from int to tuple), all supporting infrastructure needs updating including mock scripts in tests, not just the call sites that consume the API.

Applied to files:

  • tests/test_plugin.py
  • tests/__init__.py
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • tests/__init__.py
  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • tests/test_manager.py
  • snapm/_snapm.py
  • snapm/manager/plugins/_plugin.py
  • doc/user_guide.rst
  • snapm/manager/_manager.py
📚 Learning: 2025-08-26T19:07:42.290Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T19:07:42.290Z
Learning: In the snapm virt_tests module, verify_lvm_setup() is intended as a lightweight sanity check for essential volumes, not comprehensive validation for every storage layout. The responsibility for testing new layout kickstart fragments lies with the developer submitting them. Adding extensive validation for each layout would be redundant and create clutter.

Applied to files:

  • tests/test_manager.py
  • tests/test_scheduler.py
  • tests/test_boot.py
📚 Learning: 2025-08-25T17:19:19.563Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
📚 Learning: 2025-08-30T13:46:15.300Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-30T13:46:15.300Z
Learning: In snapm, boom is a hard requirement because snapm.manager._boot imports boom modules directly, and the Manager class imports and uses functionality from _boot including BootCache, create_snapset_boot_entry, and delete_snapset_boot_entry. The Manager class cannot be instantiated without boom being available.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-09-14T17:31:58.457Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-12-13T00:14:20.405Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-13T00:14:20.405Z
Learning: In the snapm codebase, snapshot set names are unique and two mounts cannot exist for the same `Selection(name=...)`. Therefore, `manager.mounts.find_mounts(Selection(name=...))` cannot return multiple mounts.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-11-12T21:27:50.414Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 596
File: snapm/_snapm.py:36-44
Timestamp: 2025-11-12T21:27:50.414Z
Learning: In the snapm codebase, the numeric values of SNAPM_DEBUG_* constants (e.g., SNAPM_DEBUG_MANAGER, SNAPM_DEBUG_MOUNTS, SNAPM_DEBUG_SCHEDULE) are not guaranteed to remain stable across versions. Consumers should always use the symbolic constant names rather than persisting numeric masks. SNAPM_DEBUG_ALL is designed to change over time as new subsystems are added, and repurposing or reassigning bit positions for debug flags is acceptable and intentional.

Applied to files:

  • snapm/_snapm.py
  • snapm/command.py
  • tests/test_command.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/_snapm.py
  • snapm/manager/plugins/_plugin.py
  • tests/test_command.py
📚 Learning: 2025-08-26T17:35:41.366Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:35:41.366Z
Learning: In the snapm virt_tests SnapmTestVM class, the run_command() method defaults to check=True, so explicit check=True parameters should not be added throughout the code. The check=False parameter is used intentionally for specific scenarios like expected-fail file existence checks.

Applied to files:

  • tests/test_command.py
📚 Learning: 2025-08-26T17:36:29.365Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:36:29.365Z
Learning: In the snapm virt_tests SnapmTestVM class, the get_command_output() method already strips whitespace from returned output, so additional .strip() calls on the returned values are unnecessary.

Applied to files:

  • tests/test_command.py
📚 Learning: 2025-08-27T21:01:32.324Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-27T21:01:32.324Z
Learning: In the snapm project, the design philosophy is to fail fast when expected files (like systemd units or tmpfiles.d configuration) don't exist during installation steps. This is intentional behavior - if these files are missing, it indicates a fundamental issue with the build/test setup that should abort the process immediately rather than continue with an incomplete installation.

Applied to files:

  • doc/user_guide.rst
🧬 Code graph analysis (4)
tests/test_plugin.py (3)
tests/__init__.py (1)
  • MockPlugin (61-88)
snapm/manager/plugins/_plugin.py (3)
  • info (176-180)
  • Plugin (112-348)
  • PluginLimits (71-109)
tests/test_manager.py (2)
  • HighPrioPlugin (951-959)
  • LowPrioPlugin (941-949)
tests/__init__.py (1)
snapm/manager/plugins/_plugin.py (15)
  • Plugin (112-348)
  • discover_snapshots (195-201)
  • can_snapshot (204-212)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
  • rename_snapshot (245-255)
  • check_resize_snapshot (258-268)
  • resize_snapshot (271-281)
  • check_revert_snapshot (284-295)
  • revert_snapshot (298-307)
  • delete_snapshot (310-315)
  • activate_snapshot (318-323)
  • deactivate_snapshot (326-331)
  • set_autoactivate (334-340)
  • origin_from_mount_point (343-348)
tests/test_manager.py (4)
snapm/manager/plugins/_plugin.py (4)
  • Plugin (112-348)
  • can_snapshot (204-212)
  • _log_debug (170-174)
  • _log_error (152-156)
tests/__init__.py (2)
  • MockPlugin (61-88)
  • can_snapshot (64-64)
tests/test_plugin.py (2)
  • LowPrioPlugin (209-211)
  • HighPrioPlugin (193-196)
snapm/manager/_manager.py (1)
  • _find_and_verify_plugins (925-996)
snapm/command.py (1)
snapm/report.py (2)
  • FieldType (232-286)
  • report_num (397-410)
🪛 Ruff (0.14.10)
tests/__init__.py

64-64: Unused method argument: source

(ARG002)

tests/test_manager.py

159-159: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


160-160: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


161-161: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


410-410: Local variable cm is assigned to but never used

Remove assignment to unused variable cm

(F841)


993-993: Unused method argument: source

(ARG002)


1004-1004: Unused method argument: source

(ARG002)


1044-1044: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


1045-1045: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_scheduler.py

57-57: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


58-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_command.py

269-269: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


270-270: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


271-271: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

snapm/manager/_manager.py

948-948: Avoid specifying long messages outside the exception class

(TRY003)


955-957: Avoid specifying long messages outside the exception class

(TRY003)


991-993: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_boot.py

86-86: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (27)
doc/user_guide.rst (2)

1063-1160: Plugin priority documentation is consistent with the CLI and core logic

The new “Plugin Priority” and related sections line up well with the code:

  • Default priorities (lvm2‑cow=10, lvm2‑thin=15, stratis=20) match the static priorities used by the plugins.
  • The plugin list example (Priority column and header names) matches _DEFAULT_PLUGIN_FIELDS = "name,version,type,priority" and the priority FieldType in snapm.command.
  • The note that priority values must be positive and that 0/negative values fall back to the plugin’s default priority matches the Plugin initialiser plus per‑plugin static defaults.

No functional mismatches spotted; the section gives users enough detail to reason about and override provider selection.


1838-1865: Troubleshooting section for “Wrong Plugin Selected” is well aligned with priority handling

The “Wrong Plugin Selected for Snapshots” flow sensibly guides users through:

  1. Inspecting current priorities via snapm plugin list.
  2. Checking plugin config files under /etc/snapm/plugins.d/.
  3. Using verbose output to see which plugins can handle a source.
  4. Overriding priority with PluginPriority.

That matches how _find_and_verify_plugins() probes can_snapshot() and then picks the highest‑priority provider, so the suggested steps should map directly to real debugging behaviour.

snapm/command.py (1)

68-69: Priority field and plugin debug flag are wired correctly

  • The new plugin FieldType for priority uses REP_NUM and report_num(d.priority), which matches the Plugin.priority integer field.
  • Updating _DEFAULT_PLUGIN_FIELDS to include priority keeps the snapm plugin list output and the user guide in sync.
  • Adding "plugin": SNAPM_DEBUG_PLUGIN to mask_map lets -d plugin behave consistently with other debug categories.

All of this is cohesive with the rest of the reporting and debug infrastructure.

Also applies to: 450-489, 2399-2415

snapm/manager/plugins/_plugin.py (3)

71-110: PluginLimits validation behaves safely on bad configuration

The extra try/except ValueError around getint() for MaxSnapshotsPerOrigin and MaxSnapshotsPerPool means malformed values are now logged (with filename context via cfg.plugin_conf_file) and cleanly ignored, defaulting to 0 rather than exploding during plugin initialisation. That’s a good balance between strictness and robustness.


111-151: Priority handling matches the documented sentinel semantics

The new PLUGIN_NO_PRIORITY = 0 sentinel and Plugin.__init__ logic:

  • Accepts only strictly positive integer PluginPriority values from the [Priority] section.
  • Treats 0, negatives, and non‑integers as invalid, logs a warning including the config file name, and resets to PLUGIN_NO_PRIORITY.

This is exactly what you described in the PR comments: explicit PluginPriority=0 is rejected and plugins can treat 0 as “no configured priority, apply static default”. The implementation looks sound and should interoperate cleanly with the static defaults in the concrete plugins. Based on learnings, this also keeps config‑filename logging consistent with Manager’s centralised ConfigParser setup.


527-548: Exporting PLUGIN_NO_PRIORITY and PluginLimits from the module is appropriate

Adding PLUGIN_NO_PRIORITY and PluginLimits to __all__ matches their new role as public API used by concrete plugins and tests. No issues here.

tests/test_plugin.py (3)

9-18: Base Plugin and MockPlugin usage in tests correctly reflect the ABC

  • Importing and using MockPlugin from tests to satisfy the Plugin ABC keeps these tests focused on core behaviour without depending on real backends.
  • test_plugin_info and test_instantiate_base_raises accurately capture the expected default name/version and ABC instantiation semantics.

All good here.

Also applies to: 44-55


179-215: Priority tests give good coverage of default, static, and config‑driven cases

The trio of tests:

  • test_default_priority (no config → 0),
  • test_static_priority (subclass overrides self.priority), and
  • test_config_priority_override (INI‑driven PluginPriority),

exercise the key branches in the new priority initialisation logic. They mirror how real plugins will behave (static defaults overridden by config). This gives solid confidence in the sentinel and override behaviour.


216-227: PluginLimits error‑path test matches the new warning/ignore behaviour

test_limits_error_checking verifies that an invalid MaxSnapshotsPerOrigin string leaves snapshots_per_origin at 0, which is exactly what the new PluginLimits error handling implements. That’s a useful regression test for misconfigured plugin limit values.

tests/test_manager.py (3)

153-162: Adjusted volume/thin/Stratis sets sensibly reduce test surface

In both ManagerTests and ManagerTestsThin, narrowing:

  • volumes to ["root", "var"],
  • thin_volumes to ["opt"] (and to ["root", "var"] in the thin‑only suite),
  • stratis_volumes to ["fs1"],

keeps you covering all three provider types while trimming the matrix, which should help with CI duration without losing meaningful behaviour coverage.

Also applies to: 1043-1046


931-975: Priority‑based provider selection test is well‑targeted

test_find_plugins_respects_priority:

  • Correctly patches exists and ismount in snapm.manager._manager, matching how _find_and_verify_plugins() imports them.
  • Injects two MockPlugin subclasses with priorities 10 and 20 that both support the same source.
  • Asserts that the higher‑priority plugin is selected.

This directly exercises the new “highest numerical priority wins” logic and will catch regressions in the selection helper.


976-1034: LVM2 CoW override test accurately models dynamic priority changes

test_lvm2_cow_priority_override neatly simulates:

  • Default behaviour where an lvm2-thin‑like plugin (priority 15) wins over lvm2-cow (priority 10) for the same source.
  • A user raising lvm2-cow’s priority (here to 100) so that it becomes the preferred provider.

The use of @patch on exists/ismount, plus direct manipulation of cow_plugin.priority, mirrors what happens when an admin edits /etc/snapm/plugins.d/lvm2-cow.conf. This gives very clear coverage of the override semantics you’re documenting elsewhere.

tests/test_command.py (2)

209-211: LGTM: Debug list correctly updated to include the new plugin subsystem.

The addition of "plugin" to the debug string ensures the test validates that SNAPM_DEBUG_ALL now encompasses all seven subsystems: manager, command, report, schedule, mounts, fsdiff, and plugin.


269-271: Volume reduction aligns with PR objective to slim test setup.

Reducing volumes from multiple items to single items addresses issue #552 to lower CI and local-run time. The static analysis hints about typing.ClassVar are optional for test code and can be safely deferred.

snapm/manager/_manager.py (5)

69-72: LGTM: TYPE_CHECKING guard for forward reference.

Using TYPE_CHECKING with a forward-declared Plugin type is the standard pattern to avoid circular imports whilst still providing type hints.


913-922: LGTM: Plugin config loading now includes filename for debugging.

The refactored _load_plugin_config correctly computes the per-plugin config path, stores plugin_conf_file on the ConfigParser for warning logging (addressing #842), and only reads the file if it exists.


937-959: LGTM: Priority-based provider selection with proper tie-breaking.

The get_highest_priority function correctly:

  1. Raises ValueError on an empty providers list.
  2. Selects the provider with the highest numerical priority.
  3. Raises SnapmNoProviderError when multiple providers share the maximum priority, preventing ambiguous selection.

This implements the priority mechanism described in #839.


985-996: LGTM: Per-source provider resolution with explicit no-provider error.

The refactored flow correctly verifies each source has at least one provider before selecting the highest-priority one, ensuring clear error messages when no provider is available.


1258-1269: LGTM: Iteration now uses resolved (source, provider) pairs.

The loop correctly iterates over provider_map.items() where each value is now the single resolved provider per source, ensuring all downstream calls use the priority-selected provider.

tests/__init__.py (2)

13-14: LGTM: Import added for plugin ABC access.

The import of snapm.manager.plugins is required to access the Plugin abstract base class for the MockPlugin implementation.


61-89: LGTM: MockPlugin satisfies the Plugin ABC contract.

The MockPlugin class correctly implements all abstract methods from the Plugin ABC:

  • discover_snapshots() returns an empty list.
  • can_snapshot() returns False (the unused source parameter is required by the ABC signature).
  • All other methods raise NotImplementedError, which is appropriate for a minimal test mock.

This enables priority and configuration tests to instantiate a concrete plugin without requiring real storage backends.

snapm/_snapm.py (6)

45-54: LGTM: New plugin debug subsystem added.

The SNAPM_DEBUG_PLUGIN constant (64) follows the established power-of-two pattern, and SNAPM_DEBUG_ALL correctly includes the new flag. This aligns with the PR objective to add a plugin debug subsystem (#841).


63-73: LGTM: Plugin subsystem name and mapping added.

The SNAPM_SUBSYSTEM_PLUGIN constant and its mapping in _DEBUG_MASK_TO_SUBSYSTEM correctly integrate the new plugin debug subsystem with the existing logging infrastructure.


1531-1535: LGTM: Snapshot converted to abstract base class.

Making Snapshot inherit from ABC enforces that plugin implementations must provide concrete subclasses, addressing PR objective #834.


1657-1666: LGTM: Abstract property decorators correctly ordered.

The @property decorator is correctly placed after @abstractmethod, which is the required ordering in Python for abstract properties.


1874-1878: LGTM: invalidate_cache marked as abstract.

Marking invalidate_cache as abstract ensures concrete snapshot implementations must provide cache invalidation logic appropriate to their storage backend.


2355-2356: LGTM: New debug constants exported.

Both SNAPM_DEBUG_PLUGIN and SNAPM_SUBSYSTEM_PLUGIN are correctly added to __all__ for public API exposure.

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

1870-1883: Performance notes correctly characterise thin vs CoW trade‑offs
The new performance notes (“LVM2 thin provisioning generally offers better performance than copy‑on‑write”,

These aren't new. They were committed in September:

commit f4d0a4a249c5c2fdf02bf0a5cc1962d133f781b5
Author: Bryn M. Reeves <bmr@redhat.com>
Date:   Mon Sep 8 22:17:49 2025 +0100

    Update README.md and move detailed guide to doc/user_guide.rst
    
    Resolves: #237
    Resolves: #401
    Resolves: #439
    
    Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1867) Performance Considerations
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1868) --------------------------
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1869) 
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1870) **Snapshot Size Planning**
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1871) 
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1872) * Use ``100%SIZE`` for complete protection but higher space usage
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1873) * Use ``200%USED`` for balance between protection and space efficiency
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1874) * Use ``50%USED`` or ``25%SIZE`` for space-constrained environments
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1875) 
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1876) **Storage Performance**
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1877) 
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1878) * LVM2 thin provisioning generally offers better performance than
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1879)   copy-on-write
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1880) * Consider separate storage for snapshot backstores in high-I/O
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1881)   environments
f4d0a4a24 (Bryn M. Reeves 2025-09-08 22:17:49 +0100 1882) * Monitor snapshot space usage to prevent exhaustion

Where are you getting that idea from?

We should have added this a long time ago...

Resolves: #841

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
While we're there also add proper error checking and warnings to
PluginLimits.

Resolves: #842

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Now that plugins expose a priority value have the Manager class use it
when selecting plugins via _fimd_and_verify_plugins(): just choose the
candidate plugin with the greatest numerical priority value.

Resolves: #839

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
The Lvm2Cow plugin is overly conservative about what it can handle
incan_snapshot(): the underlying dm-snapshot target and the LVM2 tools
allow snapshots of linear, thin, mirror, raid volumes etc. This was done
originally to prevent lvm2-cow greedily attempting to snapshot thin
devices (where thin snapshots are generally preferred).

With plugin priorities in place this can now be relaxed, allowing the
plugin to service a wider range of device types:

  root@f42-snapm-vm1:~/src/git/snapm# snapm snapshot list
  SnapsetName    Name                                              Origin           Source  Status  Size     Free     Autoactivate Provider
  prio-test      fedora/root-snapset_prio-test_1767033618_-        /dev/fedora/root /       Active  512.0MiB 511.8MiB yes          lvm2-cow
  prio-test      fedora/var-snapset_prio-test_1767033618_-var      /dev/fedora/var  /var    Active  512.0MiB 511.6MiB yes          lvm2-cow
  prio-test      fedora/home-snapset_prio-test_1767033618_-home    /dev/fedora/home /home   Active    1.0GiB   1.9GiB yes          lvm2-thin # <<< thin snap for thin vol by default, as expected.
  root@f42-snapm-vm1:~/src/git/snapm# snapm snapset delete prio-test
  root@f42-snapm-vm1:~/src/git/snapm# vim /etc/snapm/plugins.d/lvm2-cow.conf  # Twiddle lvm2-cow.conf PluginPriority to be > lvm2-thin static priority
  root@f42-snapm-vm1:~/src/git/snapm# snapm snapset create -br --size-policy=2%SIZE prio-test /home / /var
  SnapsetName:      prio-test
  Sources:          /home, /, /var
  NrSnapshots:      3
  Time:             2025-12-29 18:40:58
  UUID:             b86f280f-6874-512d-8b5a-2798380c3dfb
  Status:           Active
  Autoactivate:     yes
  OriginMounted:    yes
  Mounted:          no
  Bootable:         yes
  BootEntries:
    SnapshotEntry:  38da7dc
    RevertEntry:    458d870
  root@f42-snapm-vm1:~/src/git/snapm# snapm snapshot list
  SnapsetName    Name                                              Origin           Source  Status  Size     Free     Autoactivate Provider
  prio-test      fedora/home-snapset_prio-test_1767033658_-home    /dev/fedora/home /home   Active  512.0MiB 512.0MiB yes          lvm2-cow
  prio-test      fedora/root-snapset_prio-test_1767033658_-        /dev/fedora/root /       Active  512.0MiB 511.8MiB yes          lvm2-cow
  prio-test      fedora/var-snapset_prio-test_1767033658_-var      /dev/fedora/var  /var    Active  512.0MiB 511.5MiB yes          lvm2-cow  # <<< thick snapshot for thin volume, because that's what the user asked for.

Resolves: #838

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
coderabbitai[bot]
coderabbitai bot previously requested changes Dec 30, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
snapm/_snapm.py (1)

1531-1879: Snapshot ABC contract is clear; ensure all concrete snapshots implement it

Turning Snapshot into an ABC with abstract origin, origin_options, devpath, status, size, free, autoactivate and invalidate_cache() tightens the contract and keeps the base logic (e.g. source, origin_mounted, snapshot_mounted, autoactivate setter, resize/revert helpers) provider‑agnostic. Just ensure every in‑tree snapshot implementation (and any out‑of‑tree plugins) now implements these members as callables/properties with the expected semantics, particularly invalidate_cache() as a method that can be invoked from the base class.

snapm/manager/plugins/_plugin.py (1)

112-151: Plugin ABC and priority parsing match the intended semantics

Making Plugin an ABC and marking all lifecycle methods as @abstractmethod clearly enforces the provider contract, and storing self.priority from the config only when it’s a positive integer (otherwise resetting to PLUGIN_NO_PRIORITY and warning) lines up with the “0 = internal sentinel, not a valid explicit value” design. The tests using MockPlugin/subclasses cover the default, static override, and config‑override paths; you might optionally add a test for explicit PluginPriority = 0 and negative values to lock that validation behaviour in.

Also applies to: 194-345, 527-548

tests/test_plugin.py (1)

9-10: Plugin tests now exercise ABC, priority semantics, and limits parsing well

Switching to MockPlugin for test_plugin_info, adding test_instantiate_base_raises, and the new tests for default/static/config priority and invalid PluginLimits values give good coverage of the new Plugin contract and config parsing. You might optionally add a small test for PluginPriority = 0 and a malformed MaxSnapshotsPerPool to pin down those edge cases too, but the critical paths are well covered.

Also applies to: 17-18, 45-55, 179-227

tests/test_manager.py (2)

403-410: Minor clean‑ups possible in no‑space test

The logic of forcing a SnapmNoSpaceError by relaxing per‑origin limits and iterating more snapshot creates is sound, but there are a couple of small tidy‑ups you might consider:

  • cm from with self.assertRaises(... ) as cm: is never used; you can drop the as cm binding.
  • self._lvm.dump_lvs() inside the inner loop will generate a lot of log output when the test passes; if you only need it for debugging failures, moving it behind a flag or calling it once on failure would keep normal runs quieter.

These are non‑functional nits and can be deferred.

Example diff to drop unused context variable
-        with self.assertRaises(snapm.SnapmNoSpaceError) as cm:
+        with self.assertRaises(snapm.SnapmNoSpaceError):
             for i in range(0, 50):
                 self.manager.create_snapshot_set(
                     f"testset{i}", self.mount_points()
                 )
                 self._lvm.dump_lvs()

930-1034: Priority selection tests are solid; consider adding tie‑priority coverage

The new tests:

  • test_find_plugins_respects_priority – verifies that, for a single source handled by two providers, the highest‑priority plugin is selected.
  • test_lvm2_cow_priority_override – exercises a realistic thin‑vs‑CoW scenario and shows that raising lvm2-cow’s priority at runtime causes it to win over lvm2-thin.

These both track the manager’s _find_and_verify_plugins behaviour well.

Two optional improvements you might consider:

  • Add a small test that sets up two providers with the same highest priority for a source and asserts that SnapmNoProviderError is raised, so the failure mode of the tie‑break logic is explicitly covered.
  • When instantiating the test plugins, passing an actual ConfigParser() (or a thin wrapper) rather than a bare Mock() for plugin_cfg would decouple the tests from the internals of Plugin.__init__ if its interaction with ConfigParser becomes stricter in future.

Both are nice‑to‑haves rather than blockers.

Sketch of an extra tie‑priority test
@patch("snapm.manager._manager.exists", return_value=True)
@patch("snapm.manager._manager.ismount", return_value=True)
def test_find_plugins_priority_tie_raises(self, _ismount_mock, _exists_mock):
    source_path = "/dev/test/vol"

    class P1(MockPlugin):
        name = "p1"
        def __init__(self, logger, plugin_cfg):
            super().__init__(logger, plugin_cfg)
            self.priority = 10
        def can_snapshot(self, _source): return True

    class P2(MockPlugin):
        name = "p2"
        def __init__(self, logger, plugin_cfg):
            super().__init__(logger, plugin_cfg)
            self.priority = 10
        def can_snapshot(self, _source): return True

    self.manager.plugins = [P1(Mock(), ConfigParser()), P2(Mock(), ConfigParser())]
    sources = [source_path]
    size_policies = {source_path: None}

    with self.assertRaises(snapm.SnapmNoProviderError):
        self.manager._find_and_verify_plugins(sources, size_policies)
tests/__init__.py (1)

13-24: Centralised MockPlugin helper cleanly fulfils the Plugin ABC

Pulling in snapm.manager.plugins and defining MockPlugin as a minimal concrete subclass gives the tests a single, consistent implementation of the Plugin ABC. The methods that should never be called in these tests correctly raise NotImplementedError, while discover_snapshots/can_snapshot provide harmless defaults that specific tests can override.

If you want to silence Ruff’s unused‑argument warning for the base can_snapshot, you could rename its parameter to _source, but that’s purely cosmetic.

Also applies to: 61-88

snapm/manager/_manager.py (1)

925-997: Remove the unused _requested_provider parameter from _find_and_verify_plugins

The _requested_provider parameter is defined in the method signature but is never referenced in the method body. All callers (test and production) either omit it entirely or explicitly pass None. The method's docstring does not document this parameter, creating a signature–docstring mismatch. Remove the parameter and update the docstring accordingly to reflect the current behaviour: the method collects all candidate plugins per source and selects the highest-priority provider, with ties raising SnapmNoProviderError.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d11a43d and ad76353.

📒 Files selected for processing (19)
  • doc/user_guide.rst
  • etc/snapm/plugins.d/lvm2-cow.conf
  • etc/snapm/plugins.d/lvm2-thin.conf
  • etc/snapm/plugins.d/stratis.conf
  • man/man5/snapm-plugins.d.5
  • man/man8/snapm.8
  • snapm/_snapm.py
  • snapm/command.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/plugins/lvm2.py
  • snapm/manager/plugins/stratis.py
  • tests/__init__.py
  • tests/test_boot.py
  • tests/test_command.py
  • tests/test_manager.py
  • tests/test_plugin.py
  • tests/test_scheduler.py
  • tests/test_snapm.py
✅ Files skipped from review due to trivial changes (1)
  • etc/snapm/plugins.d/stratis.conf
🚧 Files skipped from review as they are similar to previous changes (6)
  • man/man8/snapm.8
  • etc/snapm/plugins.d/lvm2-cow.conf
  • etc/snapm/plugins.d/lvm2-thin.conf
  • snapm/manager/plugins/lvm2.py
  • snapm/manager/plugins/stratis.py
  • man/man5/snapm-plugins.d.5
🧰 Additional context used
🧠 Learnings (16)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.
📚 Learning: 2025-11-12T21:27:50.414Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 596
File: snapm/_snapm.py:36-44
Timestamp: 2025-11-12T21:27:50.414Z
Learning: In the snapm codebase, the numeric values of SNAPM_DEBUG_* constants (e.g., SNAPM_DEBUG_MANAGER, SNAPM_DEBUG_MOUNTS, SNAPM_DEBUG_SCHEDULE) are not guaranteed to remain stable across versions. Consumers should always use the symbolic constant names rather than persisting numeric masks. SNAPM_DEBUG_ALL is designed to change over time as new subsystems are added, and repurposing or reassigning bit positions for debug flags is acceptable and intentional.

Applied to files:

  • snapm/command.py
  • snapm/_snapm.py
  • tests/test_command.py
📚 Learning: 2025-09-24T02:49:21.752Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 490
File: snapm/manager/plugins/lvm2.py:618-629
Timestamp: 2025-09-24T02:49:21.752Z
Learning: When API changes affect return types (like changing from int to tuple), all supporting infrastructure needs updating including mock scripts in tests, not just the call sites that consume the API.

Applied to files:

  • tests/test_plugin.py
  • tests/__init__.py
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • doc/user_guide.rst
  • snapm/manager/plugins/_plugin.py
  • tests/test_manager.py
  • snapm/manager/_manager.py
📚 Learning: 2025-09-14T17:31:58.457Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Applied to files:

  • doc/user_guide.rst
  • tests/test_manager.py
📚 Learning: 2025-08-25T17:19:19.563Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Applied to files:

  • doc/user_guide.rst
  • tests/test_manager.py
  • tests/test_command.py
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • doc/user_guide.rst
  • tests/test_manager.py
📚 Learning: 2025-08-27T21:01:32.324Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-27T21:01:32.324Z
Learning: In the snapm project, the design philosophy is to fail fast when expected files (like systemd units or tmpfiles.d configuration) don't exist during installation steps. This is intentional behavior - if these files are missing, it indicates a fundamental issue with the build/test setup that should abort the process immediately rather than continue with an incomplete installation.

Applied to files:

  • doc/user_guide.rst
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • tests/__init__.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/plugins/_plugin.py
  • tests/test_manager.py
  • snapm/manager/_manager.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/manager/plugins/_plugin.py
  • snapm/_snapm.py
  • tests/test_command.py
📚 Learning: 2025-08-30T13:46:15.300Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-30T13:46:15.300Z
Learning: In snapm, boom is a hard requirement because snapm.manager._boot imports boom modules directly, and the Manager class imports and uses functionality from _boot including BootCache, create_snapset_boot_entry, and delete_snapset_boot_entry. The Manager class cannot be instantiated without boom being available.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-08-26T19:07:42.290Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T19:07:42.290Z
Learning: In the snapm virt_tests module, verify_lvm_setup() is intended as a lightweight sanity check for essential volumes, not comprehensive validation for every storage layout. The responsibility for testing new layout kickstart fragments lies with the developer submitting them. Adding extensive validation for each layout would be redundant and create clutter.

Applied to files:

  • tests/test_manager.py
  • tests/test_boot.py
  • tests/test_command.py
  • tests/test_scheduler.py
📚 Learning: 2025-12-13T00:14:20.405Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-13T00:14:20.405Z
Learning: In the snapm codebase, snapshot set names are unique and two mounts cannot exist for the same `Selection(name=...)`. Therefore, `manager.mounts.find_mounts(Selection(name=...))` cannot return multiple mounts.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-08-26T17:35:41.366Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:35:41.366Z
Learning: In the snapm virt_tests SnapmTestVM class, the run_command() method defaults to check=True, so explicit check=True parameters should not be added throughout the code. The check=False parameter is used intentionally for specific scenarios like expected-fail file existence checks.

Applied to files:

  • tests/test_command.py
📚 Learning: 2025-08-26T17:36:29.365Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:36:29.365Z
Learning: In the snapm virt_tests SnapmTestVM class, the get_command_output() method already strips whitespace from returned output, so additional .strip() calls on the returned values are unnecessary.

Applied to files:

  • tests/test_command.py
🧬 Code graph analysis (5)
snapm/command.py (1)
snapm/report.py (2)
  • FieldType (232-286)
  • report_num (397-410)
tests/test_snapm.py (3)
snapm/_snapm.py (14)
  • origin (1659-1662)
  • origin_options (1666-1669)
  • devpath (1716-1719)
  • status (1211-1224)
  • status (1723-1727)
  • SnapStatus (925-948)
  • size (902-922)
  • size (1731-1737)
  • free (1741-1747)
  • autoactivate (1227-1233)
  • autoactivate (1236-1248)
  • autoactivate (1751-1755)
  • autoactivate (1758-1765)
  • invalidate_cache (1875-1878)
snapm/manager/plugins/lvm2.py (10)
  • origin (343-344)
  • origin_options (347-354)
  • devpath (360-363)
  • status (366-375)
  • size (378-380)
  • free (383-384)
  • free (417-420)
  • free (437-439)
  • autoactivate (389-394)
  • invalidate_cache (396-398)
snapm/manager/plugins/stratis.py (8)
  • origin (225-226)
  • origin_options (229-236)
  • devpath (239-240)
  • status (243-247)
  • size (250-252)
  • free (255-259)
  • autoactivate (264-266)
  • invalidate_cache (268-271)
tests/__init__.py (1)
snapm/manager/plugins/_plugin.py (15)
  • Plugin (112-348)
  • discover_snapshots (195-201)
  • can_snapshot (204-212)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
  • rename_snapshot (245-255)
  • check_resize_snapshot (258-268)
  • resize_snapshot (271-281)
  • check_revert_snapshot (284-295)
  • revert_snapshot (298-307)
  • delete_snapshot (310-315)
  • activate_snapshot (318-323)
  • deactivate_snapshot (326-331)
  • set_autoactivate (334-340)
  • origin_from_mount_point (343-348)
tests/test_manager.py (4)
tests/__init__.py (2)
  • MockPlugin (61-88)
  • can_snapshot (64-64)
tests/test_plugin.py (2)
  • LowPrioPlugin (209-211)
  • HighPrioPlugin (193-196)
snapm/manager/plugins/lvm2.py (3)
  • can_snapshot (814-823)
  • can_snapshot (1126-1152)
  • can_snapshot (1435-1462)
snapm/manager/_manager.py (1)
  • _find_and_verify_plugins (925-996)
snapm/manager/_manager.py (3)
snapm/manager/plugins/_plugin.py (5)
  • Plugin (112-348)
  • _log_debug (170-174)
  • origin_from_mount_point (343-348)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
snapm/manager/plugins/lvm2.py (7)
  • origin_from_mount_point (857-868)
  • check_create_snapshot (826-839)
  • check_create_snapshot (1238-1257)
  • check_create_snapshot (1512-1532)
  • create_snapshot (842-855)
  • create_snapshot (1259-1306)
  • create_snapshot (1534-1578)
snapm/manager/plugins/stratis.py (3)
  • origin_from_mount_point (727-735)
  • check_create_snapshot (622-654)
  • create_snapshot (657-725)
🪛 Ruff (0.14.10)
tests/__init__.py

64-64: Unused method argument: source

(ARG002)

tests/test_manager.py

158-158: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


159-159: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


160-160: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


409-409: Local variable cm is assigned to but never used

Remove assignment to unused variable cm

(F841)


992-992: Unused method argument: source

(ARG002)


1003-1003: Unused method argument: source

(ARG002)


1043-1043: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


1044-1044: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

snapm/manager/_manager.py

948-948: Avoid specifying long messages outside the exception class

(TRY003)


955-957: Avoid specifying long messages outside the exception class

(TRY003)


991-993: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_boot.py

86-86: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_command.py

269-269: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


270-270: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


271-271: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_scheduler.py

57-57: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


58-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (11)
snapm/_snapm.py (1)

38-73: Plugin debug subsystem wiring looks consistent

Adding SNAPM_DEBUG_PLUGIN to the mask, SNAPM_SUBSYSTEM_PLUGIN to the subsystem names, updating _DEBUG_MASK_TO_SUBSYSTEM, and exporting both in __all__ is coherent with the existing debug design; get_debug_mask()/set_debug_mask() will naturally include the new plugin subsystem without further changes.

Also applies to: 2349-2372

snapm/manager/plugins/_plugin.py (1)

71-110: PluginLimits error handling for invalid integers is sound

Catching ValueError from getint() and falling back to get() purely for logging, while leaving the limit at its default of 0, gives robust behaviour for malformed configs. Logging the bad value together with cfg.plugin_conf_file should make diagnosing misconfigurations straightforward, provided plugin_conf_file is always set by the manager as per the architecture notes.

tests/test_scheduler.py (1)

56-59: Slimmed volume sets align with faster device‑backed tests

Reducing thin_volumes to ["opt"] and stratis_volumes to ["fs1"] should cut setup time while still providing enough distinct mount points for the scheduler tests that index into mount_points()[0] and [1]. No behavioural issues are apparent.

tests/test_boot.py (1)

80-88: Removing thin volumes from boot tests is safe and reduces overhead

Setting BootTestsBase.thin_volumes = [] stops provisioning unused thin‑provisioned volumes while the tests only exercise boot_volumes mapped from "root", "home" and "var". This looks like a straightforward runtime win with no change in test coverage.

snapm/command.py (1)

68-69: CLI wiring for plugin priority and plugin debug flag looks correct

Importing SNAPM_DEBUG_PLUGIN, exposing a numeric priority FieldType in _plugin_fields, extending _DEFAULT_PLUGIN_FIELDS to "name,version,type,priority", and teaching set_debug() about the "plugin" token integrate the new plugin‑priority and debug subsystem cleanly into the CLI and report layer.

Also applies to: 450-490, 2406-2415

doc/user_guide.rst (2)

1063-1160: Plugin priority behaviour and field docs look consistent with implementation

The description of default priorities, higher‑wins semantics, and validation of non‑positive values matches the new manager/plugin logic and the intended “0 = sentinel, fall back to static default” behaviour. The plugin list examples and priority field description are clear and should be very helpful when tuning installations.


1838-1865: Troubleshooting flow for “wrong plugin selected” is clear and actionable

The new troubleshooting steps (inspect priorities, configs, verbose snapset create, then override via /etc/snapm/plugins.d) line up with the priority system and Manager’s provider selection, and give users a practical way to debug and correct mis-selection.

tests/test_manager.py (1)

158-160: Slimmed device matrices still cover mixed-provider paths

Reducing volumes, thin_volumes, and stratis_volumes in ManagerTests and ManagerTestsThin keeps coverage of CoW, thin, and Stratis paths while materially cutting setup cost, which should help with test runtime without weakening behaviour coverage.

Also applies to: 1042-1045

tests/test_command.py (1)

205-212: Debug flag expectations and fixture slimming align with the new plugin subsystem

Including "plugin" in test_set_debug_list’s comma‑separated mask while still asserting SNAPM_DEBUG_ALL keeps this test aligned with the new plugin debug category and the documented “use symbolic names, not raw masks” guidance. The reduced volumes/thin_volumes/stratis_volumes set in CommandTests mirrors the manager tests and should help shorten CLI‑layer test runs without reducing meaningful coverage.

Also applies to: 269-271

snapm/manager/_manager.py (2)

904-923: Plugin config loading and logging look robust

Using <plugin_name>.conf under _PLUGINS_D_PATH, attaching plugin_conf_file to the ConfigParser for downstream logging, and guarding the read() call with an exists() check makes plugin configuration loading explicit and keeps error messages nicely contextualised per plugin. No issues spotted here.


357-410: Shared runtime-dir helper _check_snapm_dir hardens lock/mount directory handling

The new _check_snapm_dir centralises creation/verification of runtime directories, rejects symlinks, and normalises permissions if they drift from the expected mode. Reusing it from _check_lock_dir and _check_mounts_dir is a solid refactor and aligns with the project’s “fail fast on setup problems” philosophy.

Resolves: #834

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Similar to #834 - let's do this for the Plugin class too.

Resolves: #835

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Cut the number of volumes used in device-backed tests to improve the
test suite runtime: on f42-snapm-vm1 this reduces the runtime from ~33m
to 27m.

Resolves: #552

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #845

Assisted-by: Google Gemini https://gemini.google.com
Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #846

Assisted-by: Anthropic Claude https://claude.ai
Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
It's vestigial from the block device work: let's clean it up before
Btrfs goes in.

Resolves: #850

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
snapm/manager/_manager.py (1)

1248-1319: Fix _find_and_verify_plugins call-site to match new signature

_find_and_verify_plugins now only accepts (self, sources, size_policies), but create_snapshot_set() still passes a third positional argument (None), which will raise a TypeError at runtime when creating a snapshot set.

Proposed fix: drop the vestigial argument
-        # Initialise provider mapping.
-        provider_map = self._find_and_verify_plugins(sources, size_policies, None)
+        # Initialise provider mapping.
+        provider_map = self._find_and_verify_plugins(sources, size_policies)

Once this is adjusted, the subsequent use of provider_map (iterating items() and using the resolved provider per source) is consistent with the new priority-resolution behaviour.

🧹 Nitpick comments (7)
scripts/setpaths.sh (1)

35-37: Add directory existence check for consistency.

The boom-boot PATH modification (lines 29-33) checks for directory existence before updating, but the PYTHONPATH modification does not. Whilst adding a non-existent directory to PYTHONPATH is harmless, wrapping this block in the same existence check would maintain consistency.

🔎 Suggested refactor
-if ! [[ "$PYTHONPATH" =~ "$PWD/../boom-boot:" ]]; then
-    export PYTHONPATH="$PWD/../boom-boot:$PYTHONPATH"
-fi
+if ! [[ "$PYTHONPATH" == *"$PWD/../boom-boot:"* ]]; then
+    if [ -d "$PWD/../boom-boot" ]; then
+        export PYTHONPATH="$PWD/../boom-boot:$PYTHONPATH"
+    fi
+fi

Note: This also includes the glob pattern fix from the previous comment.

tests/test_boot.py (1)

85-91: Slimming boot test thin volumes looks fine; ClassVar annotation is optional

Setting thin_volumes = [] here to avoid creating thin-provisioned volumes in the boot tests is reasonable given thin coverage elsewhere. If you run Ruff with RUF012 enabled across tests, you may want to annotate volumes, thin_volumes, and boot_volumes as typing.ClassVar to silence the “mutable class attribute” warning, but it’s not functionally significant.

tests/test_scheduler.py (1)

56-59: Reduced fixture volumes keep coverage while speeding scheduler tests

Limiting thin_volumes to ["opt"] and stratis_volumes to ["fs1"] still exercises both thin and Stratis scheduling paths while cutting device setup cost, which matches the CI runtime objectives. If Ruff’s RUF012 is enabled for tests, you could annotate these lists as ClassVar to avoid “mutable class attribute” warnings, but it’s not functionally required.

tests/test_manager.py (3)

152-161: Slimmed Manager test fixtures maintain coverage while reducing setup cost

Reducing volumes, thin_volumes, and stratis_volumes in ManagerTests and ManagerTestsThin keeps LVM2 COW, LVM2 thin, and Stratis paths exercised while cutting the number of loop-backed devices, which should help test runtime. If you run Ruff with RUF012 enabled on tests, you may want to annotate these lists as ClassVar to silence “mutable class attribute” warnings, but that’s optional.

Also applies to: 1068-1071


403-413: Consider making the no-space test less iteration-heavy or confirm it remains stable

Overriding snapshots_per_origin to 100 avoids PluginLimits interfering, and looping up to 50 snapshot-set creations should reliably trigger SnapmNoSpaceError. That said, this many real snapshot creations can be relatively expensive and might behave differently on environments with unusually generous backing storage; if CI ever becomes slow or flaky here, consider driving the error more deterministically (e.g. via smaller test VG size or a dedicated low-limit config) instead of a high iteration count.


929-1059: Priority-selection tests are solid; clean up minor static-analysis nits

The new tests for _find_and_verify_plugins correctly exercise:

  • higher-priority provider winning when multiple plugins can snapshot the same source,
  • dynamic override of a lower-priority plugin to outrank a higher-priority one, and
  • tie detection raising SnapmNoProviderError.

To keep Ruff happy without changing behaviour, you could:

  • rename the unused source argument in Lvm2Thin.can_snapshot / Lvm2Cow.can_snapshot and the tie-test classes to _source (or add del source in the body), and
  • drop the unused mock_logger assignment in test_find_plugins_priority_tie_raises.

These are purely cosmetic and won’t affect the validity of the tests.

tests/test_command.py (1)

269-271: Command test fixture volume slimming is fine; ClassVar for lists is optional

Using only ["root"], ["opt"], and ["fs1"] in CommandTests keeps the CLI exercising all provider types while reducing loop-backed setup work, consistent with the broader test-slimming in this PR. As with other fixtures, you can optionally annotate these list attributes as ClassVar if you want to silence Ruff’s RUF012 warning about mutable class attributes.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad76353 and a81ef3a.

📒 Files selected for processing (12)
  • doc/user_guide.rst
  • scripts/setpaths.sh
  • snapm/_snapm.py
  • snapm/manager/_manager.py
  • snapm/manager/plugins/_plugin.py
  • tests/__init__.py
  • tests/test_boot.py
  • tests/test_command.py
  • tests/test_manager.py
  • tests/test_plugin.py
  • tests/test_scheduler.py
  • tests/test_snapm.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/init.py
  • tests/test_snapm.py
🧰 Additional context used
🧠 Learnings (16)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
  • snapm/manager/_manager.py
📚 Learning: 2025-08-25T17:19:19.563Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
  • tests/test_command.py
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
📚 Learning: 2025-08-30T13:46:15.300Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-30T13:46:15.300Z
Learning: In snapm, boom is a hard requirement because snapm.manager._boot imports boom modules directly, and the Manager class imports and uses functionality from _boot including BootCache, create_snapset_boot_entry, and delete_snapset_boot_entry. The Manager class cannot be instantiated without boom being available.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-09-14T17:31:58.457Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Applied to files:

  • tests/test_manager.py
  • doc/user_guide.rst
📚 Learning: 2025-08-26T19:07:42.290Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T19:07:42.290Z
Learning: In the snapm virt_tests module, verify_lvm_setup() is intended as a lightweight sanity check for essential volumes, not comprehensive validation for every storage layout. The responsibility for testing new layout kickstart fragments lies with the developer submitting them. Adding extensive validation for each layout would be redundant and create clutter.

Applied to files:

  • tests/test_manager.py
  • tests/test_boot.py
  • tests/test_scheduler.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • tests/test_manager.py
  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-12-13T00:14:20.405Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-13T00:14:20.405Z
Learning: In the snapm codebase, snapshot set names are unique and two mounts cannot exist for the same `Selection(name=...)`. Therefore, `manager.mounts.find_mounts(Selection(name=...))` cannot return multiple mounts.

Applied to files:

  • tests/test_manager.py
📚 Learning: 2025-08-27T21:01:32.324Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-27T21:01:32.324Z
Learning: In the snapm project, the design philosophy is to fail fast when expected files (like systemd units or tmpfiles.d configuration) don't exist during installation steps. This is intentional behavior - if these files are missing, it indicates a fundamental issue with the build/test setup that should abort the process immediately rather than continue with an incomplete installation.

Applied to files:

  • doc/user_guide.rst
📚 Learning: 2025-11-12T21:27:50.414Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 596
File: snapm/_snapm.py:36-44
Timestamp: 2025-11-12T21:27:50.414Z
Learning: In the snapm codebase, the numeric values of SNAPM_DEBUG_* constants (e.g., SNAPM_DEBUG_MANAGER, SNAPM_DEBUG_MOUNTS, SNAPM_DEBUG_SCHEDULE) are not guaranteed to remain stable across versions. Consumers should always use the symbolic constant names rather than persisting numeric masks. SNAPM_DEBUG_ALL is designed to change over time as new subsystems are added, and repurposing or reassigning bit positions for debug flags is acceptable and intentional.

Applied to files:

  • snapm/_snapm.py
  • tests/test_command.py
📚 Learning: 2025-10-10T19:18:28.912Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Applied to files:

  • snapm/_snapm.py
  • tests/test_command.py
  • snapm/manager/plugins/_plugin.py
📚 Learning: 2025-08-26T17:35:41.366Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:35:41.366Z
Learning: In the snapm virt_tests SnapmTestVM class, the run_command() method defaults to check=True, so explicit check=True parameters should not be added throughout the code. The check=False parameter is used intentionally for specific scenarios like expected-fail file existence checks.

Applied to files:

  • tests/test_command.py
📚 Learning: 2025-08-26T17:36:29.365Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T17:36:29.365Z
Learning: In the snapm virt_tests SnapmTestVM class, the get_command_output() method already strips whitespace from returned output, so additional .strip() calls on the returned values are unnecessary.

Applied to files:

  • tests/test_command.py
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • snapm/manager/plugins/_plugin.py
  • snapm/manager/_manager.py
📚 Learning: 2025-09-24T02:49:21.752Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 490
File: snapm/manager/plugins/lvm2.py:618-629
Timestamp: 2025-09-24T02:49:21.752Z
Learning: When API changes affect return types (like changing from int to tuple), all supporting infrastructure needs updating including mock scripts in tests, not just the call sites that consume the API.

Applied to files:

  • tests/test_plugin.py
🧬 Code graph analysis (3)
tests/test_manager.py (2)
tests/test_plugin.py (2)
  • LowPrioPlugin (209-211)
  • HighPrioPlugin (193-196)
snapm/manager/_manager.py (1)
  • _find_and_verify_plugins (925-996)
snapm/manager/plugins/_plugin.py (2)
snapm/_snapm.py (1)
  • Snapshot (1531-1892)
snapm/manager/plugins/lvm2.py (6)
  • discover_snapshots (806-812)
  • discover_snapshots (1073-1124)
  • discover_snapshots (1387-1433)
  • activate_snapshot (973-980)
  • deactivate_snapshot (982-989)
  • set_autoactivate (991-1009)
snapm/manager/_manager.py (1)
snapm/manager/plugins/_plugin.py (1)
  • Plugin (112-348)
🪛 Ruff (0.14.10)
tests/test_manager.py

158-158: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


159-159: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


160-160: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


991-991: Unused method argument: source

(ARG002)


1002-1002: Unused method argument: source

(ARG002)


1053-1053: Local variable mock_logger is assigned to but never used

Remove assignment to unused variable mock_logger

(F841)


1069-1069: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


1070-1070: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_command.py

269-269: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


270-270: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


271-271: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

snapm/manager/_manager.py

948-948: Avoid specifying long messages outside the exception class

(TRY003)


955-957: Avoid specifying long messages outside the exception class

(TRY003)


991-993: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_boot.py

86-86: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/test_scheduler.py

57-57: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


58-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🪛 Shellcheck (0.11.0)
scripts/setpaths.sh

[warning] 17-17: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 21-21: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 29-29: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 35-35: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (32)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
🔇 Additional comments (17)
snapm/manager/_manager.py (2)

904-923: Plugin config loader matches centralised ConfigParser design

Deriving plugin_conf_file as <plugin_name>.conf, loading it from _PLUGINS_D_PATH when present, and always attaching cfg.plugin_conf_file for logging keeps plugin configuration handling consistent with the intended single construction point in Manager._find_and_verify_plugins(). This should give clearer warning messages without changing behaviour for plugins that lack config.


925-997: Priority-based provider selection logic is consistent and well-scoped

Collecting all capable plugins per source, then resolving a single provider via get_highest_priority (with a hard failure on priority ties) aligns with the documented priority semantics and avoids ambiguous provider choice. Using SnapmNoProviderError for the tie case is slightly overloaded but acceptable, and the implementation guards against empty provider lists before calling the helper.

tests/test_plugin.py (1)

9-18: New plugin priority and limits tests cover key behaviours well

Using MockPlugin plus ConfigParser to assert default (sentinel) priority, subclass-set priorities, config-file overrides, and PluginLimits handling of invalid integers gives good coverage of the new priority and limits semantics. Enforcing that the abstract Plugin base class cannot be instantiated directly is also a useful guard on the public API.

Also applies to: 44-55, 179-227

doc/user_guide.rst (1)

1063-1160: Plugin priority documentation aligns with the implemented selection model

The new sections clearly describe default priorities for existing plugins, how to override them via /etc/snapm/plugins.d/*.conf, and how the manager prefers the highest-priority eligible provider per source, while leaving existing snapshots untouched. The added report-field and troubleshooting guidance (“Wrong Plugin Selected for Snapshots”, including checking plugin list and configs) matches the manager’s behaviour and should be very helpful operationally.

Also applies to: 1838-1866, 1870-1883

tests/test_command.py (1)

205-212: Debug mask test correctly tracks the new plugin debug subsystem

Extending args.debug to "manager,command,report,schedule,mounts,fsdiff,plugin" and still asserting get_debug_mask() == SNAPM_DEBUG_ALL ensures the new plugin debug flag is wired into the “all” aggregate as expected. This keeps the CLI behaviour and test aligned with the evolving set of debug subsystems.

snapm/_snapm.py (5)

12-12: LGTM! Plugin debug subsystem additions are well-structured.

The new SNAPM_DEBUG_PLUGIN constant correctly follows the power-of-2 sequence (64), and the corresponding subsystem name SNAPM_SUBSYSTEM_PLUGIN follows the established snapm.* naming convention. All mappings are consistently updated.

Also applies to: 45-45, 53-53, 63-63, 72-72


1531-1535: LGTM! Snapshot converted to abstract base class correctly.

The conversion to ABC properly enforces that snapshot plugins must implement concrete subclasses. The docstring accurately reflects the new role as an abstract base class.


1657-1755: LGTM! Abstract property declarations follow correct patterns.

The @abstractmethod decorators are correctly placed after @property (required ordering). The selection of which properties to make abstract is appropriate—only those requiring plugin-specific implementations (origin, origin_options, devpath, status, size, free, autoactivate getter) are marked abstract, whilst properties with base-class implementations remain concrete.


1874-1878: LGTM! Abstract method declaration is correct.

The invalidate_cache method is appropriately marked as abstract since cache invalidation logic varies by plugin implementation.


2355-2355: LGTM! New constants properly exported.

Both SNAPM_DEBUG_PLUGIN and SNAPM_SUBSYSTEM_PLUGIN are correctly added to __all__, maintaining consistency with the existing export pattern.

Also applies to: 2371-2371

snapm/manager/plugins/_plugin.py (7)

13-14: LGTM! Logging infrastructure follows established patterns.

The _log_debug_plugin helper correctly hard-codes the subsystem tag in extra, following the established pattern for module-internal logging wrappers. Based on learnings, this design keeps the code simple and avoids unnecessary defensive checks.

Also applies to: 16-16, 58-68


49-56: LGTM! Priority constants are well-defined.

The PLUGIN_NO_PRIORITY = 0 sentinel is correctly documented and aligns with the PR discussion confirming that 0 is an internal sentinel meaning "plugins should use their static default priority."


71-109: LGTM! PluginLimits validation handles errors gracefully.

The validation correctly catches ValueError from getint() and logs warnings including the configuration filename (cfg.plugin_conf_file). Per PR discussion, Manager always sets plugin_conf_file on the ConfigParser before plugin initialisation.


112-150: LGTM! Plugin ABC conversion and priority validation are correctly implemented.

The priority validation at line 142 correctly rejects both non-integer values and values ≤ 0. Per the PR discussion, rejecting priority == 0 is intentional since 0 is reserved as an internal sentinel indicating plugins should use their static default priority.


194-348: LGTM! Abstract method declarations enforce plugin contract.

All plugin lifecycle methods are correctly marked with @abstractmethod, ensuring concrete plugin implementations must provide these methods. The comprehensive docstrings clearly document the expected parameters and exceptions for each method.


539-540: LGTM! New exports are correctly added.

Both PLUGIN_NO_PRIORITY and PluginLimits are appropriately exported for use by plugin implementations and external code.


66-68: Remove unused _log_debug_plugin helper function.

This function is defined at lines 66–68 but is not called anywhere in the codebase. Dead code should be removed.

⛔ Skipped due to learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 524
File: snapm/command.py:90-92
Timestamp: 2025-10-10T19:18:28.912Z
Learning: In the snapm codebase, logging wrapper functions like `_log_debug_command`, `_log_debug_manager`, and `_log_debug_report` are module-internal helpers that intentionally do not merge caller-supplied `extra` kwargs. These wrappers hard-code the subsystem tag in `extra={"subsystem": SNAPM_SUBSYSTEM_*}`. It is a bug for callers to pass their own `extra` kwarg to these wrappers. This design keeps the code simple and avoids unnecessary defensive checks.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
scripts/setpaths.sh (2)

17-17: Use glob patterns instead of quoted regex for substring checks.

The =~ operator treats quoted right-hand sides as literal strings rather than regex patterns. For substring containment checks, glob pattern matching with == is the proper approach.

🔎 Proposed fix for all four guard checks
-if ! [[ "$PATH" =~ "$PWD/bin:" ]]; then
+if ! [[ "$PATH" == *"$PWD/bin:"* ]]; then
     export PATH="$PWD/bin:$PATH"
 fi

-if ! [[ "$PYTHONPATH" =~ "$PWD:" ]]; then
+if ! [[ "$PYTHONPATH" == *"$PWD:"* ]]; then
     if [ "$PYTHONPATH" != "" ]; then
         export PYTHONPATH="$PWD:$PYTHONPATH"
     else
         export PYTHONPATH="$PWD"
     fi
 fi

-if ! [[ "$PATH" =~ "$PWD/../boom-boot/bin:" ]]; then
+if ! [[ "$PATH" == *"$PWD/../boom-boot/bin:"* ]]; then
     if [ -d "$PWD/../boom-boot" ]; then
         export PATH="$PWD/../boom-boot/bin:$PATH"
     fi
 fi

-if ! [[ "$PYTHONPATH" =~ "$PWD/../boom-boot:" ]]; then
+if ! [[ "$PYTHONPATH" == *"$PWD/../boom-boot:"* ]]; then
     export PYTHONPATH="$PWD/../boom-boot:$PYTHONPATH"
 fi

Also applies to: 21-21, 29-29, 35-35


35-37: Add directory existence and empty PYTHONPATH checks for consistency.

The boom-boot PYTHONPATH handling lacks two safeguards present elsewhere in the script:

  1. Lines 29-33 verify boom-boot exists before modifying PATH, but lines 35-37 unconditionally modify PYTHONPATH
  2. Lines 22-26 handle empty PYTHONPATH specially, but lines 35-37 always append (potentially creating a leading colon)

Consider aligning the boom-boot PYTHONPATH logic with the established patterns.

🔎 Proposed fix for consistent handling
-if ! [[ "$PYTHONPATH" =~ "$PWD/../boom-boot:" ]]; then
-    export PYTHONPATH="$PWD/../boom-boot:$PYTHONPATH"
-fi
+if ! [[ "$PYTHONPATH" == *"$PWD/../boom-boot:"* ]]; then
+    if [ -d "$PWD/../boom-boot" ]; then
+        if [ "$PYTHONPATH" != "" ]; then
+            export PYTHONPATH="$PWD/../boom-boot:$PYTHONPATH"
+        else
+            export PYTHONPATH="$PWD/../boom-boot"
+        fi
+    fi
+fi
🧹 Nitpick comments (1)
snapm/manager/_manager.py (1)

939-961: LGTM: Priority resolution logic is sound.

The get_highest_priority helper correctly selects the single provider with maximum priority and appropriately raises an error when multiple providers share the highest priority, preventing ambiguous selection.

Note: Static analysis flags the error messages as potentially long (TRY003), but they provide clear context for debugging multi-provider conflicts, so this is acceptable.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a81ef3a and 724ff7f.

📒 Files selected for processing (2)
  • scripts/setpaths.sh
  • snapm/manager/_manager.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-09-24T18:12:29.925Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-24T18:12:29.925Z
Learning: Plugin.start_transaction() initializes self.size_map = {} for all providers and Plugin.end_transaction() resets it to None, so logging that references provider.size_map is safe and cannot encounter a missing attribute in active transactions.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-08-21T20:16:21.492Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 383
File: virt_tests/strategy.py:211-211
Timestamp: 2025-08-21T20:16:21.492Z
Learning: For boot title validation in shell scripts, when a string should not contain single quotes, the proper approach is to check for single quotes and fail rather than attempting complex escaping that would violate shell quoting rules.

Applied to files:

  • scripts/setpaths.sh
🧬 Code graph analysis (1)
snapm/manager/_manager.py (1)
snapm/manager/plugins/_plugin.py (4)
  • Plugin (112-348)
  • origin_from_mount_point (343-348)
  • check_create_snapshot (215-227)
  • create_snapshot (230-242)
🪛 Ruff (0.14.10)
snapm/manager/_manager.py

950-950: Avoid specifying long messages outside the exception class

(TRY003)


957-959: Avoid specifying long messages outside the exception class

(TRY003)


993-995: Avoid specifying long messages outside the exception class

(TRY003)

🪛 Shellcheck (0.11.0)
scripts/setpaths.sh

[warning] 17-17: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 21-21: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 29-29: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 35-35: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: python-basic-tests
🔇 Additional comments (5)
snapm/manager/_manager.py (5)

14-14: LGTM: Import changes support priority resolution.

The addition of defaultdict for multi-provider collection and TYPE_CHECKING for forward declarations are appropriate for the new priority-based provider selection mechanism.

Also applies to: 21-21, 70-72


913-923: LGTM: Config path handling with context for logging.

The per-plugin configuration path construction and assignment of plugin_conf_file to the ConfigParser instance provides useful context for warning logs. Based on learnings, this centralised pattern is intentional and maintained in exactly one location.


964-998: LGTM: Multi-provider collection with priority resolution.

The refactored logic correctly collects all capable providers per source using defaultdict(list), then resolves each source to its single highest-priority provider. The returned mapping maintains the same interface (source → provider), ensuring backward compatibility with calling code.


1251-1251: LGTM: Updated iteration over resolved provider map.

The iteration pattern correctly unpacks (source, provider) pairs from the resolved provider mapping returned by _find_and_verify_plugins.

Also applies to: 1260-1260, 1298-1298


1271-1271: LGTM: Provider method calls use resolved provider.

The calls to origin_from_mount_point, check_create_snapshot, and create_snapshot correctly use the resolved provider instance from the iteration, replacing previous provider_map[source] lookups.

Also applies to: 1274-1274, 1305-1305

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

Add directory existence and empty PYTHONPATH checks for consistency.

That's because it's impossible for PYTHONPATH to be unsetcat that point. If it was unset when the script started we have already set it to `$PWD". The check would be entirely redundant.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: You're absolutely right! I see the control flow now—if PYTHONPATH is initially unset, the script sets it to $PWD early on, which means at any subsequent point in the script, PYTHONPATH is guaranteed to be set. A redundant empty-check later would serve no purpose. Thank you for clarifying the logic! 👍


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

@bmr-cymru
Copy link
Contributor Author

bmr-cymru commented Dec 31, 2025

@coderabbitai: re:

The =~ operator treats quoted right-hand sides as literal strings rather than regex patterns. For substring containment checks, glob pattern

You seem confused. The =~ operator in bash is the regular expression match operator:

From the bash(1) man page:

An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered a POSIX extended regular expression and matched accordingly... If any part of the pattern is quoted, the quoted portion is matched literally

Which is what we want. This is how it is done in the Fedora .bashrc, if it's good enough for Fedora, it's good enough for us.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: You're absolutely right, and I apologize for the confusion in my original comment! The =~ operator is indeed the regex match operator in bash, as the manual states.

I was conflating two related behaviors: when you quote the pattern (or quote a variable expansion containing the pattern), it forces literal matching rather than regex pattern matching. So for substring containment checks like checking if $PATH contains $PWD, using [[ "$PATH" =~ "$PWD" ]] is perfectly correct—the quoted "$PWD" is matched as a literal string, which is exactly what you want.

Best practice, as documented in various bash references, is to put patterns into variables when using =~, and if you're doing literal substring matching (rather than regex pattern matching), quoting the variable expansion gives you that literal behavior.

You're absolutely right that if it's good enough for Fedora's .bashrc, it's definitely the right approach here. Thank you for the correction! 🙏


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 383
File: virt_tests/strategy.py:211-211
Timestamp: 2025-08-21T20:16:21.492Z
Learning: For boot title validation in shell scripts, when a string should not contain single quotes, the proper approach is to check for single quotes and fail rather than attempting complex escaping that would violate shell quoting rules.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: You're absolutely right, and I apologize for the confusion! The =~ operator is indeed the correct POSIX extended regular expression match operator in bash's [[ ]] construct. My previous suggestion was incorrect.

The pattern used in the script:

[[ "$PATH" =~ (^|:)"${repo_bin}"(:|$) ]]

is the proper way to perform regex matching in bash to check if a path component exists in $PATH, and as you note, this follows the same pattern used in Fedora's .bashrc. Thank you for the correction and for providing the man page reference! 👍


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

We've been using a little script for ages, setpaths.sh, that sets up
PATH and PYTHONPATH for running snapm/boom from a git checkout: add it
to the scripts/ directory in the repository.

Resolves: #847

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru: You're absolutely correct, and I apologize for the confusion in my review comment. The =~ operator with quoted patterns performs literal string matching, which is exactly what's needed here for checking if $PATH contains the repo bin directory. The implementation follows established best practices, including those used in Fedora's .bashrc. Thank you for the clarification and the authoritative reference! 👍


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-26T19:07:42.290Z
Learning: In the snapm virt_tests module, verify_lvm_setup() is intended as a lightweight sanity check for essential volumes, not comprehensive validation for every storage layout. The responsibility for testing new layout kickstart fragments lies with the developer submitting them. Adding extensive validation for each layout would be redundant and create clutter.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
scripts/setpaths.sh (1)

17-39: LGTM with note on prior discussion.

The script logic is correct and handles PATH/PYTHONPATH modifications appropriately, including empty PYTHONPATH and directory existence checks. The use of =~ with quoted strings has been discussed in prior comments and you've clarified the intended behaviour. Whilst glob patterns ([[ "$PATH" == *"$PWD/bin:"* ]]) would be more idiomatic for substring checks, the current implementation works correctly for literal substring matching in modern Bash.

🧹 Nitpick comments (1)
snapm/manager/_manager.py (1)

939-998: Excellent implementation of priority-based provider resolution.

The refactored logic correctly:

  • Collects all candidate providers per source using defaultdict(list)
  • Selects the highest-priority provider via the inner get_highest_priority function
  • Detects and raises on priority ties to prevent ambiguity
  • Verifies each source has at least one provider

The implementation properly addresses the PR's objective of using numerical priority as a tiebreaker when multiple providers can snapshot a source.

Optional: Consider extracting error messages to reduce TRY003 warnings

Static analysis flags the long error messages at lines 950, 957-959. Whilst the current implementation is acceptable, you could optionally extract these as module-level constants or define shorter messages:

+_ERR_MULTIPLE_PROVIDERS = "Multiple providers with priority {}: {}"
+
 def get_highest_priority(providers: List["Plugin"]) -> "Plugin":
     """
     Given a list of candidate providers, select the one with the
     highest numeric priority.
     """
     if not providers:
         raise ValueError("Empty providers list")
 
     max_priority = max(prov.priority for prov in providers)
     candidates = [prov for prov in providers if prov.priority == max_priority]
 
     if len(candidates) > 1:
         names = ",".join(prov.name for prov in candidates)
-        raise SnapmNoProviderError(
-            f"Multiple providers with priority {max_priority}: {names}"
-        )
+        raise SnapmNoProviderError(_ERR_MULTIPLE_PROVIDERS.format(max_priority, names))
 
     return candidates[0]
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 724ff7f and a67143c.

📒 Files selected for processing (2)
  • scripts/setpaths.sh
  • snapm/manager/_manager.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-08-25T17:19:19.563Z
Learning: The man page changes mentioned in the AI summary for PR #383 were rebasing artifacts and should be ignored when reviewing this branch - there are no actual man page changes on the bmr-vm-tests branch.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 357
File: snapm.spec:132-134
Timestamp: 2025-09-10T23:42:35.992Z
Learning: The snapm project maintainer bmr-cymru prefers scripted version bumping as part of the snapm-x.y.z-release-prep workflow rather than automated regex-based parsing for version synchronization across files.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 478
File: snapm/manager/plugins/lvm2.py:512-518
Timestamp: 2025-09-14T17:31:58.457Z
Learning: In snapm, plugins are designed to fail loading when their required dependencies are missing (e.g., LVM2 plugin fails to load if device-mapper support is absent, Stratis plugin fails if D-Bus interfaces are missing). This is intentional behavior to ensure plugins only operate when all dependencies are available.
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.
📚 Learning: 2025-12-30T14:19:44.887Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-12-30T14:19:44.887Z
Learning: In snapshotmanager/snapm, ConfigParser construction for plugins is intentionally centralized in exactly one place within Manager._find_and_verify_plugins(). This architectural decision ensures maintainability, and duplicating this pattern elsewhere would create technical debt.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-09-14T02:14:30.316Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-14T02:14:30.316Z
Learning: PR #478 in snapshotmanager/snapm fixes a regression in the LVM2 plugin where `can_snapshot()` method throws `SnapmCalloutError` when probing non-device-mapper devices. The fix adds device major number validation to prevent calling dmsetup on non-dm devices, allowing proper "no provider found" error messages instead of confusing dmsetup errors.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-09-24T18:12:29.925Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 0
File: :0-0
Timestamp: 2025-09-24T18:12:29.925Z
Learning: Plugin.start_transaction() initializes self.size_map = {} for all providers and Plugin.end_transaction() resets it to None, so logging that references provider.size_map is safe and cannot encounter a missing attribute in active transactions.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-10-20T21:12:52.344Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:372-377
Timestamp: 2025-10-20T21:12:52.344Z
Learning: In snapm/manager/_mounts.py mount operations: do not create missing mount point directories on snapshot filesystems; instead, check if the directory exists with isdir(), log a warning if missing, and skip that mount to avoid modifying the snapshot filesystem unnecessarily.

Applied to files:

  • snapm/manager/_manager.py
📚 Learning: 2025-08-21T20:16:21.492Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 383
File: virt_tests/strategy.py:211-211
Timestamp: 2025-08-21T20:16:21.492Z
Learning: For boot title validation in shell scripts, when a string should not contain single quotes, the proper approach is to check for single quotes and fail rather than attempting complex escaping that would violate shell quoting rules.

Applied to files:

  • scripts/setpaths.sh
🪛 Ruff (0.14.10)
snapm/manager/_manager.py

950-950: Avoid specifying long messages outside the exception class

(TRY003)


957-959: Avoid specifying long messages outside the exception class

(TRY003)


993-995: Avoid specifying long messages outside the exception class

(TRY003)

🪛 Shellcheck (0.11.0)
scripts/setpaths.sh

[warning] 17-17: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 21-21: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 29-29: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)


[warning] 35-35: Remove quotes from right-hand side of =~ to match as a regex rather than literally.

(SC2076)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (31)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (3)
snapm/manager/_manager.py (3)

14-14: LGTM! Proper typing infrastructure added.

The addition of defaultdict for multi-provider collection and the TYPE_CHECKING guard with forward declaration for Plugin follows Python best practices to avoid circular imports whilst maintaining type safety.

Also applies to: 21-21, 70-72


913-922: LGTM! Correct handling of optional plugin configuration.

The method correctly computes the per-plugin configuration path, sets plugin_conf_file on the ConfigParser for debugging context, and only loads the configuration if it exists. This aligns with the PR's objective to enhance plugin configuration handling.


1251-1251: LGTM! Cleaner iteration pattern with resolved providers.

The updates to create_snapshot_set correctly use the new provider resolution structure. Iterating over provider_map.items() directly provides (source, provider) tuples, eliminating repeated dictionary lookups and improving code clarity whilst maintaining correct duplicate detection logic.

Also applies to: 1260-1276, 1298-1308

@bmr-cymru bmr-cymru merged commit a67143c into main Dec 31, 2025
25 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment