diff --git a/README.md b/README.md index de600800..0f6cea96 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Sets are defined in the following format: ```yaml snapshot_lvm_set: name: snapset1 + bootable: true volumes: - name: snapshot VG1 LV1 vg: test_vg1 @@ -84,6 +85,7 @@ that it applies, for example: ```yaml snapshot_lvm_set: name: snapset1 + bootable: false volumes: - name: snapshot VG1 LV1 vg: test_vg1 @@ -243,6 +245,18 @@ the rest will be excluded. For example, `snapshot_lvm_vg_include: "^sql_db_"` will only operate on volume groups whose names start with `sql_db_`. This uses the Python `re.search`. +### snapshot_lvm_bootable + +Boolean - default is false. Only supported on operating systems that +support snapshot manager (snapm). When set to true, and passed to the +'snapshot' command, the snapshot created will have a corresponding boot +entry. The boot entry will be removed when the snapset is removed. + +### snapshot_use_copr (EXPERIMENTAL) + +Boolean - default is unset - if you want to enable the copr repo to use the +latest development version of snapm, use `snapshot_use_copr: true` + ### Variables Exported by the Role #### snapshot_facts diff --git a/defaults/main.yml b/defaults/main.yml index 5a380e7e..ac0fbc14 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -17,3 +17,4 @@ snapshot_lvm_mountpoint: '' snapshot_lvm_mount_options: '' snapshot_lvm_vg_include: '' snapshot_lvm_fstype: '' +snapshot_lvm_bootable: false diff --git a/library/snapshot.py b/library/snapshot.py index f053123b..cacfab9c 100644 --- a/library/snapshot.py +++ b/library/snapshot.py @@ -95,6 +95,11 @@ to a regex pattern that matches the names of the volume groups you want to use and the rest will be excluded type: str + snapshot_lvm_bootable: + description: Only supported on operating systems that support snapshot manager (snapm). + When set to true, and passed to the 'snapshot' command, the snapshot created will have + a corresponding boot entry. The boot entry will be removed when the snapset is removed. + type: bool snapshot_lvm_set: description: set of volumes type: dict @@ -102,6 +107,9 @@ name: description: name of set type: str + bootable: + description: create snapshot with boot menu entry (requires snapm) + type: bool volumes: description: list of volumes type: list @@ -282,6 +290,7 @@ def run_module(): snapshot_lvm_all_vgs=dict(type="bool"), snapshot_lvm_verify_only=dict(type="bool"), snapshot_lvm_mount_origin=dict(type="bool"), + snapshot_lvm_bootable=dict(type="bool"), snapshot_lvm_mountpoint_create=dict(type="bool"), snapshot_lvm_unmount_all=dict(type="bool"), snapshot_lvm_percent_space_required=dict(type="str"), @@ -296,6 +305,7 @@ def run_module(): type="dict", options=dict( name=dict(type="str"), + bootable=dict(type="bool"), volumes=dict( type="list", elements="dict", diff --git a/module_utils/snapshot_lsr/lvm.py b/module_utils/snapshot_lsr/lvm.py index b827ea71..d9cd01a4 100644 --- a/module_utils/snapshot_lsr/lvm.py +++ b/module_utils/snapshot_lsr/lvm.py @@ -81,6 +81,11 @@ def get_json_from_args(module, module_args, vg_include): if module_args["snapshot_lvm_snapset_name"]: args_dict["name"] = module_args["snapshot_lvm_snapset_name"] + if module_args["snapshot_lvm_bootable"]: + args_dict["bootable"] = module_args["snapshot_lvm_bootable"] + else: + args_dict["bootable"] = False + for vg, lv_list in vgs_lvs_iterator( module, module_args["snapshot_lvm_vg"], diff --git a/module_utils/snapshot_lsr/snapmgr.py b/module_utils/snapshot_lsr/snapmgr.py index 28bae861..cee45059 100644 --- a/module_utils/snapshot_lsr/snapmgr.py +++ b/module_utils/snapshot_lsr/snapmgr.py @@ -200,6 +200,10 @@ def mgr_snapshot_cmd(module, module_args, snapset_json): snapset_name = snapset_json["name"] volume_list = snapset_json["volumes"] + if "bootable" in snapset_json: + bootable = snapset_json["bootable"] + else: + bootable = False source_list = mgr_get_source_list_for_create(volume_list) @@ -210,7 +214,10 @@ def mgr_snapshot_cmd(module, module_args, snapset_json): try: manager.create_snapshot_set( - snapset_name, source_list, SNAPM_DEFAULT_SIZE_POLICY + snapset_name, + source_list, + SNAPM_DEFAULT_SIZE_POLICY, + boot=bootable, ) changed = True except snapm.SnapmError as snap_err: diff --git a/module_utils/snapshot_lsr/validate.py b/module_utils/snapshot_lsr/validate.py index 223b474f..b778f0cf 100644 --- a/module_utils/snapshot_lsr/validate.py +++ b/module_utils/snapshot_lsr/validate.py @@ -33,6 +33,9 @@ def get_json_from_args(module, module_args, vg_include): if module_args["snapshot_lvm_snapset_name"]: args_dict["name"] = module_args["snapshot_lvm_snapset_name"] + if module_args["snapshot_lvm_bootable"]: + args_dict["bootable"] = module_args["snapshot_lvm_bootable"] + for vg, lv_list in vgs_lvs_iterator( module, module_args["snapshot_lvm_vg"], diff --git a/tasks/enable_copr.yml b/tasks/enable_copr.yml new file mode 100644 index 00000000..94426b0a --- /dev/null +++ b/tasks/enable_copr.yml @@ -0,0 +1,6 @@ +--- +# Primarily for testing unreleased versions +- name: Enable snapm copr on Fedora + command: dnf -y copr enable packit/snapshotmanager-snapm-357 + when: ansible_facts['distribution'] == 'Fedora' + changed_when: true diff --git a/tasks/main.yml b/tasks/main.yml index cb50999f..68c28772 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -3,6 +3,10 @@ - name: Set platform/version specific variables include_tasks: tasks/set_vars.yml +- name: Enable copr if requested + include_tasks: enable_copr.yml + when: snapshot_use_copr | d(false) + - name: Ensure required packages are installed package: name: "{{ __snapshot_packages }}" @@ -10,7 +14,64 @@ use: "{{ (__snapshot_is_ostree | d(false)) | ternary('ansible.posix.rhel_rpm_ostree', omit) }}" +- name: Get snapm version + check_mode: false + command: snapm --version + changed_when: false + register: __snapshot_snapm_version_output + # Ignore errors as snapm may not be available + ignore_errors: true + +- name: Set snapm availability fact + set_fact: + __snapshot_snapm_available: "{{ __snapshot_snapm_version_output is success }}" + +- name: Set snapm version + set_fact: + __snapshot_snapm_version: "{{ __snapshot_snapm_version_output.stdout }}" + when: __snapshot_snapm_available + +# Determine if bootable support is needed +# If snapshot_lvm_bootable is set to true, or any of the volumes in snapshot_lvm_set +# have bootable set to true, then bootable support is needed. +- name: Set needs bootable support + set_fact: + __snapshot_needs_bootable_support: "{{ snapshot_lvm_bootable or (snapshot_lvm_set | + selectattr('bootable', 'defined') | + selectattr('bootable') | list | length > 0) }}" + +- name: Package snapm version must be 0.4 or later + fail: + msg: >- + Package snapm version {{ __snapshot_snapm_version }} is too old - + must be 0.4 or later + when: + - __snapshot_snapm_available + - __snapshot_snapm_version is version("0.4", "<") + +- name: Package snapm must available for bootable snapsets + fail: + msg: >- + Package snapm version 0.5 or later is required to use bootable snapsets + when: + - not __snapshot_snapm_available + - __snapshot_needs_bootable_support + +- name: Package snapm must be version 0.5 or later for bootable snapsets + fail: + msg: >- + Package snapm version {{ __snapshot_snapm_version }} is too old - + version 0.5 or later is required to use bootable snapsets + when: + - not __snapshot_snapm_available or + __snapshot_snapm_version is version("0.5", "<") + - __snapshot_needs_bootable_support + - name: Run snapshot module and handle errors + when: + - snapshot_lvm_action is defined + - not __snapshot_needs_bootable_support or (__snapshot_snapm_available and + __snapshot_snapm_version is version("0.5", ">=")) block: - name: Run snapshot module {{ snapshot_lvm_action }} snapshot: @@ -19,6 +80,7 @@ snapshot_lvm_all_vgs: "{{ snapshot_lvm_all_vgs | d(false) }}" snapshot_lvm_verify_only: "{{ snapshot_lvm_verify_only | d(false) }}" snapshot_lvm_mount_origin: "{{ snapshot_lvm_mount_origin | d(false) }}" + snapshot_lvm_bootable: "{{ snapshot_lvm_bootable | d(false) }}" snapshot_lvm_mountpoint_create: "{{ snapshot_lvm_mountpoint_create | d(false) }}" snapshot_lvm_unmount_all: "{{ snapshot_lvm_unmount_all | d(false) }}" @@ -44,17 +106,19 @@ debug: var: snapshot_cmd verbosity: 2 + when: snapshot_cmd is defined - name: Set result set_fact: snapshot_cmd: "{{ snapshot_cmd }}" + when: snapshot_cmd is defined - name: Set snapshot_facts to the JSON results set_fact: snapshot_facts: "{{ snapshot_cmd['data'] }}" - when: snapshot_lvm_action == "list" + when: snapshot_cmd is defined and snapshot_lvm_action == "list" - name: Show errors debug: var: snapshot_cmd["errors"] - when: snapshot_cmd["return_code"] != 0 + when: snapshot_cmd is defined and snapshot_cmd["return_code"] != 0 diff --git a/tests/tests_set_bootable.yml b/tests/tests_set_bootable.yml new file mode 100644 index 00000000..82d0719b --- /dev/null +++ b/tests/tests_set_bootable.yml @@ -0,0 +1,133 @@ +--- +- name: Snapshot a set of volumes with snapshot_lvm_bootable set to true + hosts: all + vars: + test_disk_min_size: "1g" + test_disk_count: 10 + test_storage_pools: + - name: test_vg1 + disks: "{{ range(0, 3) | map('extract', unused_disks) | list }}" + volumes: + - name: lv1 + size: "15%" + - name: lv2 + size: "50%" + - name: test_vg2 + disks: "{{ range(3, 6) | map('extract', unused_disks) | list }}" + volumes: + - name: lv3 + size: "10%" + - name: lv4 + size: "20%" + - name: test_vg3 + disks: "{{ range(6, 10) | map('extract', unused_disks) | list }}" + volumes: + - name: lv5 + size: "30%" + - name: lv6 + size: "25%" + - name: lv7 + size: "10%" + - name: lv8 + size: "10%" + snapshot_test_set: + name: snapset1 + volumes: + - name: snapshot VG1 LV1 + vg: test_vg1 + lv: lv1 + percent_space_required: 20 + - name: snapshot VG2 LV3 + vg: test_vg2 + lv: lv3 + percent_space_required: 15 + - name: snapshot VG2 LV4 + vg: test_vg2 + lv: lv4 + percent_space_required: 15 + - name: snapshot VG3 LV7 + vg: test_vg3 + lv: lv7 + percent_space_required: 15 + snapshot_lvm_bootable: true + tasks: + - name: Load test variables + include_vars: + file: vars/rh_distros_vars.yml + when: __snapshot_is_ostree is not defined + + - name: Run tests + block: + - name: Setup + include_tasks: tasks/setup.yml + + - name: Run the snapshot role to create snapshot set of LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: snapshot + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Assert changes for create snapset + assert: + that: snapshot_cmd["changed"] + + - name: Run the snapshot role to verify the set of snapshots for the LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: check + snapshot_lvm_set: "{{ snapshot_test_set }}" + snapshot_lvm_verify_only: true + + - name: Create snapset again for idempotence + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: snapshot + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Assert no changes for create snapset + assert: + that: not snapshot_cmd["changed"] + + - name: Run the snapshot role remove the set + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: remove + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Assert changes for remove snapset + assert: + that: snapshot_cmd["changed"] + + - name: Run the snapshot role to verify the set is removed + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: remove + snapshot_lvm_set: "{{ snapshot_test_set }}" + snapshot_lvm_verify_only: true + + - name: Remove again to check idempotence + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: remove + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Assert no changes for remove snapset + assert: + that: not snapshot_cmd["changed"] + rescue: + - name: Check if error is due to snapm version too old + fail: + msg: Unexpected error occurred {{ ansible_failed_result | to_nice_json }} + when: not ansible_failed_result.msg is search(err_msg) + vars: + err_msg: Package snapm .*version 0.5 or later is required to use bootable snapsets + always: + - name: Cleanup + include_tasks: tasks/cleanup.yml + tags: tests::cleanup