Skip to content

Conversation

@trgill
Copy link
Collaborator

@trgill trgill commented Jan 5, 2026

Add check to test that uses snapm to validate the snapset attribute is set to bootable.

Reason:

Global bootable flag was not being passed to snapm

Result:

Snapset will now be created with bootable attribute set and corresponding boot menu options.

Issue Tracker Tickets (Jira or BZ if any):

RHEL-137034

Summary by Sourcery

Ensure the snapshot role correctly propagates and validates the bootable flag, including when using snapm.

New Features:

  • Add support for passing a global bootable flag into snapset validation so snapm receives the bootable attribute.
  • Introduce explicit error reporting when requesting bootable snapshots without snapm support or when conflicting bootable settings are provided.
  • Add a reusable Ansible task file to query snapm for snapset status and expose bootable-related facts.
  • Add a basic end-to-end test that creates, verifies, and removes a bootable snapshot set using snapm.

Bug Fixes:

  • Fix the bootable flag not being passed from module arguments into the snapset definition used by snapm so bootable snapsets are created correctly.

Enhancements:

  • Update snapshot command handling to prioritize the global bootable setting, fall back to snapset configuration, and detect conflicts.
  • Tighten snapshot module argument handling by guarding against missing or empty snapset volume definitions before validation.

Tests:

  • Adjust the existing bootable test to validate bootability via snapm output instead of only role behavior.
  • Add coverage for bootable snapshot creation, verification, idempotence, and removal across multiple VGs.

Add check to test that uses snapm to validate the snapset attribute is set
to bootable.

Signed-off-by: Todd Gill <tgill@redhat.com>
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 5, 2026

Reviewer's Guide

Ensures the global bootable flag is correctly propagated through the snapshot module to snapm, adds validation and error codes for unsupported/conflicting bootable configurations, makes the snapshot entrypoint more robust, and extends the test suite with bootable-specific coverage including snapm-based verification.

Sequence diagram for bootable flag propagation to snapm

sequenceDiagram
    actor AnsibleUser
    participant snapshot_py as snapshot
    participant validate_py as validate
    participant snapmgr_py as snapmgr

    AnsibleUser->>snapshot_py: run_module(params)
    activate snapshot_py
    snapshot_py->>snapshot_py: check snapshot_lvm_set and volumes
    snapshot_py->>validate_py: validate_snapset_json(SNAPSHOT, module_params, verify_only=False)
    activate validate_py
    validate_py->>validate_py: snapset_dict = module_args.snapshot_lvm_set
    validate_py->>validate_py: if module_args.snapshot_lvm_bootable
    validate_py->>validate_py:   snapset_dict.snapshot_lvm_bootable = module_args.snapshot_lvm_bootable
    validate_py-->>snapshot_py: rc, message, updated_snapset_dict
    deactivate validate_py

    snapshot_py->>snapmgr_py: mgr_snapshot_cmd(module, module_params, snapset_json)
    activate snapmgr_py
    snapmgr_py->>snapmgr_py: bootable = None
    snapmgr_py->>snapmgr_py: if module_args.snapshot_lvm_bootable
    snapmgr_py->>snapmgr_py:   bootable = module_args.snapshot_lvm_bootable
    snapmgr_py->>snapmgr_py: if bootable is None and snapset_json.bootable present
    snapmgr_py->>snapmgr_py:   bootable = snapset_json.bootable
    snapmgr_py->>snapmgr_py: else if bootable is None
    snapmgr_py->>snapmgr_py:   bootable = False
    snapmgr_py->>snapmgr_py: if conflict between global and snapset bootable
    snapmgr_py-->>snapshot_py: {return_code: ERROR_BOOTABLE_CONFLICT, changed: False}
    deactivate snapmgr_py

    snapshot_py-->>AnsibleUser: result (includes bootable propagated or conflict error)
Loading

Class diagram for updated snapshot bootable handling

classDiagram
    class SnapshotStatus {
        <<enumeration>>
        int SNAPSHOT_OK
        int ERROR_UMOUNT_NOT_MOUNTED
        int ERROR_CREATE_FAILED
        int ERROR_SNAPM_INTERNAL_ERROR
        int ERROR_BOOTABLE_NOT_SUPPORTED
        int ERROR_BOOTABLE_CONFLICT
    }

    class snapshot_py {
        +run_module()
    }

    class validate_py {
        +validate_snapset_json(cmd, module_args, verify_only)
        +validate_json_request(snapset_dict, verify_sources)
        +validate_json_umount(snapset_dict)
    }

    class lvm_py {
        +snapshot_create_set(module, snapset_json, check_mode)
        +snapshot_set(module, snapset_json, check_mode)
        +verify_snapset_source_lvs_exist(module, snapset_json)
    }

    class snapmgr_py {
        +mgr_check_verify_lvs_set(manager, module, snapset_json)
        +mgr_snapshot_cmd(module, module_args, snapset_json)
        +mgr_get_source_list_for_create(volume_list)
    }

    SnapshotStatus <.. lvm_py : uses
    SnapshotStatus <.. snapmgr_py : uses
    SnapshotStatus <.. validate_py : uses

    snapshot_py ..> validate_py : calls
    snapshot_py ..> snapmgr_py : calls
    snapshot_py ..> lvm_py : calls

    validate_py ..> SnapshotStatus : returns
    lvm_py ..> SnapshotStatus : returns
    snapmgr_py ..> SnapshotStatus : returns
Loading

Flow diagram for bootable resolution and validation

flowchart TD
    A[module.run_module entry] --> B{snapshot_lvm_set exists and has volumes}
    B -- No --> Z[Exit or other handling]
    B -- Yes --> C[Call validate_snapset_json]

    subgraph Validate_snapset_json
        C --> D[Set snapset_dict = module_args.snapshot_lvm_set]
        D --> E{module_args.snapshot_lvm_bootable set}
        E -- Yes --> F[Set snapset_dict.snapshot_lvm_bootable = module_args.snapshot_lvm_bootable]
        E -- No --> G[Leave snapset_dict unchanged]
        F --> H[Continue validation]
        G --> H[Continue validation]
    end

    H --> I{Using snapm path}
    I -- Yes --> J[Call mgr_snapshot_cmd]
    I -- No --> K[Call snapshot_set in lvm]

    subgraph mgr_snapshot_cmd bootable resolution
        J --> L[bootable = None]
        L --> M{module_args.snapshot_lvm_bootable set}
        M -- Yes --> N[bootable = module_args.snapshot_lvm_bootable]
        M -- No --> O[bootable remains None]
        N --> P
        O --> P{bootable is None}
        P -- Yes --> Q{snapset_json.bootable present}
        Q -- Yes --> R[bootable = snapset_json.bootable]
        Q -- No --> S[bootable = False]
        P -- No --> T[bootable already set from global]
        R --> U
        S --> U
        T --> U
        U --> V{Global and snapset bootable conflict}
        V -- Yes --> W[Return ERROR_BOOTABLE_CONFLICT]
        V -- No --> X[Proceed to create bootable snapset]
    end

    subgraph snapshot_set non_snapm path
        K --> Y{snapshot_lvm_bootable is True or bootable is True}
        Y -- Yes --> AA[Return ERROR_BOOTABLE_NOT_SUPPORTED]
        Y -- No --> AB[Proceed with non bootable snapshot creation]
    end
Loading

File-Level Changes

Change Details Files
Propagate global bootable flag into snapset validation and snap manager logic, with conflict detection.
  • Pass full module params instead of just the snapset dict into validate_snapset_json so it can see global bootable parameters.
  • In validate_snapset_json, inject snapshot_lvm_bootable from module arguments into the snapset dict when set.
  • In mgr_snapshot_cmd, derive bootable from the global module_args flag first, then fall back to the snapset JSON, and return a specific error when the two sources conflict.
library/snapshot.py
module_utils/snapshot_lsr/validate.py
module_utils/snapshot_lsr/snapmgr.py
module_utils/snapshot_lsr/consts.py
Prevent bootable snapshots when using the non-snapm LVM backend and expose explicit status codes.
  • Short-circuit snapshot_set when a bootable snapshot is requested via either snapshot_lvm_bootable or bootable in the snapset JSON.
  • Return a dedicated ERROR_BOOTABLE_NOT_SUPPORTED code and message from snapshot_set in these cases.
  • Define new status constants for bootable-not-supported and bootable-conflict scenarios.
module_utils/snapshot_lsr/lvm.py
module_utils/snapshot_lsr/consts.py
Expand and refocus tests to validate bootable behavior via snapm and to assert the bootable attribute is set.
  • Adjust tests_set_bootable.yml to set snapshot_lvm_bootable at the role invocation level instead of under the host vars.
  • Replace idempotence and remove checks with a snapm-based status retrieval task and an assertion that the snapset is bootable.
  • Add a reusable get_snapset_status.yml task file that queries snapm, parses JSON, and exposes is_bootable and boot entry facts.
  • Introduce tests_basic_bootable.yml to perform a full bootable snapshot lifecycle test (create, verify, idempotence, remove) using the role with snapshot_lvm_bootable enabled.
tests/tests_set_bootable.yml
tests/tests_basic_bootable.yml
tests/get_snapset_status.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@trgill
Copy link
Collaborator Author

trgill commented Jan 5, 2026

Note: this is a draft PR because we need a fix in snapm to land before CI will pass. snapshotmanager/snapm#570 is required. snapm 0.5.3 is planned for release/tag today (1/5/26).

if module.params["snapshot_lvm_vg_include"]:
vg_include = re.compile(module.params["snapshot_lvm_vg_include"])

if len(module.params["snapshot_lvm_set"].get("volumes")) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

    if len(module.params.get("snapshot_lvm_set", {}).get("volumes", [])) > 0:

This should work as a one-liner - it will work if params has no snapshot_lvm_set, and if snapshot_lvm_set has no volumes - I believe the problem with the old code was that you could call len(None) which is an error


# If this function has been called with bootable snapshot requested, return error
# because snapm is required for bootable snapshots.
if (
Copy link
Contributor

Choose a reason for hiding this comment

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

    if any((snapset_json["snapshot_lvm_bootable"], snapset_json["bootable"])):

this is more pythonic - you shouldn't need to compare a value to True unless snapset_json["snapshot_lvm_bootable"] can have values other than None or boolean

@@ -0,0 +1,15 @@
# get_snapset_status.yml
- name: Invoke snapm for {{ snapset_name }}
ansible.builtin.command: "snapm snapset show {{ snapset_name }} -j"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
ansible.builtin.command: "snapm snapset show {{ snapset_name }} -j"
ansible.builtin.command: snapm snapset show {{ snapset_name | quote }} -j

- name: Define reusable variables
ansible.builtin.set_fact:
is_bootable: "{{ _raw_data.Bootable | bool }}"
boot_entries_list: "{{ _raw_data.BootEntries.values() | list }}" No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

missing newline at end of file

snapshot_lvm_all_vgs: true
snapshot_lvm_snapset_name: snapset1
snapshot_lvm_action: snapshot
snapshot_lvm_bootable: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
snapshot_lvm_bootable: true,
snapshot_lvm_bootable: true

?

@@ -0,0 +1,135 @@
---
- name: Basic snapshot test
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between this new test and tests_set_bootable.yml?

@richm
Copy link
Contributor

richm commented Jan 8, 2026

You'll need to rebase on top of the latest main branch - also, you won't be able to use Ansible variables such as ansible_distribution anymore - you'll need to use ansible_facts["distribution"] instead - see #141

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants