diff --git a/README.md b/README.md index 895c8de..b9dff54 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ This collection depends on the following collections These are the roles included in the collection. Follow the links below to see the detailed documentation and example playbooks for each role. -- [`lvm_snapshots`](./roles/lvm_snapshots/) - controls creation and rollback for a defined set of LVM snapshot volumes +- [`create_snapshot`](./roles/create_snapshot/) - controls the creation for a defined set of LVM snapshot volumes +- [`remove_snapshot`](./roles/remove_snapshot/) - used to remove snapshots previously created using the `create_snapshot` role +- [`revert_snapshot`](./roles/revert_snapshot/) - used to revert to snapshots previously created using the `create_snapshot` role - [`shrink_lv`](./roles/shrink_lv/) - controls decreasing logical volume size along with the filesystem - [`bigboot`](./roles/bigboot/) - controls increasing of the boot partition while moving, and shrinking if needed, the adjacent partition - [`initramfs`](./roles/initramfs/) - controls the atomic flow of building and using a temporary initramfs in a reboot and restoring the original one diff --git a/changelogs/fragments/split-lvm-snapshot_role.yml b/changelogs/fragments/split-lvm-snapshot_role.yml new file mode 100644 index 0000000..25d5238 --- /dev/null +++ b/changelogs/fragments/split-lvm-snapshot_role.yml @@ -0,0 +1,2 @@ +breaking_changes: +- Split lvm_snapshots role into create_snapshot, revert_snapshot and remove_snapshot \ No newline at end of file diff --git a/roles/create_snapshot/README.md b/roles/create_snapshot/README.md new file mode 100644 index 0000000..1e8aadd --- /dev/null +++ b/roles/create_snapshot/README.md @@ -0,0 +1,84 @@ +# create_snapshot role + + +The `create_snapshot` role is used to control the creation for a defined set of LVM snapshot volumes. +In addition, it can optionally save the Grub configuration and image files under /boot and configure settings to enable the LVM snapshot autoextend capability. +The role will verify free space and should fail if there is not enough or if any snapshots already exist for the given `create_snapshot_set_name`. + +The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities. + +## Role Variables + +### `create_snapshot_check_only` + +When set to `true` the role will only verify there is enough free space for the specified snapshots and not create them. +Default `false` + +### `create_snapshot_set_name` + +The variable `create_snapshot_set_name` is used to identify the list of volumes to be operated upon. +The role will use the following naming convention when creating the snapshots: + +`_` + +### `create_snapshot_boot_backup` + +Boolean to specify that the role should preserve the Grub configuration and image files under /boot required for booting the default kernel. +The files are preserved in a compressed tar archive at `/root/boot-backup-.tgz`. Default is `false`. + +> **Warning** +> +> When automating RHEL in-place upgrades, do not perform a Grub to Grub2 migration as part of your upgrade playbook. It will invalidate your boot backup and cause a subsequent `revert` action to fail. For example, if you are using the [`upgrade`](https://github.com/redhat-cop/infra.leapp/tree/main/roles/upgrade#readme) role from the [`infra.leapp`](https://github.com/redhat-cop/infra.leapp) collection, do not set `update_grub_to_grub_2` to `true`. Grub to Grub2 migration should only be performed after the `remove` action has been performed to delete the snapshots and boot backup. + +### `create_snapshot_snapshot_autoextend_threshold` + +Configure the given `create_snapshot_autoextend_threshold` setting in lvm.conf before creating snapshots. + +### `create_snapshot_snapshot_autoextend_percent` + +Configure the given `create_snapshot_snapshot_autoextend_percent` setting in lvm.conf before creating snapshots. + +### `create_snapshot_volumes` + +This is the list of logical volumes for which snapshots are to be created and the size requirements for those snapshots. The volumes list is only required when the role is run with the check or create action. + +### `vg` + +The volume group of the origin logical volume for which a snapshot will be created. + +### `lv` + +The origin logical volume for which a snapshot will be created. + +### `size` + +The size of the logical volume according to the definition of the +[size](https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-size) +parameter of the `community.general.lvol` module. + +To create thin provisioned snapshot of a thin provisioned volume, omit the `size` parameter or set it to `0` + +## Example Playbooks + +Perform space check and fail of there will not be enough space for all the snapshots in the set. +If there is sufficient space, proceed to create snapshots for the listed logical volumes. +Each snapshot will be sized to 20% of the origin volume size. +Snapshot autoextend settings are configured to enable free space in the volume group to be allocated to any snapshot that may exceed 70% usage in the future. +Files under /boot will be preserved. + +```yaml +- hosts: all + roles: + - name: create_snapshot + create_snapshot_set_name: ripu + create_snapshot_snapshot_autoextend_threshold: 70 + create_snapshot_snapshot_autoextend_percent: 20 + create_snapshot_boot_backup: true + create_snapshot_volumes: + - vg: rootvg + lv: root + size: 2G + - vg: rootvg + lv: var + size: 2G +``` diff --git a/roles/create_snapshot/defaults/main.yml b/roles/create_snapshot/defaults/main.yml new file mode 100644 index 0000000..a28c258 --- /dev/null +++ b/roles/create_snapshot/defaults/main.yml @@ -0,0 +1,2 @@ +create_snapshot_volumes: [] +create_snapshot_boot_backup: false diff --git a/roles/lvm_snapshots/files/check.py b/roles/create_snapshot/files/check.py similarity index 100% rename from roles/lvm_snapshots/files/check.py rename to roles/create_snapshot/files/check.py diff --git a/roles/lvm_snapshots/tasks/check.yml b/roles/create_snapshot/tasks/check.yml similarity index 62% rename from roles/lvm_snapshots/tasks/check.yml rename to roles/create_snapshot/tasks/check.yml index 422d249..af46daf 100644 --- a/roles/lvm_snapshots/tasks/check.yml +++ b/roles/create_snapshot/tasks/check.yml @@ -1,26 +1,26 @@ - name: Verify that all volumes exist ansible.builtin.include_tasks: verify_volume_exists.yml - loop: "{{ lvm_snapshots_volumes }}" + loop: "{{ create_snapshot_volumes }}" - name: Verify that there are no existing snapshots ansible.builtin.include_tasks: verify_no_existing_snapshot.yml - loop: "{{ lvm_snapshots_volumes }}" + loop: "{{ create_snapshot_volumes }}" - name: Verify that there is enough storage space - ansible.builtin.script: check.py snapshots '{{ lvm_snapshots_volumes | to_json }}' + ansible.builtin.script: check.py snapshots '{{ create_snapshot_volumes | to_json }}' args: executable: "{{ ansible_python.executable }}" - register: lvm_snapshots_check_status + register: create_snapshot_check_status failed_when: false changed_when: false - name: Store check return in case of failure ansible.builtin.set_fact: - lvm_snapshots_check_failure_json: "{{ lvm_snapshots_check_status.stdout | from_json }}" - when: lvm_snapshots_check_status.rc != 0 + create_snapshot_check_failure_json: "{{ create_snapshot_check_status.stdout | from_json }}" + when: create_snapshot_check_status.rc != 0 - name: Assert results ansible.builtin.assert: - that: lvm_snapshots_check_status.rc == 0 + that: create_snapshot_check_status.rc == 0 fail_msg: Not enough space in the Volume Groups to create the requested snapshots success_msg: The Volume Groups have enough space to create the requested snapshots diff --git a/roles/lvm_snapshots/tasks/create.yml b/roles/create_snapshot/tasks/create.yml similarity index 58% rename from roles/lvm_snapshots/tasks/create.yml rename to roles/create_snapshot/tasks/create.yml index 55561d1..9b67457 100644 --- a/roles/lvm_snapshots/tasks/create.yml +++ b/roles/create_snapshot/tasks/create.yml @@ -2,21 +2,21 @@ block: - name: Stringify snapshot_autoextend_percent setting ansible.builtin.set_fact: - lvm_snapshots_snapshot_autoextend_percent_config: "activation/snapshot_autoextend_percent={{ lvm_snapshots_snapshot_autoextend_percent }}" - when: lvm_snapshots_snapshot_autoextend_percent is defined + create_snapshot_snapshot_autoextend_percent_config: "activation/snapshot_autoextend_percent={{ create_snapshot_snapshot_autoextend_percent }}" + when: create_snapshot_snapshot_autoextend_percent is defined - name: Stringify snapshot_autoextend_threshold setting ansible.builtin.set_fact: - lvm_snapshots_snapshot_autoextend_threshold_config: "activation/snapshot_autoextend_threshold={{ lvm_snapshots_snapshot_autoextend_threshold }}" - when: lvm_snapshots_snapshot_autoextend_threshold is defined + create_snapshot_snapshot_autoextend_threshold_config: "activation/snapshot_autoextend_threshold={{ create_snapshot_snapshot_autoextend_threshold }}" + when: create_snapshot_snapshot_autoextend_threshold is defined - name: Stringify the new config ansible.builtin.set_fact: - lvm_snapshots_new_lvm_config: > - {{ lvm_snapshots_snapshot_autoextend_percent_config | default('') }} - {{ lvm_snapshots_snapshot_autoextend_threshold_config | default('') }} + create_snapshot_new_lvm_config: > + {{ create_snapshot_snapshot_autoextend_percent_config | default('') }} + {{ create_snapshot_snapshot_autoextend_threshold_config | default('') }} - name: Set LVM configuration - ansible.builtin.command: 'lvmconfig --mergedconfig --config "{{ lvm_snapshots_new_lvm_config }}" --file /etc/lvm/lvm.conf' + ansible.builtin.command: 'lvmconfig --mergedconfig --config "{{ create_snapshot_new_lvm_config }}" --file /etc/lvm/lvm.conf' changed_when: true - when: ((lvm_snapshots_new_lvm_config | trim) | length) > 0 + when: ((create_snapshot_new_lvm_config | trim) | length) > 0 - name: Check for grubenv saved_entry ansible.builtin.lineinfile: @@ -26,20 +26,20 @@ check_mode: true changed_when: false failed_when: false - register: grubenv + register: create_snapshot_grubenv - name: Add grubenv saved_entry ansible.builtin.shell: 'grubby --set-default-index=$(grubby --default-index)' changed_when: true - when: grubenv.found is defined and grubenv.found == 0 + when: create_snapshot_grubenv.found is defined and create_snapshot_grubenv.found == 0 - name: Create snapshots community.general.lvol: vg: "{{ item.vg }}" lv: "{{ item.lv }}" - snapshot: "{{ item.lv }}_{{ lvm_snapshots_set_name }}" + snapshot: "{{ item.lv }}_{{ create_snapshot_set_name }}" size: "{{ item.size | default(omit) }}" - loop: "{{ lvm_snapshots_volumes }}" + loop: "{{ create_snapshot_volumes }}" - name: Required packages are present ansible.builtin.package: @@ -52,7 +52,7 @@ community.general.archive: format: gz mode: '0644' - dest: "/root/boot-backup-{{ lvm_snapshots_set_name }}.tgz" + dest: "/root/boot-backup-{{ create_snapshot_set_name }}.tgz" path: - "/boot/initramfs-{{ ansible_kernel }}.img" - "/boot/vmlinuz-{{ ansible_kernel }}" @@ -65,4 +65,4 @@ - /boot/grub2/grubenv - /boot/loader/entries - /boot/efi/EFI/redhat/grub.cfg - when: lvm_snapshots_boot_backup + when: create_snapshot_boot_backup diff --git a/roles/create_snapshot/tasks/main.yml b/roles/create_snapshot/tasks/main.yml new file mode 100644 index 0000000..91f2411 --- /dev/null +++ b/roles/create_snapshot/tasks/main.yml @@ -0,0 +1,8 @@ +- name: Check available disk space + ansible.builtin.include_tasks: check.yml + +- name: Create Snapshot + vars: + create_snapshot_volumes: "{{ create_snapshot_check_status.stdout | from_json }}" + ansible.builtin.include_tasks: create.yml + when: not (create_snapshot_check_only | default(false)) diff --git a/roles/lvm_snapshots/tasks/verify_no_existing_snapshot.yml b/roles/create_snapshot/tasks/verify_no_existing_snapshot.yml similarity index 53% rename from roles/lvm_snapshots/tasks/verify_no_existing_snapshot.yml rename to roles/create_snapshot/tasks/verify_no_existing_snapshot.yml index 023c252..dbc4aea 100644 --- a/roles/lvm_snapshots/tasks/verify_no_existing_snapshot.yml +++ b/roles/create_snapshot/tasks/verify_no_existing_snapshot.yml @@ -3,19 +3,19 @@ lvs --select 'vg_name = {{ item.vg }} && origin = {{ item.lv }} - && lv_name = {{ item.lv }}_{{ lvm_snapshots_set_name }}' + && lv_name = {{ item.lv }}_{{ create_snapshot_set_name }}' --reportformat json - register: lvm_snapshots_lvs_response + register: create_snapshot_lvs_response changed_when: false - name: Parse report ansible.builtin.set_fact: - lvm_snapshots_lv_snapshot_report_array: "{{ (lvm_snapshots_lvs_response.stdout | from_json).report[0].lv }}" + create_snapshot_lv_snapshot_report_array: "{{ (create_snapshot_lvs_response.stdout | from_json).report[0].lv }}" - name: Verify that the no snapshot exists for the volume ansible.builtin.assert: - that: (lvm_snapshots_lv_snapshot_report_array | length) == 0 + that: (create_snapshot_lv_snapshot_report_array | length) == 0 fail_msg: > The volume '{{ item.lv }}' in volume group '{{ item.vg }}' already has at least one snapshot - '{{ lvm_snapshots_lv_snapshot_report_array[0].lv_name | default('none') }}' + '{{ create_snapshot_lv_snapshot_report_array[0].lv_name | default('none') }}' diff --git a/roles/lvm_snapshots/tasks/verify_volume_exists.yml b/roles/create_snapshot/tasks/verify_volume_exists.yml similarity index 70% rename from roles/lvm_snapshots/tasks/verify_volume_exists.yml rename to roles/create_snapshot/tasks/verify_volume_exists.yml index 2e74cab..198c336 100644 --- a/roles/lvm_snapshots/tasks/verify_volume_exists.yml +++ b/roles/create_snapshot/tasks/verify_volume_exists.yml @@ -1,9 +1,9 @@ - name: Run lvs ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg }} && lv_name = {{ item.lv }}' --reportformat json" - register: lvm_snapshots_lvs_response + register: create_snapshot_lvs_response changed_when: false - name: Verify that the volume was found ansible.builtin.assert: - that: (((lvm_snapshots_lvs_response.stdout | from_json).report[0].lv) | length) > 0 + that: (((create_snapshot_lvs_response.stdout | from_json).report[0].lv) | length) > 0 fail_msg: "Could not find volume '{{ item.lv }}' in volume group '{{ item.vg }}'" diff --git a/roles/lvm_snapshots/README.md b/roles/lvm_snapshots/README.md deleted file mode 100644 index d99ff4c..0000000 --- a/roles/lvm_snapshots/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# lvm_snapshots role - - -The `lvm_snapshots` role is used to control the creation and rollback for a defined set of LVM snapshot volumes. In addition, it can optionally save the Grub configuration and image files under /boot and configure settings to enable the LVM snapshot autoextend capability. - -The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities. - -## Role Variables - -### `lvm_snapshots_set_name` - -The variable `lvm_snapshots_set_name` is used to identify the list of volumes to be operated upon. The role will use the following naming convention when creating the snapshots: - -`_` - -When the role is run with a revert or remove action, this naming convention will be used to identify the snapshots to be merged or removed. - -### `lvm_snapshots_action` - -The role will accept an action variable that will control the operation to be performed: - -- `check` - verify there is enough free space for the specified snapshots -- `create` - verify free space as above and create snapshots -- `revert` - merge snapshots to origin and reboot (i.e., rollback) -- `remove` - remove snapshots - -Both the `check` and `create` actions will verify free space and should fail if there is not enough. A `check` or `create` action should fail if any snapshots already exist for the given `snapshot_set_name`. - -The `revert` action will verify that all snapshots in the set are still active state before doing any merges. This is to prevent rolling back if any snapshots have become invalidated in which case the `revert` action should fail. - -### `lvm_snapshots_boot_backup` - -Boolean to specify that the `create` action should preserve the Grub configuration and image files under /boot required for booting the default kernel. The preserved files will be restored with a `revert` action and they will be deleted with a `remove` action. The files are preserved in a compressed tar archive at `/root/boot-backup-.tgz`. Default is false. - -> **Warning** -> -> When automating RHEL in-place upgrades, do not perform a Grub to Grub2 migration as part of your upgrade playbook. It will invalidate your boot backup and cause a subsequent `revert` action to fail. For example, if you are using the [`upgrade`](https://github.com/redhat-cop/infra.leapp/tree/main/roles/upgrade#readme) role from the [`infra.leapp`](https://github.com/redhat-cop/infra.leapp) collection, do not set `update_grub_to_grub_2` to `true`. Grub to Grub2 migration should only be performed after the `remove` action has been performed to delete the snapshots and boot backup. - -### `lvm_snapshots_snapshot_autoextend_threshold` - -Configure the given `snapshot_autoextend_threshold` setting in lvm.conf before creating snapshots. - -### `lvm_snapshots_snapshot_autoextend_percent` - -Configure the given `snapshot_autoextend_percent` setting in lvm.conf before creating snapshots. - -### `lvm_snapshots_volumes` - -This is the list of logical volumes for which snapshots are to be created and the size requirements for those snapshots. The volumes list is only required when the role is run with the check or create action. - -### `vg` - -The volume group of the origin logical volume for which a snapshot will be created. - -### `lv` - -The origin logical volume for which a snapshot will be created. - -### `size` - -The size of the logical volume according to the definition of the -[size](https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-size) -parameter of the `community.general.lvol` module. - -To create thin provisioned snapshot of a thin provisioned volume, omit the `size` parameter or set it to `0` - -## Example Playbooks - -### Create snapshots - -Perform space check and fail of there will not be enough space for all the snapshots in the set. If there is sufficient space, proceed to create snapshots for the listed logical volumes. Each snapshot will be sized to 20% of the origin volume size. Snapshot autoextend settings are configured to enable free space in the volume group to be allocated to any snapshot that may exceed 70% usage in the future. Files under /boot will be preserved. - -```yaml -- hosts: all - roles: - - name: lvm_snapshots - lvm_snapshots_set_name: ripu - lvm_snapshots_action: create - lvm_snapshots_snapshot_autoextend_threshold: 70 - lvm_snapshots_snapshot_autoextend_percent: 20 - lvm_snapshots_boot_backup: true - lvm_snapshots_volumes: - - vg: rootvg - lv: root - size: 2G - - vg: rootvg - lv: var - size: 2G -``` - -### Rollback - -This playbook rolls back the host using the snapshots created above. After verifying that all snapshots are still valid, each logical volume in the snapshot set is merged. The image files under /boot will be restored and then the host will be rebooted. - -```yaml -- hosts: all - roles: - - name: lvm_snapshots - lvm_snapshots_set_name: ripu - lvm_snapshots_action: revert - lvm_snapshots_boot_backup: true -``` - -### Commit - -A commit playbook is used when users are comfortable the snapshots are not needed any longer. Each snapshot in the snapshot set is removed and the backed up image files from /boot are deleted. - -```yaml -- hosts: all - roles: - - name: lvm_snapshots - lvm_snapshots_set_name: ripu - lvm_snapshots_action: remove - lvm_snapshots_boot_backup: true -``` diff --git a/roles/lvm_snapshots/defaults/main.yml b/roles/lvm_snapshots/defaults/main.yml deleted file mode 100644 index 8d1051d..0000000 --- a/roles/lvm_snapshots/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -lvm_snapshots_volumes: [] -lvm_snapshots_boot_backup: false diff --git a/roles/lvm_snapshots/tasks/check_for_resize.yml b/roles/lvm_snapshots/tasks/check_for_resize.yml deleted file mode 100644 index 32a34ab..0000000 --- a/roles/lvm_snapshots/tasks/check_for_resize.yml +++ /dev/null @@ -1,35 +0,0 @@ -- name: Verify that all volumes exist - ansible.builtin.include_tasks: verify_volume_exists.yml - loop: "{{ lvm_snapshots_volumes }}" - -- name: Verify that there is enough storage space - ansible.builtin.script: check.py resize '{{ lvm_snapshots_volumes | to_json }}' - args: - executable: "{{ ansible_python.executable }}" - register: lvm_snapshots_check_resize_status - failed_when: false - changed_when: false - -- name: Check the results - when: lvm_snapshots_check_resize_status.rc != 0 - block: - - name: Store check return in case of failure - ansible.builtin.set_fact: - lvm_snapshots_check_resize_failure_json: "{{ lvm_snapshots_check_resize_status.stdout | from_json }}" - - name: Check if not enough space in the volume groups - ansible.builtin.debug: - msg: Not enough space in the Volume Groups to create the requested snapshots - when: lvm_snapshots_check_resize_status.rc == 1 - - name: Check if unsupporting file system - ansible.builtin.debug: - msg: Some mounted volumes are formatted in an unsupported file system type - when: lvm_snapshots_check_resize_status.rc == 2 - - name: Check if target size if too small - ansible.builtin.debug: - msg: For some volumes the used size if bigger than the new size - when: lvm_snapshots_check_resize_status.rc == 3 - -- name: Assert results - ansible.builtin.assert: - that: lvm_snapshots_check_resize_status.rc == 0 - success_msg: All resize checks passed diff --git a/roles/lvm_snapshots/tasks/main.yml b/roles/lvm_snapshots/tasks/main.yml deleted file mode 100644 index 5aa247f..0000000 --- a/roles/lvm_snapshots/tasks/main.yml +++ /dev/null @@ -1,32 +0,0 @@ -- name: Check before resize - ansible.builtin.include_tasks: check_for_resize.yml - when: lvm_snapshots_action in ['check_for_resize', 'resize'] - -- name: Check available disk space - ansible.builtin.include_tasks: check.yml - when: lvm_snapshots_action in ['check', 'create'] - -- name: Create Snapshot - vars: - lvm_snapshots_volumes: "{{ lvm_snapshots_check_status.stdout | from_json }}" - ansible.builtin.include_tasks: create.yml - when: lvm_snapshots_action == 'create' - -- name: Calculate the list of snapshots - when: lvm_snapshots_action in ['revert', 'remove'] - block: - - name: Get list of volumes - ansible.builtin.command: "lvs --select 'lv_name =~ {{ lvm_snapshots_set_name }}$ && origin != \"\"' --reportformat json " - register: lvm_snapshots_lvs_response - changed_when: false - - name: Get LV dict List - ansible.builtin.set_fact: - lvm_snapshots_snapshots: "{{ (lvm_snapshots_lvs_response.stdout | from_json).report[0].lv }}" - -- name: Reverting to Snapshot - ansible.builtin.include_tasks: revert.yml - when: lvm_snapshots_action == 'revert' - -- name: Removing the Snapshot - ansible.builtin.include_tasks: remove.yml - when: lvm_snapshots_action == 'remove' diff --git a/roles/lvm_snapshots/tasks/remove.yml b/roles/lvm_snapshots/tasks/remove.yml deleted file mode 100644 index be2ac56..0000000 --- a/roles/lvm_snapshots/tasks/remove.yml +++ /dev/null @@ -1,13 +0,0 @@ -- name: Remove snapshots - community.general.lvol: - state: absent - vg: "{{ item.vg_name }}" - lv: "{{ item.origin }}" - snapshot: "{{ item.lv_name }}" - force: true - loop: "{{ lvm_snapshots_snapshots }}" - -- name: Remove boot backup - ansible.builtin.file: - path: "/root/boot-backup-{{ lvm_snapshots_set_name }}.tgz" - state: absent diff --git a/roles/lvm_snapshots/tasks/revert.yml b/roles/lvm_snapshots/tasks/revert.yml deleted file mode 100644 index 721553e..0000000 --- a/roles/lvm_snapshots/tasks/revert.yml +++ /dev/null @@ -1,58 +0,0 @@ -- name: Verify that all snapshots are active - ansible.builtin.include_tasks: verify_snapshot_active.yml - loop: "{{ lvm_snapshots_snapshots }}" - -- name: Required packages are present - ansible.builtin.package: - name: - - gzip - - tar - state: present - -- name: Restore boot backup - ansible.builtin.unarchive: - remote_src: true - src: "/root/boot-backup-{{ lvm_snapshots_set_name }}.tgz" - dest: /boot - when: lvm_snapshots_boot_backup - -- name: Revert to snapshots - ansible.builtin.command: "lvconvert --merge /dev/{{ item.vg_name }}/{{ item.lv_name }}" - loop: "{{ lvm_snapshots_snapshots }}" - changed_when: false - -- name: Reboot - ansible.builtin.reboot: - -- name: Check if /boot is on LVM - ansible.builtin.command: "grub2-probe --target=abstraction /boot" - changed_when: false - failed_when: false - register: boot_abstraction - -- name: Reinstall Grub to boot device - when: boot_abstraction.stdout == 'lvm' - block: - - name: Get boot device - ansible.builtin.shell: "lsblk -spnlo name $(grub2-probe --target=device /boot)" - changed_when: false - register: boot_dev_deps - - - name: Run grub2-install - ansible.builtin.command: "grub2-install {{ boot_dev_deps.stdout_lines | last }}" - changed_when: true - -- name: Remove boot backup - ansible.builtin.file: - path: "/root/boot-backup-{{ lvm_snapshots_set_name }}.tgz" - state: absent - when: lvm_snapshots_boot_backup - -- name: Wait for the snapshot to drain - ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg_name }} && lv_name = {{ item.origin }}' --reportformat json" - register: _lv_drain_check - until: (_lv_drain_check.stdout | from_json).report[0].lv[0].data_percent == "" - retries: 20 - delay: 30 - loop: "{{ lvm_snapshots_snapshots }}" - changed_when: false diff --git a/roles/remove_snapshot/README.md b/roles/remove_snapshot/README.md new file mode 100644 index 0000000..0b40549 --- /dev/null +++ b/roles/remove_snapshot/README.md @@ -0,0 +1,32 @@ +# remove_snapshot role + +The `remove_snapshot` role is used to remove snapshots. +In addition, it removes the Grub configuration and image files under /boot if it was previously backed up +It is intended to be used along with the `create_snapshot` role. + +The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities. + +## Role Variables + +### `remove_snapshot_set_name` + +The variable `remove_snapshot_set_name` is used to identify the list of volumes to be operated upon. +The role will use the following naming convention when reverting the snapshots: + +`_` + +This naming convention will be used to identify the snapshots to be removed. + +## Example Playbooks + +### Commit + +A commit playbook is used when users are comfortable the snapshots are not needed any longer. +Each snapshot in the snapshot set is removed and the backed up image files from /boot are deleted. + +```yaml +- hosts: all + roles: + - name: remove_snapshot + remove_snapshot_set_name: ripu +``` diff --git a/roles/remove_snapshot/tasks/main.yml b/roles/remove_snapshot/tasks/main.yml new file mode 100644 index 0000000..7a76699 --- /dev/null +++ b/roles/remove_snapshot/tasks/main.yml @@ -0,0 +1,23 @@ +- name: Calculate the list of snapshots + block: + - name: Get list of volumes + ansible.builtin.command: "lvs --select 'lv_name =~ {{ remove_snapshot_set_name }}$ && origin != \"\"' --reportformat json " + register: remove_snapshot_lvs_response + changed_when: false + - name: Get LV dict List + ansible.builtin.set_fact: + remove_snapshot_snapshots: "{{ (remove_snapshot_lvs_response.stdout | from_json).report[0].lv }}" + +- name: Remove snapshots + community.general.lvol: + state: absent + vg: "{{ item.vg_name }}" + lv: "{{ item.origin }}" + snapshot: "{{ item.lv_name }}" + force: true + loop: "{{ remove_snapshot_snapshots }}" + +- name: Remove boot backup + ansible.builtin.file: + path: "/root/boot-backup-{{ remove_snapshot_set_name }}.tgz" + state: absent diff --git a/roles/revert_snapshot/README.md b/roles/revert_snapshot/README.md new file mode 100644 index 0000000..fa51380 --- /dev/null +++ b/roles/revert_snapshot/README.md @@ -0,0 +1,36 @@ +# revert_snapshot role + + +The `revert_snapshot` role is used to merge snapshots to origin and reboot (i.e., rollback). +The role will verify that all snapshots in the set are still in active state before doing any merges. +This is to prevent rolling back if any snapshots have become invalidated in which case the role should fail. +In addition, it restores the Grub configuration and image files under /boot is it was previously backed up +It is intended to be used along with the `create_snapshot` role. + +The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities. + +## Role Variables + +### `revert_snapshot_set_name` + +The variable `revert_snapshot_set_name` is used to identify the list of volumes to be operated upon. +The role will use the following naming convention when reverting the snapshots: + +`_` + +This naming convention will be used to identify the snapshots to be merged. + +The `revert` action will verify that all snapshots in the set are still active state before doing any merges. This is to prevent rolling back if any snapshots have become invalidated in which case the `revert` action should fail. + +## Example Playbooks + +This playbook rolls back the host using the snapshots created using the `create_snapshot` role. +After verifying that all snapshots are still valid, each logical volume in the snapshot set is merged. +The image files under /boot will be restored and then the host will be rebooted. + +```yaml +- hosts: all + roles: + - name: revert_snapshot + revert_snapshot_set_name: ripu +``` diff --git a/roles/revert_snapshot/tasks/main.yml b/roles/revert_snapshot/tasks/main.yml new file mode 100644 index 0000000..e19e7d9 --- /dev/null +++ b/roles/revert_snapshot/tasks/main.yml @@ -0,0 +1,73 @@ +- name: Calculate the list of snapshots + block: + - name: Get list of volumes + ansible.builtin.command: "lvs --select 'lv_name =~ {{ revert_snapshot_set_name }}$ && origin != \"\"' --reportformat json " + register: revert_snapshot_lvs_response + changed_when: false + - name: Get LV dict List + ansible.builtin.set_fact: + revert_snapshot_snapshots: "{{ (revert_snapshot_lvs_response.stdout | from_json).report[0].lv }}" + +- name: Verify that all snapshots are active + ansible.builtin.include_tasks: verify_snapshot_active.yml + loop: "{{ revert_snapshot_snapshots }}" + +- name: Required packages are present + ansible.builtin.package: + name: + - gzip + - tar + state: present + +- name: Check if Boot backup exists + ansible.builtin.stat: + path: "/root/boot-backup-{{ revert_snapshot_set_name }}.tgz" + register: revert_snapshot_boot_archive_stat + +- name: Restore boot backup + ansible.builtin.unarchive: + remote_src: true + src: "{{ revert_snapshot_boot_archive_stat.stat.path }}" + dest: /boot + when: revert_snapshot_boot_archive_stat.stat.exists + +- name: Revert to snapshots + ansible.builtin.command: "lvconvert --merge /dev/{{ item.vg_name }}/{{ item.lv_name }}" + loop: "{{ revert_snapshot_snapshots }}" + changed_when: false + +- name: Reboot + ansible.builtin.reboot: + +- name: Check if /boot is on LVM + ansible.builtin.command: "grub2-probe --target=abstraction /boot" + changed_when: false + failed_when: false + register: revert_snapshot_boot_abstraction + +- name: Reinstall Grub to boot device + when: revert_snapshot_boot_abstraction.stdout == 'lvm' + block: + - name: Get boot device + ansible.builtin.shell: "lsblk -spnlo name $(grub2-probe --target=device /boot)" + changed_when: false + register: revert_snapshot_boot_dev_deps + + - name: Run grub2-install + ansible.builtin.command: "grub2-install {{ revert_snapshot_boot_dev_deps.stdout_lines | last }}" + changed_when: true + +- name: Remove boot backup + ansible.builtin.file: + path: "{{ revert_snapshot_boot_archive_stat.stat.path }}" + state: absent + when: revert_snapshot_boot_archive_stat.stat.exists + +- name: Wait for the snapshot to drain + ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg_name }} && lv_name = {{ item.origin }}' --reportformat json" + register: revert_snapshot_lv_drain_check + until: (revert_snapshot_lv_drain_check.stdout | from_json).report[0].lv[0].data_percent == "" + retries: 20 + delay: 30 + loop: "{{ revert_snapshot_snapshots }}" + changed_when: false diff --git a/roles/lvm_snapshots/tasks/verify_snapshot_active.yml b/roles/revert_snapshot/tasks/verify_snapshot_active.yml similarity index 52% rename from roles/lvm_snapshots/tasks/verify_snapshot_active.yml rename to roles/revert_snapshot/tasks/verify_snapshot_active.yml index e62e712..1e41dd4 100644 --- a/roles/lvm_snapshots/tasks/verify_snapshot_active.yml +++ b/roles/revert_snapshot/tasks/verify_snapshot_active.yml @@ -1,14 +1,14 @@ - name: Run lvs ansible.builtin.command: "lvs --select 'lv_name = {{ item.lv_name }}' --reportformat json" - register: lvm_snapshots_lvs_response + register: revert_snapshot_lvs_response changed_when: false - name: Parse report ansible.builtin.set_fact: - lvm_snapshots_lv_attr: "{{ (lvm_snapshots_lvs_response.stdout | from_json).report[0].lv[0].lv_attr }}" + revert_snapshot_lv_attr: "{{ (revert_snapshot_lvs_response.stdout | from_json).report[0].lv[0].lv_attr }}" - name: Verify that the snapshot is active ansible.builtin.assert: that: - - lvm_snapshots_lv_attr[0] == 's' - - lvm_snapshots_lv_attr[4] == 'a' + - revert_snapshot_lv_attr[0] == 's' + - revert_snapshot_lv_attr[4] == 'a' diff --git a/tests/create-snapshot.yml b/tests/create-snapshot.yml index 051b028..6d96e30 100644 --- a/tests/create-snapshot.yml +++ b/tests/create-snapshot.yml @@ -5,11 +5,10 @@ - name: Create the snapshot vars: - lvm_snapshots_action: create - lvm_snapshots_volumes: "{{ _snapshots }}" - lvm_snapshots_set_name: "{{ snapshot_set_name }}" + create_snapshot_volumes: "{{ _snapshots }}" + create_snapshot_set_name: "{{ snapshot_set_name }}" ansible.builtin.include_role: - name: lvm_snapshots + name: create_snapshot - name: Verify that the snapshot was created ansible.builtin.include_tasks: verify-snapshot-created.yml diff --git a/tests/test-create-snapshot-from-free-playbook.yml b/tests/test-create-snapshot-from-free-playbook.yml index 9f8be2f..1da6fe0 100644 --- a/tests/test-create-snapshot-from-free-playbook.yml +++ b/tests/test-create-snapshot-from-free-playbook.yml @@ -49,10 +49,9 @@ always: - name: Remove Snapshot vars: - lvm_snapshots_action: remove - lvm_snapshots_set_name: "{{ snapshot_set_name }}" + remove_snapshot_set_name: "{{ snapshot_set_name }}" ansible.builtin.include_role: - name: lvm_snapshots + name: remove_snapshot - name: Cleanup vars: volumes: diff --git a/tests/test-drain-snapshot.yml b/tests/test-drain-snapshot.yml index 1ce34b5..a16f9e6 100644 --- a/tests/test-drain-snapshot.yml +++ b/tests/test-drain-snapshot.yml @@ -24,10 +24,9 @@ - name: Revert to Snapshot vars: - lvm_snapshots_action: revert - lvm_snapshots_set_name: "{{ snapshot_set_name }}" + revert_snapshot_set_name: "{{ snapshot_set_name }}" ansible.builtin.include_role: - name: lvm_snapshots + name: revert_snapshot - name: Verify that the snapshot was completely drained block: diff --git a/tests/test-remove-playbook.yml b/tests/test-remove-playbook.yml index 2ae2865..fc34fb7 100644 --- a/tests/test-remove-playbook.yml +++ b/tests/test-remove-playbook.yml @@ -8,8 +8,8 @@ size: 1g directory: /mnt/test snapshot_set_name: demo_snap - lvm_snapshots_snapshot_autoextend_threshold: 80 - lvm_snapshots_snapshot_autoextend_percent: 15 + create_snapshot_snapshot_autoextend_threshold: 80 + create_snapshot_snapshot_autoextend_percent: 15 tasks: - name: Run pre-test steps ansible.builtin.include_tasks: pre-test-tasks.yml @@ -19,10 +19,9 @@ - name: Remove Snapshot vars: - lvm_snapshots_action: remove - lvm_snapshots_set_name: "{{ snapshot_set_name }}" + remove_snapshot_set_name: "{{ snapshot_set_name }}" ansible.builtin.include_role: - name: lvm_snapshots + name: remove_snapshot - name: Verify that the snapshot no longer exist vars: diff --git a/tests/test-resize-too-small-playbook.yml b/tests/test-resize-too-small-playbook.yml deleted file mode 100644 index ea6188f..0000000 --- a/tests/test-resize-too-small-playbook.yml +++ /dev/null @@ -1,52 +0,0 @@ -- name: Test trying to resize the volume to a size smaller than what the filesystem uses - hosts: all - become: true - vars: - volume_group: test_vg - test_directory: "/mnt/test" - volumes: - - name: test_lv - size: 4g - directory: "{{ test_directory }}" - test_file_path: "{{ test_directory }}/foo.txt" - test_file_size: 2g - tasks: - - name: Run pre-test steps - ansible.builtin.include_tasks: pre-test-tasks.yml - - - name: Create a giant file - community.general.filesize: - path: "{{ test_file_path }}" - size: "{{ test_file_size }}" - - - name: Create the snapshot and handle the failure - block: - - name: Check before resize - vars: - lvm_snapshots_action: check_for_resize - lvm_snapshots_volumes: - - vg: "{{ volume_group }}" - lv: test_lv - size: 1g - ansible.builtin.include_role: - name: lvm_snapshots - always: - - name: Cleanup - ansible.builtin.include_tasks: post-test-tasks.yml - - name: Assert that failure JSON exists - ansible.builtin.assert: - that: - - lvm_snapshots_check_resize_failure_json is defined - fail_msg: Failure JSON was not created - - name: Check Failure JSON - block: - - name: Print the Failure JSON - ansible.builtin.debug: - var: lvm_snapshots_check_resize_failure_json - - name: Check results - ansible.builtin.assert: - that: - - lvm_snapshots_check_resize_failure_json.test_vg_test_lv - - lvm_snapshots_check_resize_failure_json.test_vg_test_lv.file_system_type == 'ext4' - - lvm_snapshots_check_resize_failure_json.test_vg_test_lv.used == '2.0g' - - lvm_snapshots_check_resize_failure_json.test_vg_test_lv.requested_size == '1.0g' diff --git a/tests/test-revert-playbook.yml b/tests/test-revert-playbook.yml index 82c4c64..41a0e22 100644 --- a/tests/test-revert-playbook.yml +++ b/tests/test-revert-playbook.yml @@ -41,10 +41,9 @@ - name: Revert to Snapshot vars: - lvm_snapshots_action: revert - lvm_snapshots_set_name: "{{ snapshot_set_name }}" + revert_snapshot_set_name: "{{ snapshot_set_name }}" ansible.builtin.include_role: - name: lvm_snapshots + name: revert_snapshot - name: Verify that the file no longer exist block: diff --git a/tests/test-snapshot-too-big-playbook.yml b/tests/test-snapshot-too-big-playbook.yml index 7d9a9d2..861d134 100644 --- a/tests/test-snapshot-too-big-playbook.yml +++ b/tests/test-snapshot-too-big-playbook.yml @@ -8,8 +8,8 @@ size: 8g directory: /mnt/test snapshot_set_name: demo_snap - lvm_snapshots_snapshot_autoextend_threshold: 80 - lvm_snapshots_snapshot_autoextend_percent: 15 + create_snapshot_snapshot_autoextend_threshold: 80 + create_snapshot_snapshot_autoextend_percent: 15 tasks: - name: Run pre-test steps ansible.builtin.include_tasks: pre-test-tasks.yml @@ -27,13 +27,13 @@ ansible.builtin.include_tasks: post-test-tasks.yml - name: Print the failure JSON if exists ansible.builtin.debug: - var: lvm_snapshots_check_failure_json - when: lvm_snapshots_check_failure_json is defined + var: create_snapshot_check_failure_json + when: create_snapshot_check_failure_json is defined - name: Check results ansible.builtin.assert: that: - - lvm_snapshots_check_failure_json is defined - - lvm_snapshots_check_failure_json.test_vg - - lvm_snapshots_check_failure_json.test_vg.size == 9646899200 - - lvm_snapshots_check_failure_json.test_vg.free == 1056964608 - - lvm_snapshots_check_failure_json.test_vg.requested_size == 8589934592 + - create_snapshot_check_failure_json is defined + - create_snapshot_check_failure_json.test_vg + - create_snapshot_check_failure_json.test_vg.size == 9646899200 + - create_snapshot_check_failure_json.test_vg.free == 1056964608 + - create_snapshot_check_failure_json.test_vg.requested_size == 8589934592