Skip to content

fix: problem with bootable flag not getting passed properly to snapm#138

Merged
richm merged 3 commits intolinux-system-roles:mainfrom
trgill:bootable_variable_fix
Feb 3, 2026
Merged

fix: problem with bootable flag not getting passed properly to snapm#138
richm merged 3 commits intolinux-system-roles:mainfrom
trgill:bootable_variable_fix

Conversation

@trgill
Copy link
Copy Markdown
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 bootable snapshot flag is correctly propagated to snapm and validate bootable behavior, while preventing unsupported or conflicting bootable configurations.

New Features:

  • Add a bootable snapshot test that exercises creation, verification, idempotence, and removal using snapm.
  • Introduce a reusable task file to query snapm for snapset status and expose bootable and boot entry information to tests.

Bug Fixes:

  • Fix handling of the global bootable flag so that it is passed through validation and snapshot management and reflected in snapm-created snapsets.

Enhancements:

  • Add explicit error codes and validation for unsupported bootable snapshots when snapm is not used and for conflicting bootable settings between global and snapset-level configuration.
  • Harden snapshot set handling to safely cope with missing snapshot set parameters in the Ansible module.

Tests:

  • Adjust existing bootable snapshot tests to validate bootable state via snapm output instead of only using role idempotence checks.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Jan 5, 2026

Reviewer's Guide

Propagates the global bootable flag through snapshot validation and snapm integration, adds explicit error codes for unsupported or conflicting bootable settings, and extends tests to verify bootable behavior via snapm and a new end-to-end scenario.

Sequence diagram for bootable snapshot creation via snapm

sequenceDiagram
    actor AnsibleUser
    participant SnapshotModule as snapshot_py
    participant Validate as validate_py
    participant SnapManager as snapmgr_py
    participant Snapm as snapm_binary

    AnsibleUser->>SnapshotModule: run snapshot module with snapshot_lvm_bootable, snapshot_lvm_set
    SnapshotModule->>SnapshotModule: check snapshot_lvm_set volumes length
    SnapshotModule->>Validate: validate_snapset_json(cmd, module_params, verify_only=False)
    Validate->>Validate: snapset_dict = module_args.snapshot_lvm_set
    Validate->>Validate: if module_args.snapshot_lvm_bootable set
    Validate->>Validate: snapset_dict.snapshot_lvm_bootable = module_args.snapshot_lvm_bootable
    Validate-->>SnapshotModule: rc, message

    SnapshotModule->>SnapManager: mgr_snapshot_cmd(module, module_args, snapset_json)
    SnapManager->>SnapManager: bootable = None
    SnapManager->>SnapManager: if module_args.snapshot_lvm_bootable set
    SnapManager->>SnapManager: bootable = module_args.snapshot_lvm_bootable
    SnapManager->>SnapManager: if bootable is None and snapset_json.bootable exists
    SnapManager->>SnapManager: else default bootable = False
    SnapManager->>SnapManager: if both global and snapset bootable set and differ
    SnapManager-->>SnapshotModule: return ERROR_BOOTABLE_CONFLICT

    SnapManager->>Snapm: create bootable snapset with bootable attribute
    Snapm-->>SnapManager: snapset created, boot menu updated
    SnapManager-->>SnapshotModule: result with changed=True and bootable info
    SnapshotModule-->>AnsibleUser: task result including bootable status facts
Loading

Class diagram for updated snapshot_lsr snapshot handling

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

    class snapshot_py {
        +run_module()
        +main()
    }

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

    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)
    }

    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)
    }

    snapshot_py --> validate_py : calls
    snapshot_py --> snapmgr_py : uses for snapm based snapshots
    snapshot_py --> lvm_py : uses for lvm only snapshots

    validate_py --> SnapshotStatus : returns
    snapmgr_py --> SnapshotStatus : returns
    lvm_py --> SnapshotStatus : returns

    snapmgr_py --> lvm_py : may coordinate with
    lvm_py --> validate_py : uses validation results
Loading

Flow diagram for bootable flag resolution in mgr_snapshot_cmd

flowchart TD
    A[start mgr_snapshot_cmd] --> B[bootable = None]
    B --> C{module_args.snapshot_lvm_bootable is truthy}
    C -- Yes --> D[bootable = module_args.snapshot_lvm_bootable]
    C -- No --> E[bootable remains None]

    D --> F{snapset_json.bootable is not None}
    F -- Yes --> G{bootable != snapset_json.bootable}
    G -- Yes --> H[return ERROR_BOOTABLE_CONFLICT, changed False]
    G -- No --> I[proceed to source_list creation]
    F -- No --> I

    E --> J{bootable is None}
    J -- Yes --> K{bootable in snapset_json}
    K -- Yes --> L[bootable = snapset_json.bootable]
    K -- No --> M[bootable = False]
    L --> I
    M --> I

    I --> N[use bootable when building snapset for snapm]
    N --> O[end mgr_snapshot_cmd]
Loading

Flow diagram for handling bootable snapshots without snapm

flowchart TD
    A[start snapshot_set in lvm_py] --> B{bootable requested?}
    B --> C[snapset_json.snapshot_lvm_bootable]
    B --> D[snapset_json.bootable]
    C --> E{any of snapshot_lvm_bootable or bootable is truthy}
    D --> E
    E -- Yes --> F[return ERROR_BOOTABLE_NOT_SUPPORTED, message 'Bootable snapshots are not supported without snapm', changed False]
    E -- No --> G[verify_snapset_source_lvs_exist]
    G --> H{rc == SNAPSHOT_OK?}
    H -- No --> I[return error from verification]
    H -- Yes --> J[proceed with non bootable snapshot_set]
    J --> K[end snapshot_set]
    F --> K
    I --> K
Loading

File-Level Changes

Change Details Files
Propagate global bootable flag into snapset validation and snapm snapshot command, including conflict detection.
  • Initialize a bootable variable in mgr_snapshot_cmd, preferring the global snapshot_lvm_bootable module argument when set before falling back to the snapset JSON bootable field or defaulting to False.
  • Add logic in mgr_snapshot_cmd to detect and return an ERROR_BOOTABLE_CONFLICT status when the global bootable flag conflicts with the snapset JSON bootable value.
  • Modify validate_snapset_json to accept the full module_args, derive snapset_dict from snapshot_lvm_set, and inject snapshot_lvm_bootable into the snapset when present.
  • Update the Ansible module entrypoint to guard against missing snapshot_lvm_set by safely accessing volumes and passing module.params instead of a raw snapset dict into validate_snapset_json.
module_utils/snapshot_lsr/snapmgr.py
module_utils/snapshot_lsr/validate.py
library/snapshot.py
Disallow bootable snapshots when snapm is not used and surface clear error codes for bootable-related failures.
  • Insert an early guard in snapshot_set to return ERROR_BOOTABLE_NOT_SUPPORTED and a descriptive message whenever bootable snapshot flags are present without snapm.
  • Extend SnapshotStatus with ERROR_BOOTABLE_NOT_SUPPORTED and ERROR_BOOTABLE_CONFLICT constants for new error conditions.
module_utils/snapshot_lsr/lvm.py
module_utils/snapshot_lsr/consts.py
Refactor and extend tests to validate bootable behavior via snapm and add a new bootable end-to-end scenario.
  • Move snapshot_lvm_bootable from inventory/vars into the task-level role invocation in tests_set_bootable.yml so the bootable flag flows through module_args.
  • Replace in-role verification and idempotence checks in tests_set_bootable.yml with a call to a new reusable get_snapset_status.yml task that queries snapm and asserts the snapset is bootable before removing it.
  • Introduce tests_basic_bootable.yml to exercise creation, verification, idempotence, and removal of a bootable snapshot set across multiple VGs with snapshot_lvm_bootable set at creation time.
  • Add get_snapset_status.yml as a reusable task file that runs "snapm snapset show" in JSON mode and exposes is_bootable and boot_entries_list facts for assertions.
  • Apply a small cleanup in find_unused_disk.py to remove an unnecessary blank line.
tests/tests_set_bootable.yml
tests/tests_basic_bootable.yml
tests/get_snapset_status.yml
tests/library/find_unused_disk.py

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
Copy Markdown
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).

@richm
Copy link
Copy Markdown
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

@trgill trgill force-pushed the bootable_variable_fix branch from 27969eb to c5cdf33 Compare February 1, 2026 21:54
@richm
Copy link
Copy Markdown
Contributor

richm commented Feb 1, 2026

1 minor python black issue -

black: commands[1]> black --check --diff --config /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/tox_lsr/config_files/black.toml .
--- /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py	2026-02-01 21:54:34.614304+00:00
+++ /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py	2026-02-01 21:55:26.781373+00:00
@@ -78,11 +78,10 @@
 
 # pylint: disable=E0401, E0611
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.size import Size
 
-
 SYS_CLASS_BLOCK = "/sys/class/block/"
 IGNORED_DEVICES = [re.compile(r"^/dev/nullb\d+$")]
 
 
 def is_ignored(disk_path):
would reformat /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py

@trgill
Copy link
Copy Markdown
Collaborator Author

trgill commented Feb 1, 2026

1 minor python black issue -

black: commands[1]> black --check --diff --config /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/tox_lsr/config_files/black.toml .
--- /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py	2026-02-01 21:54:34.614304+00:00
+++ /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py	2026-02-01 21:55:26.781373+00:00
@@ -78,11 +78,10 @@
 
 # pylint: disable=E0401, E0611
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.size import Size
 
-
 SYS_CLASS_BLOCK = "/sys/class/block/"
 IGNORED_DEVICES = [re.compile(r"^/dev/nullb\d+$")]
 
 
 def is_ignored(disk_path):
would reformat /home/runner/work/snapshot/snapshot/tests/library/find_unused_disk.py

Looking at that now... I didn't change that file in this PR. It is the file we copied from the storage role for finding disks for the tests.

It was just an extra line - fixed now.

@trgill trgill force-pushed the bootable_variable_fix branch from c5cdf33 to 748a899 Compare February 2, 2026 00:07
@trgill trgill marked this pull request as ready for review February 2, 2026 14:29
@trgill trgill requested a review from spetrosi as a code owner February 2, 2026 14:29
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In snapshot_set, accessing snapset_json["snapshot_lvm_bootable"] and snapset_json["bootable"] without .get() or default values can raise a KeyError for existing call sites that don’t populate these keys; consider using snapset_json.get(...) and treating missing/None as non-bootable.
  • The new validate_snapset_json signature now takes module_args but immediately overwrites snapset_dict from module_args["snapshot_lvm_set"] and assumes snapshot_lvm_bootable is present; it may be clearer and safer to keep the explicit snapset_dict parameter and use .get("snapshot_lvm_bootable") to avoid KeyErrors and tight coupling to the module’s param structure.
  • In mgr_snapshot_cmd, the logic if module_args["snapshot_lvm_bootable"]: only treats truthy values as a set global flag, so an explicit false from the user won’t override a true value in snapset_json; if you intend explicit false to take precedence, check for is not None or similar rather than truthiness.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `snapshot_set`, accessing `snapset_json["snapshot_lvm_bootable"]` and `snapset_json["bootable"]` without `.get()` or default values can raise a `KeyError` for existing call sites that don’t populate these keys; consider using `snapset_json.get(...)` and treating missing/None as non-bootable.
- The new `validate_snapset_json` signature now takes `module_args` but immediately overwrites `snapset_dict` from `module_args["snapshot_lvm_set"]` and assumes `snapshot_lvm_bootable` is present; it may be clearer and safer to keep the explicit `snapset_dict` parameter and use `.get("snapshot_lvm_bootable")` to avoid KeyErrors and tight coupling to the module’s param structure.
- In `mgr_snapshot_cmd`, the logic `if module_args["snapshot_lvm_bootable"]:` only treats truthy values as a set global flag, so an explicit `false` from the user won’t override a `true` value in `snapset_json`; if you intend explicit `false` to take precedence, check for `is not None` or similar rather than truthiness.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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

Signed-off-by: Todd Gill <tgill@redhat.com>
@trgill trgill force-pushed the bootable_variable_fix branch from 748a899 to cf3b8cc Compare February 2, 2026 15:12
Signed-off-by: Rich Megginson <rmeggins@redhat.com>
@richm
Copy link
Copy Markdown
Contributor

richm commented Feb 2, 2026

@trgill trgill#10

@trgill
Copy link
Copy Markdown
Collaborator Author

trgill commented Feb 2, 2026

@trgill trgill#10

Those changes look good. Should I merge into this PR?

@richm
Copy link
Copy Markdown
Contributor

richm commented Feb 2, 2026

@trgill trgill#10

Those changes look good. Should I merge into this PR?

Yes. This should fix the problem with centos-9

fix: fix issues with bootable snapshot code
@richm
Copy link
Copy Markdown
Contributor

richm commented Feb 2, 2026

[citest]

@richm
Copy link
Copy Markdown
Contributor

richm commented Feb 2, 2026

@trgill ready to merge? Did you test to see if you can boot from a bootable snapshot?

@trgill
Copy link
Copy Markdown
Collaborator Author

trgill commented Feb 3, 2026

@trgill ready to merge? Did you test to see if you can boot from a bootable snapshot?

yes it is ready to merge. I've tested with a VM that boots from LVM. I'm not aware of any way to automate that test.

@richm richm merged commit 59a7f48 into linux-system-roles:main Feb 3, 2026
30 checks passed
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