Compare commits

...

114 Commits

Author SHA1 Message Date
f39fea0f89 Calculate PE size, and subtract from the shrink size; Attempts to fix allocation/pretending size issue 2025-06-23 17:35:28 -04:00
23c7547f32 Restore shrink size to defined value 2025-06-05 10:47:20 -04:00
78902c1371 Remove 4MB from calculated LVB size 2025-06-05 10:26:33 -04:00
8e057342da pvsqueeze restoration 2025-06-05 10:11:02 -04:00
8106b95478 Remove pvsqueeze execution tasks for testing 2025-06-05 04:49:15 -04:00
a3d5c17253 Add functions to filter plugin; Tweaks 2025-06-05 02:51:07 -04:00
5cb1b9d68f Add tasks to capture block size instead of filesystem size 2025-06-04 22:50:46 -04:00
9a9aa83adb Tweaks 2025-06-03 10:23:23 -04:00
53f3dbc6c8 Remove end_host 2025-06-03 10:21:30 -04:00
50faad8c17 . 2025-06-03 10:20:53 -04:00
8b1e65188c Tweaks 2025-06-03 10:15:28 -04:00
68140fc346 Minor tweaks 2025-06-02 13:09:01 -04:00
d0caf12112 Guardrails to ensure we dont run target LV out of space; minor tweaks 2025-06-02 12:28:17 -04:00
37d44db328 Only import playbook if we need to; define defaults 2025-05-27 16:09:26 -04:00
87a8235cc0 Remove end_host; fixes workflow missing variables 2025-05-27 15:06:42 -04:00
7abd1e74e2 Assertion fix 2025-05-27 14:02:13 -04:00
716698202d Refactored for idempotency 2025-05-22 15:52:27 -04:00
e75578352f Task addition for idempotency 2025-05-22 15:26:47 -04:00
5e2ecd4f21 Task name update 2025-05-21 16:53:21 -04:00
16eed5fec5 Refinements 2025-05-15 17:21:50 -04:00
999ab17dfd Refinements 2025-05-12 16:26:37 -04:00
a64abecc65 Fixes to assertion logic, and cleanup of unused stuffs 2025-05-12 14:25:51 -04:00
2d7764c183 Updates 2025-05-07 17:00:48 -04:00
9008c28110 Add debugging 2025-05-05 18:16:24 -04:00
efaf76ff23 Do you work under AAP now? 2025-05-05 09:45:48 -04:00
ff5eeea88b More fixes to the fixes 2025-05-02 17:40:12 -04:00
2c9fcdef5a Fixes to the fixes 2025-05-02 17:25:59 -04:00
155c6e2631 Fixes 2025-05-01 12:25:58 -04:00
07d169f527 Add set stats; comment debugging 2025-04-30 22:32:02 -04:00
a3021ac84e Updates to allow Bigboot to decrement from the default target size, down to a minimum threshold of 1GB 2025-04-30 22:22:11 -04:00
212ace2d6d pvsqueeze.sh update 2025-03-20 15:26:02 -04:00
a4a9c2fa89 Update ReaR restore 2025-03-06 01:08:15 -05:00
2efe662c0e Remove success check; its in the collection now 2025-03-04 17:38:25 -05:00
93d256af80 Task shift 2025-03-04 16:59:18 -05:00
35531c86d7 Removing debugging; probably a sync/timing issue -- gather_facts for nfs_export 2025-03-04 16:44:07 -05:00
51e313692d Add some debugging 2025-03-04 16:07:04 -05:00
91a2423e2e Bleh 2025-03-04 15:45:17 -05:00
0cc014824c Updates to Bigboot ReaR based on updates to ReaR 2025-03-04 15:32:57 -05:00
212a686c90 Updates to Bigboot ReaR based on updates to ReaR 2025-03-04 15:25:00 -05:00
3c05c4cd5d Path fix 2025-03-03 14:42:18 -05:00
da8fda8555 Updates, renames, etc. 2025-02-28 01:11:39 -05:00
09fad57ac6 Add log archive and task shuffle 2025-02-28 00:41:14 -05:00
d583c511bb Vars fix 2025-02-27 23:57:45 -05:00
8cc3e3fd48 Vars cleanup 2025-02-27 23:54:24 -05:00
e0f478e2f9 Task name update 2025-02-27 23:38:32 -05:00
0508017e08 Remove debug 2025-02-27 23:14:31 -05:00
17c95aad22 Add debug 2025-02-27 23:12:55 -05:00
94d53287d0 More logic fixes 2025-02-27 21:32:49 -05:00
a0054be5e6 Logic fixes 2025-02-27 21:16:14 -05:00
b2d6c4467e Variable fixes 2025-02-27 20:34:54 -05:00
2eaeb48fee Fix additional naming issues 2025-02-27 20:26:32 -05:00
29f8064441 Fix variable name causing error 2025-02-27 20:06:25 -05:00
d4561bad99 no_log: false for debugging 2025-02-27 20:01:34 -05:00
4c75f10ec3 Updates for bigboot_rear_backup.yml; Add bigboot_rear_clean.yml 2025-02-27 19:53:26 -05:00
2bce76e413 Remove: Update backup path to include hostname 2025-02-26 17:50:18 -05:00
7c78438d2d Update backup path to include hostname 2025-02-26 16:25:55 -05:00
8417a28302 Provide fsid for Bigboot share; add missing call 2025-02-22 19:52:13 -05:00
b91c7c60fb Provide fsid for Bigboot share 2025-02-22 19:38:40 -05:00
5aa0380c2a Call nfs_export role with backup path defined; fixed 2025-02-22 17:42:11 -05:00
6a9d2833fd Call role with backup path defined; fixed 2025-02-22 17:20:07 -05:00
eca5be5592 Call role with backup path defined 2025-02-22 17:16:39 -05:00
3cc182de09 Updates for the workflow 2025-02-22 17:15:02 -05:00
52854491f2 Remove conditional 2025-02-21 14:33:40 -05:00
238a348a5f Restore Bigboot execution 2025-02-05 15:42:33 -05:00
d64de081f7 Remove Bigboot execution for now...skip straight to failure 2025-02-05 15:36:14 -05:00
f8780d1272 Failures will result in cleanup. Pt 2. 2025-02-05 15:35:17 -05:00
d0888f3737 Failures will result in cleanup. 2025-02-05 15:02:26 -05:00
8e194f9fe5 Re-add local copy of infra.lvm_snapshots so we can better tailor the lab experience 2025-01-31 16:28:01 -05:00
3c5f740320 this was stupid 2025-01-31 16:27:20 -05:00
e343b212fb Re-add local copy of infra.lvm_snapshots so we can better tailor the lab experience 2025-01-31 16:18:55 -05:00
70a9a2e665 Add option to force ReaR backup: draft 2025-01-22 17:20:33 -05:00
be2ee9be1d Add option to force ReaR backup: draft 2025-01-22 17:09:28 -05:00
96c5cbd44c Add option to force ReaR backup: draft 2025-01-22 17:05:07 -05:00
9e94d3425d Add option to force ReaR backup: draft 2025-01-22 17:02:10 -05:00
15a7a3531f Add option to force ReaR backup: draft 2025-01-22 16:58:04 -05:00
5f13492ee4 Remove local copy of infra.lvm_snapshots; Use feature branch version from Github 2025-01-08 17:28:27 -05:00
e68d127845 Update assertion 2025-01-08 15:20:13 -05:00
9c87c1311e Updates to check_device.yml; testing 2025-01-08 15:04:47 -05:00
07e9fac987 Cleanup; testing 2025-01-08 12:42:24 -05:00
0606ff81b2 Finalize development of shrink_lv fix; Testing 2025-01-08 12:38:23 -05:00
078da59120 Add assertion fix to local copy of collection; Fix deployed to Github - needs testing also 2025-01-07 16:57:06 -05:00
d3cf06fbd1 Comment out additional cleanup tasks 2025-01-07 15:10:15 -05:00
493a5758b7 Comment out service checks; This should cause errors initially 2025-01-07 14:54:50 -05:00
6c69d48268 Move to using verified_reboot to manage reboots for Bigboot; infra.lvm_snapshots is local for testing this 2024-11-07 17:28:03 -05:00
e4245dfb58 remove dummy collection 2024-10-18 10:45:52 -04:00
09bcd6347e add dummy collection to test failure 2024-10-18 10:44:53 -04:00
9d53dada64 add script cleanup task 2024-10-18 10:33:26 -04:00
d02a3809cb more task name modification 2024-10-17 15:05:19 -04:00
868b891e09 task name modification 2024-10-17 15:04:53 -04:00
5bd52ff627 change method for deploying and executing pvsqueeze.sh due to environmental issues 2024-10-17 14:33:02 -04:00
af38c279da add pvsqueeze.sh script, and execute it prior to ALL bigboot executions 2024-10-14 14:24:28 -04:00
0076a60651 add bigboot_pv datas... correctly now 2024-10-14 12:11:41 -04:00
73c8171bbe add bigboot_pv datas 2024-10-14 12:00:41 -04:00
82da0dd6ac add bigboot_cleanup.yml playbook 2024-09-12 18:07:42 -04:00
ca0ecb570d add bigboot_rear_restore.yml playbook 2024-09-11 16:16:16 -04:00
fde58e368d Check connectivity to NFS servers and update exports accordingly on those which succeed the connection 2024-09-10 21:52:35 -04:00
2bf5969f51 Add default 2024-08-29 21:49:59 -04:00
a02753e91f Update collection reference 2024-08-29 18:08:07 -04:00
f2e6dcc9e5 Add fsck cleanup to cleanup.yml; comment out pre-checks for now 2024-08-27 15:33:07 -04:00
7b447e0fd2 Use bigboot_size_target instead of the calculated size diff 2024-08-20 17:38:13 -04:00
7ad8d715af Update collection reference 2024-08-20 17:29:45 -04:00
b0e94aba64 Fix variable references 2024-08-20 14:54:36 -04:00
3b8624fea8 Fix variable references 2024-08-20 14:36:56 -04:00
d41609c832 Merge pull request 'Sync with upstream release 2.1.0' (#6) from wip into develop
Reviewed-on: #6
2024-08-20 14:22:16 -04:00
80c1e0a09c Revert changes which are not needed 2024-08-20 14:20:02 -04:00
862908fe0c Sync with develop branch 2024-08-20 14:12:31 -04:00
035fc1b57c Add initial pre-checks 2024-08-19 17:53:08 -04:00
9f9a798d91 more task rename; block remove 2024-08-07 10:57:30 -04:00
677ba57401 more task rename 2024-08-07 10:54:20 -04:00
2a8ce9fa89 task rename 2024-08-07 10:51:40 -04:00
684c1bef51 cleanup and add code block 2024-08-07 10:17:05 -04:00
e7e90aaaf5 cleanup 2024-08-06 11:47:21 -04:00
aba39cbad4 new service state handling 2024-08-06 11:30:30 -04:00
fa7f4d0e31 sync with upstream; re-work in progress 2024-07-16 15:19:12 -04:00
111 changed files with 3601 additions and 249 deletions

6
.gitignore vendored
View File

@ -5,6 +5,10 @@ misc
filter_plugins/__pycache__
filter_plugins/*.bak
python/
collections/ansible_collections
collections/ansible_collections/ansible*
collections/ansible_collections/community*
collections/ansible_collections/rhc*
roles/autofsck
roles/verified_reboot
*.bak
tasks/check_space_fallback_local.yml

View File

@ -14,6 +14,7 @@ timeout = 30
host_key_checking = false
display_skipped_hosts = false
deprecation_warnings = false
show_custom_stats = true
# callback_whitelist is deprecated
# we only include here for backwards compatibility

13
bigboot_cleanup.yml Normal file
View File

@ -0,0 +1,13 @@
---
- name: Cleanup from previous Bigboot executions
hosts: all
become: true
gather_facts: true
strategy: free
vars_files:
- bigboot_vars.yml
tasks:
- name: Cleanup from any previous executions
ansible.builtin.import_tasks: tasks/cleanup.yml

View File

@ -9,23 +9,10 @@
- bigboot_vars.yml
tasks:
- name: Perform service and filesystem checks prior to Bigboot execution
when:
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | default(false) | bool
block:
- name: Check for and disable services exceeding the timeout threshold
ansible.builtin.import_tasks: tasks/check_systemd_services.yml
- name: Perform filesystem check prior to Bigboot execution
ansible.builtin.import_tasks: tasks/grub_filesystem_check.yml
- name: Extend the timeout values for physical hosts
ansible.builtin.set_fact:
initramfs_post_reboot_delay: 300
initramfs_reboot_timeout: 14400
when:
- "'host' in ansible_virtualization_role"
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | default(false) | bool
- name: Shrink the logical volume to support /boot expansion
ansible.builtin.import_role:
@ -35,41 +22,48 @@
- device: "{{ bigboot_data[inventory_hostname]['bigboot_adjacent_lvm_device'] }}"
size: "{{ bigboot_data[inventory_hostname]['bigboot_lv_shrink_size'] | int }}"
when:
- bigboot_data[inventory_hostname]['bigboot_execute_shrink_lv'] | bool
- bigboot_data[inventory_hostname]['bigboot_execute_shrink_lv'] | default(false) | bool
- name: Shift free extents and expand /boot
when:
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | bool
block:
# Under normal circumstances we'd use this method, but unable to do
# so in certain environments so lets use copy/command instead
# - name: Shift free extents to end of PV
# ansible.builtin.script: "scripts/pvsqueeze.sh {{ bigboot_data[inventory_hostname]['bigboot_pv'] }}"
# register: bigboot_pvsqueeze
- name: Copy pvsqueeze.sh script to host
ansible.builtin.copy:
src: scripts/pvsqueeze.sh
dest: /var/tmp/pvsqueeze.sh
owner: root
group: root
mode: '0700'
- name: Shift free extents to end of PV
ansible.builtin.command:
cmd: "/var/tmp/pvsqueeze.sh {{ bigboot_data[inventory_hostname]['bigboot_pv'] }}"
register: bigboot_pvsqueeze
- name: Remove pvsqueeze.sh from host
ansible.builtin.file:
path: /var/tmp/pvsqueeze.sh
state: absent
- name: Expand the /boot partition as requested
ansible.builtin.import_role:
name: infra.lvm_snapshots.bigboot
vars:
bigboot_size: "{{ bigboot_data[inventory_hostname]['bigboot_size'] }}"
when:
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | bool
bigboot_partition_size: "{{ bigboot_data[inventory_hostname]['bigboot_size_target'] }}"
- name: Ensure service facts are available
ansible.builtin.service_facts:
- name: Restore named-chroot service to its pre-Bigboot state
ansible.builtin.service:
name: "{{ bigboot_named_chroot_service }}"
state: "{{ bigboot_data[inventory_hostname]['bigboot_named_chroot_running'] }}"
enabled: "{{ bigboot_data[inventory_hostname]['bigboot_named_chroot_enabled'] }}"
- name: Failure on request
ansible.builtin.fail:
msg: "Ansible job has been failed upon request."
when:
- ansible_facts['services'][bigboot_named_chroot_service] is defined
- bigboot_fail_request | default(false) | bool
- name: Restore Docker service to its pre-Bigboot state
ansible.builtin.service:
name: "{{ bigboot_docker_service }}"
state: "{{ bigboot_data[inventory_hostname]['bigboot_docker_running'] }}"
enabled: "{{ bigboot_data[inventory_hostname]['bigboot_docker_enabled'] }}"
when:
- ansible_facts['services'][bigboot_docker_service] is defined
- name: Re-enabling services previously disabled
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop: "{{ bigboot_systemd_disabled_services }}"
when:
- bigboot_systemd_disabled_services is defined
- bigboot_systemd_disabled_services | length > 0
rescue:
- name: Cleanup from any previous executions
ansible.builtin.import_tasks: tasks/cleanup.yml

View File

@ -1,6 +1,27 @@
---
- name: Perform a ReaR backup before the /boot expansion
ansible.builtin.import_playbook: rhc.rear.rear_backup
- name: ReaR Backup Playbook
hosts: all
become: true
gather_facts: true
strategy: free
vars_files:
- bigboot_vars.yml
tasks:
- name: Import rear_vars role
ansible.builtin.import_role:
name: rhc.rear.rear_vars
- name: Fetch the template name from NFS exports
ansible.builtin.set_fact:
job_template_name: "{{ nfs_exports_template_name }}"
- name: Perform ReaR Backup
when:
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | default(false) | bool
- not rear_backup_skip | default(false) | bool
block:
- name: Perform ReaR backup
ansible.builtin.include_role:
name: rhc.rear.rear_backup

6
bigboot_rear_clean.yml Normal file
View File

@ -0,0 +1,6 @@
---
- name: Cleanup ReaR artifacts on successful Bigboot
ansible.builtin.import_playbook: rhc.rear.rear_remove
when:
- bigboot_data[inventory_hostname]['bigboot_execute_bigboot'] | default(false) | bool
- not rear_backup_skip | default(false) | bool

View File

@ -1,13 +1,46 @@
---
- name: Perform logical volume and boot parition resizing as needed
- name: Check SSH connectivity and create group of reachable hosts
hosts: rear_server
become: true
gather_facts: false
become: true
tasks:
- name: Check SSH connectivity
ansible.builtin.ping:
register: result
failed_when: false
- name: Clear unreachable host errors
ansible.builtin.meta: clear_host_errors
- name: Add reachable hosts to group
ansible.builtin.group_by:
key: reachable_hosts
when:
- result['ping'] is defined
- "'pong' in result['ping']"
- name: Update NFS exports on reachable servers
hosts: reachable_hosts
become: true
gather_facts: true
vars_files:
- bigboot_vars.yml
tasks:
- name: Import rear_vars role
ansible.builtin.import_role:
name: rhc.rear.rear_vars
- name: Grab the template name
ansible.builtin.set_stats:
aggregate: false
per_host: false
data:
nfs_exports_template_name: "{{ tower_job_template_name }}"
- name: Create IP list and add to NFS exports
ansible.builtin.include_tasks: tasks/rear_nfs_exports.yml
loop: "{{ bigboot_data | dict2items }}"
@ -16,4 +49,3 @@
when:
- item['value']['bigboot_execute_bigboot'] | default(false) | bool
- not rear_backup_skip | default(false) | bool

21
bigboot_rear_restore.yml Normal file
View File

@ -0,0 +1,21 @@
---
- name: Execute ReaR restore
hosts: all
become: true
gather_facts: true
strategy: free
vars_files:
- bigboot_vars.yml
tasks:
- name: Import rear_vars role
ansible.builtin.import_role:
name: rhc.rear.rear_vars
- name: Restore from ReaR backup
ansible.builtin.include_role:
name: rhc.rear.rear_restore
- name: Cleanup from any previous executions
ansible.builtin.import_tasks: tasks/cleanup.yml

View File

@ -12,17 +12,13 @@
- name: Cleanup from any previous executions
ansible.builtin.import_tasks: tasks/cleanup.yml
- name: Stop and disable the Docker service if present and running
ansible.builtin.import_tasks: tasks/disable_docker_service.yml
- name: Stop and disable the named-chroot service if present and running
ansible.builtin.import_tasks: tasks/disable_named_chroot_service.yml
- name: Set boot device details
ansible.builtin.import_tasks: tasks/capture_boot_device_details.yml
- name: Set logical volume information
ansible.builtin.import_tasks: tasks/capture_lv_device_details.yml
when:
- bigboot_execute_bigboot | default(false) | bool
- name: Set environment for subsequent workflow nodes
ansible.builtin.set_stats:
@ -32,14 +28,11 @@
combine({inventory_hostname:
{
'bigboot_execute_bigboot': bigboot_execute_bigboot,
'bigboot_execute_shrink_lv': bigboot_execute_shrink_lv,
'bigboot_adjacent_lvm_device': bigboot_adjacent_lvm_device,
'bigboot_lv_shrink_size': bigboot_lv_shrink_size | int,
'bigboot_size': bigboot_size,
'bigboot_docker_running': bigboot_docker_running,
'bigboot_docker_enabled': bigboot_docker_enabled,
'bigboot_named_chroot_running': bigboot_named_chroot_running,
'bigboot_named_chroot_enabled': bigboot_named_chroot_enabled,
'bigboot_execute_shrink_lv': bigboot_execute_shrink_lv | default(false),
'bigboot_adjacent_lvm_device': bigboot_adjacent_lvm_device | default(omit),
'bigboot_lv_shrink_size': bigboot_lv_shrink_size | default(0) | int,
'bigboot_pv': bigboot_pv | default(omit),
'bigboot_size_target': bigboot_size_target_fallback | default(bigboot_size_target),
'ip_addresses': ansible_all_ipv4_addresses,
'server_hostname': ansible_hostname
}

View File

@ -0,0 +1,5 @@
---
exclude_paths:
- .github
- changelogs
...

View File

@ -0,0 +1,18 @@
name: Ansible Lint
on:
- push
- pull_request
- workflow_call
jobs:
ansible-lint:
name: Ansible Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run ansible-lint
uses: ansible/ansible-lint@main

View File

@ -0,0 +1,15 @@
name: CodeSpell
on:
- push
- pull_request
- workflow_call
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: codespell-project/actions-codespell@master

View File

@ -0,0 +1,59 @@
name: PyCodeStyle
on:
- push
- pull_request
- workflow_call
jobs:
python-3:
name: PyCodeStyle
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install pylint
run: |
python -m pip install --upgrade pip
pip install pycodestyle
- name: Analysing the code with pylint
run: |
pycodestyle --config=./.pycodestyle .
python-2-7:
name: PyCodeStyle
runs-on: ubuntu-latest
container:
image: python:2.7.18-buster
strategy:
fail-fast: false
matrix:
python-version: ["2.7"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pylint
run: |
python -m pip install --upgrade pip
pip install pycodestyle
- name: Analysing the code with pylint
run: |
pycodestyle --config=./.pycodestyle .

View File

@ -0,0 +1,59 @@
name: PyLint
on:
- push
- pull_request
- workflow_call
jobs:
python-3:
name: PyLint
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install pylint
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Analysing the code with pylint
run: |
find . -name '*.py' -exec pylint {} \;
python-2-7:
name: PyLint
runs-on: ubuntu-latest
container:
image: python:2.7.18-buster
strategy:
fail-fast: false
matrix:
python-version: ["2.7"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pylint
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Analysing the code with pylint
run: |
find . -name '*.py' -exec pylint {} \;

View File

@ -0,0 +1,140 @@
---
name: Release
on:
release:
types:
- published
jobs:
ansible-lint:
uses: redhat-cop/infra.lvm_snapshots/.github/workflows/ansible-lint.yml@main
pylint:
uses: redhat-cop/infra.lvm_snapshots/.github/workflows/pylint.yml@main
pycodestyle:
uses: redhat-cop/infra.lvm_snapshots/.github/workflows/pycodestyle.yml@main
shellcheck:
uses: redhat-cop/infra.lvm_snapshots/.github/workflows/shellcheck.yml@main
codespell:
uses: redhat-cop/infra.lvm_snapshots/.github/workflows/codespell.yml@main
prechecks:
needs:
- ansible-lint
- pylint
- pycodestyle
- shellcheck
- codespell
runs-on: ubuntu-latest
steps:
- run: >-
python -c "assert set([
'${{ needs.ansible-lint.result }}',
'${{ needs.pylint.result }}',
'${{ needs.pycodestyle.result }}',
'${{ needs.shellcheck.result }}',
'${{ needs.codespell.result }}',
]) == {'success'}"
infra_release:
needs:
- prechecks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install Ansible
run: pip install --upgrade ansible-core
- name: Update version in galaxy.yml
run: sed -i 's/version:.*$/version:{{:dumb_space_issue:}}${{ github.ref_name }}/' galaxy.yml; sed -i 's/{{:dumb_space_issue:}}/ /' galaxy.yml
- name: Build collection
run: ansible-galaxy collection build -vvv
shell: bash
working-directory: ${{ vars.GITHUB_PATH }}
- name: Get tar name
run: echo "tar_file=$(ls | grep '.tar')" >> $GITHUB_OUTPUT
id: build
- name: Get version
run: echo "NUM=$(cat galaxy.yml | grep version | cut -d ':' -f 2 | awk '{print $1}')" >> $GITHUB_OUTPUT
id: vers
- name: Install collection
run: ansible-galaxy collection install ./infra-lvm_snapshots-${{ steps.vers.outputs.NUM }}.tar.gz -p /home/runner/collections
shell: bash
working-directory: ${{ vars.GITHUB_PATH }}
- name: Publish to Automation Hub
run: |
cat << EOF > ansible.cfg
[galaxy]
server_list = rh_automation_hub
[galaxy_server.rh_automation_hub]
url=https://cloud.redhat.com/api/automation-hub/
auth_url=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token
token=${{ secrets.CONSOLE_API_KEY }}
EOF
ansible-galaxy collection publish ${{ steps.build.outputs.tar_file }}
rm ansible.cfg
- name: Publish to galaxy
run: ansible-galaxy collection publish --api-key=${{ secrets.GALAXY_INFRA_KEY }} ${{ steps.build.outputs.tar_file }}
- name: Upload files to tag
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.build.outputs.tar_file }}
tag: ${{ github.ref }}
overwrite: true
changelog:
needs:
- infra_release
runs-on: ubuntu-latest
env:
ANSIBLE_FORCE_COLOR: 1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install Ansible
run: pip install --upgrade ansible-core antsibull-changelog
- name: Update version in galaxy.yml
run: sed -i 's/version:.*$/version:{{:dumb_space_issue:}}${{ github.ref_name }}/' galaxy.yml; sed -i 's/{{:dumb_space_issue:}}/ /' galaxy.yml
- name: Run changelog
run: antsibull-changelog release --verbose --version ${{ github.ref_name }}
- name: Create Pull Request
id: prcreate
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update changelog ${{ github.ref_name }}
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
signoff: false
base: main
branch: changelog-patches
delete-branch: true
title: '[RELEASE] Update changelog ${{ github.ref_name }}'
body: |
Update changelog
- Updated with changelog for release ${{ github.ref_name }}
- Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
labels: |
changelog
automated pr
draft: false
...

View File

@ -0,0 +1,18 @@
name: ShellCheck
on:
- push
- pull_request
- workflow_call
jobs:
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master

View File

@ -0,0 +1 @@
inventory.yml

View File

@ -0,0 +1,2 @@
[pycodestyle]
max-line-length=120

View File

@ -0,0 +1,78 @@
[MESSAGES CONTROL]
disable=
# "F" Fatal errors that prevent further processing
# import-error,
# "I" Informational noise
# "E" Error for important programming issues (likely bugs)
# no-member,
# no-name-in-module,
# raising-bad-type,
# "W" Warnings for stylistic problems or minor programming issues
# no-absolute-import,
# arguments-differ,
# cell-var-from-loop,
# fixme,
; lost-exception,
; no-init,
; pointless-string-statement,
; protected-access,
; redefined-outer-name,
; relative-import,
; undefined-loop-variable,
; unsubscriptable-object,
# unused-argument,
; unused-import,
; unspecified-encoding,
# "C" Coding convention violations
; bad-continuation,
; missing-docstring,
; wrong-import-order,
; use-maxsplit-arg,
; consider-using-dict-items,
; consider-using-enumerate,
# "R" Refactor recommendations
; duplicate-code,
; no-self-use,
; too-few-public-methods,
; too-many-branches,
; too-many-locals,
; too-many-statements,
; consider-using-from-import,
; use-list-literal,
; use-dict-literal,
# new for python3 version of pylint
; useless-object-inheritance,
; consider-using-set-comprehension, # pylint3 force to use comprehension in place we don't want (py2 doesn't have these options, for inline skip)
; unnecessary-pass,
; invalid-envvar-default, # pylint3 warnings envvar returns str/none by default
; bad-option-value, # python 2 doesn't have import-outside-toplevel, but in some case we need to import outside toplevel
; super-with-arguments, # required in python 2
; raise-missing-from, # no 'raise from' in python 2
; use-a-generator, # cannot be modified because of Python2 support
; consider-using-with, # on bunch spaces we cannot change that...
; duplicate-string-formatting-argument, # TMP: will be fixed in close future
consider-using-f-string, # sorry, not gonna happen, still have to support py2
; use-dict-literal
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=120
[DESIGN]
max-args=11 # 2x + 1 from default
max-attributes=21 # 4x + 1 from default
[REPORTS]
msg-template='[{msg_id} {symbol}] {msg} File: {path}, line {line}, in {obj}'
[BASIC]
# In order to make a check more strict add proper regex http://pylint-messages.wikidot.com/messages:c0103
argument-rgx=.*
attr-rgx=.*
class-rgx=.*
const-rgx=.*
function-rgx=.*
method-rgx=.*
module-rgx=.*
variable-rgx=.*
inlinevar-rgx=.*

View File

@ -0,0 +1,4 @@
rules:
indentation:
spaces: 2
indent-sequences: false

View File

@ -0,0 +1,167 @@
=====================================
LVM Snapshot Linux Role Release Notes
=====================================
.. contents:: Topics
v2.1.1
======
Minor Changes
-------------
- Improve documentation and example usage of initramfs role
- Improved console logging of bigboot progress to include percent complete
- Support check mode when using the bigboot role
Bugfixes
--------
- Fix bigboot repeatedly increasing the partition size
v2.1.0
======
Major Changes
-------------
- add bigboot support for Btrfs next partition
Minor Changes
-------------
- do bigboot LVM changes with Ansible instead of pre-mount hook
- new bigboot_partition_size variable to make bigboot role more idempotent
- show console log output from bigboot even if quiet kernel arg is set
v2.0.3
======
Bugfixes
--------
- Fix how locking is disabled for newer LVM versions
- Fix missing role metadata
- Fix potential space exhaustion when restoring previous initramfs
v2.0.2
======
Minor Changes
-------------
- Add bigboot progress messages so inpatient operators don't think their server is hung
Bugfixes
--------
- Clean up bad math in bigboot.sh
- Fix bigboot device not found error
- Fix bigboot fail when autoactivation property not set
- Fix vgs not found error
- Round down requested size to multiple of extent size
- Shorten bigboot.sh usage help message to not exceed the kmsg buffer
- Use sectors with sfdisk
v2.0.1
======
Minor Changes
-------------
- Add publish to Automation Hub to release workflow
Bugfixes
--------
- Fix release workflow prechecks
v2.0.0
======
Minor Changes
-------------
- bigboot - Rename internal variables with role name prefix
- initramfs - Rename internal variables with role name prefix
- shrink_lv - Rename internal variables with role name prefix
Breaking Changes / Porting Guide
--------------------------------
- Split lvm_snapshots role into snapshot_create, snapshot_revert and snapshot_remove
v1.1.2
======
Minor Changes
-------------
- Updated links in docs and workflows to reflect move to redhat-cop org
v1.1.1
======
Bugfixes
--------
- Fix "Failed to list block device properties" error
- Fix dracut path
v1.1.0
======
Major Changes
-------------
- New role, bigboot, to increase the boot partition while moving, and shrinking if needed, the adjacent partition
- New role, initramfs, to execute an atomic flow of building and using a temporary initramfs in a reboot and restoring the original one
- New role, shrink_lv, to decrease logical volume size along with the filesystem
v1.0.3
======
Minor Changes
-------------
- Changed the lvm_snapshots_boot_backup var default to false
- Removed unimplemented lvm_snapshots_use_boom var from the docs
- Revert - wait for snapshot to drain before returning
Bugfixes
--------
- Add task to ensure tar package is present
- Grub needs reinstall if /boot is on LVM
- Wrong kernel version booting after rolling back
v1.0.2
======
Minor Changes
-------------
- Create snapshots with normalized sizes
Bugfixes
--------
- Existing Snapshots with Different Name Cause verify_no_existing_snapshot.yml to Fail
v1.0.1
======
Major Changes
-------------
- Initial MVP release
Minor Changes
-------------
- Add boot backup support
- Add support for checking before resizing logical volumes
v1.0.0
======

View File

@ -0,0 +1,3 @@
@swapdisk
@ygalblum
@heatmiser

View File

@ -0,0 +1,5 @@
# Contributing
Thank you for your interest in contributing to the LVM Snapshots Collection. All we ask is that contributors please observe the [Ansible Community Guidelines](https://docs.ansible.com/ansible/devel/community/index.html) and follow the [Ansible Collections Contributor Guide](https://docs.ansible.com/ansible/devel/community/contributions_collections.html). We look forward to reviewing your pull request.
Everyone is invited to participate. We welcome first timers as well as experienced open source contributors. If you are unsure how to get started with your contribution, open a [new issue](https://github.com/redhat-cop/infra.lvm_snapshots/issues/new/choose) explaining what you want to do and we'll do our best to help!

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Red Hat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,84 @@
# LVM Snapshots Collection
[![Ansible Lint](https://github.com/redhat-cop/infra.lvm_snapshots/workflows/Ansible%20Lint/badge.svg?event=push)](https://github.com/redhat-cop/infra.lvm_snapshots/actions) [![PyLint](https://github.com/redhat-cop/infra.lvm_snapshots/workflows/PyLint/badge.svg?event=push)](https://github.com/redhat-cop/infra.lvm_snapshots/actions) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8141/badge)](https://www.bestpractices.dev/projects/8141)
## Overview
A reliable snapshot/rollback capability is a key feature required to enable the success of RHEL In-place Upgrade automation solutions. Without it, users will be wary of using the solution because of the potential risk that their applications may not function properly after the OS upgrade. Including automation so that snapshot creation happens right before the OS upgrade reduces this risk. If there are any application issues uncovered after the OS upgrade, a rollback playbook can be executed to instantly revert the environment back to the original state as it was before the upgrade. Application teams will no longer have an excuse not to use in-place upgrades to bring their RHEL estate into compliance.
## Requirements
### Ansible Version
This collection requires ansible from version `2.14.0` and above
### Collections
This collection depends on the following collections
```yaml
- "community.general": "*"
```
## Roles
These are the roles included in the collection. Follow the links below to see the detailed documentation and example playbooks for each role.
- [`snapshot_create`](./roles/snapshot_create/) - controls the creation for a defined set of LVM snapshot volumes
- [`snapshot_remove`](./roles/snapshot_remove/) - used to remove snapshots previously created using the `snapshot_create` role
- [`snapshot_revert`](./roles/snapshot_revert/) - used to revert to snapshots previously created using the `snapshot_create` 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
## Installing the collection from Ansible Galaxy
Before using this collection, you need to install it with the Ansible Galaxy command-line tool:
```bash
ansible-galaxy collection install infra.lvm_snapshots
```
You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format:
```yaml
---
collections:
- name: infra.lvm_snapshots
```
Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically when you upgrade the `ansible` package. To upgrade the collection to the latest available version, run the following command:
```bash
ansible-galaxy collection install infra.lvm_snapshots --upgrade
```
You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax to install version `1.0.0`:
```bash
ansible-galaxy collection install infra.lvm_snapshots:==1.0.0
```
See [Using Ansible collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more details.
## Contributing
We appreciate participation from any new contributors. Get started by opening an issue or pull request. Refer to our [contribution guide](CONTRIBUTING.md) for more information.
## Reporting issues
Please open a [new issue](https://github.com/redhat-cop/infra.lvm_snapshots/issues/new/choose) for any bugs or security vulnerabilities you may encounter. We also invite you to open an issue if you have ideas on how we can improve the solution or want to make a suggestion for enhancement.
## More information
This collection is just one building block of our larger initiative to make RHEL in-place upgrade automation that works at enterprise scale. Learn more about our end-to-end approach for automating RHEL in-place upgrades at this [blog post](https://red.ht/bobblog).
## Release notes
See the [changelog](https://github.com/redhat-cop/infra.lvm_snapshots/tree/main/CHANGELOG.rst).
## Licensing
MIT
See [LICENSE](LICENSE) to see the full text.

View File

@ -0,0 +1,13 @@
# Bigboot Stickers
I presented a lightning talk at Red Hat Summit 2024 giving an overview and demo of the [`bigboot`](./roles/bigboot/) role. To help drive engagement and add some excitement, I had 100 of these stickers printed up. The talk was well received and all the stickers were given away.
![People grabbing stickers after my talk](images/bobtalk.jpg)
Since then, lots of folks asked me if there are more stickers. Well, I recently discovered how to make them available for folks to order at Sticker Mule. You can get 10 stickers for 9.99 USD. These are nice die cut stickers measuring 2.29" x 3" (58mm x 76mm). They are available with [plain white vinyl](https://www.stickermule.com/swapdisk/item/14744765) the same as the ones I gave away or with this [fancy pants holigraphic background](https://www.stickermule.com/swapdisk/item/17024469) that looks really cool.
![Bigboot stickers in white and holographic](images/bigboot-stickers.png)
If you have the means to print your own stickers or just want the source artwork, download the 1200 dpi image file [here](images/bigboot-sticker-transbg-1200dpi.png).
Add some bling to your laptop lid today and share with your friends. Cheers!

View File

@ -0,0 +1,19 @@
objects:
role: {}
plugins:
become: {}
cache: {}
callback: {}
cliconf: {}
connection: {}
filter: {}
httpapi: {}
inventory: {}
lookup: {}
module: {}
netconf: {}
shell: {}
strategy: {}
test: {}
vars: {}
version: 2.1.1

View File

@ -0,0 +1,150 @@
ancestor: null
releases:
1.0.0:
release_date: '2023-08-03'
1.0.1:
changes:
major_changes:
- Initial MVP release
minor_changes:
- Add boot backup support
- Add support for checking before resizing logical volumes
fragments:
- boot_backup.yml
- check_before_resize.yml
- release.yml
release_date: '2023-08-04'
1.0.2:
changes:
bugfixes:
- Existing Snapshots with Different Name Cause verify_no_existing_snapshot.yml
to Fail
minor_changes:
- Create snapshots with normalized sizes
fragments:
- create_snapshots_with_normalized_sizes.yml
- filtering_by_lvname_on_existing_snapshot_check.yml
release_date: '2023-08-31'
1.0.3:
changes:
bugfixes:
- Add task to ensure tar package is present
- Grub needs reinstall if /boot is on LVM
- Wrong kernel version booting after rolling back
minor_changes:
- Changed the lvm_snapshots_boot_backup var default to false
- Removed unimplemented lvm_snapshots_use_boom var from the docs
- Revert - wait for snapshot to drain before returning
fragments:
- fix-lvm-grub.yml
- minor-var-changes.yml
- revert-wait-for-drain.yml
- tar-present.yml
- wrong-kernel.yml
release_date: '2023-11-29'
1.1.0:
changes:
major_changes:
- New role, bigboot, to increase the boot partition while moving, and shrinking
if needed, the adjacent partition
- New role, initramfs, to execute an atomic flow of building and using a temporary
initramfs in a reboot and restoring the original one
- New role, shrink_lv, to decrease logical volume size along with the filesystem
fragments:
- add-bigboot-role.yml
- add-shrink-lv.yml
release_date: '2023-11-30'
1.1.1:
changes:
bugfixes:
- Fix "Failed to list block device properties" error
- Fix dracut path
fragments:
- fix_entries.yml
- no_sbin_dracut.yml
release_date: '2023-12-05'
1.1.2:
changes:
minor_changes:
- Updated links in docs and workflows to reflect move to redhat-cop org
fragments:
- update_links.yml
release_date: '2023-12-13'
2.0.0:
changes:
breaking_changes:
- Split lvm_snapshots role into snapshot_create, snapshot_revert and snapshot_remove
minor_changes:
- bigboot - Rename internal variables with role name prefix
- initramfs - Rename internal variables with role name prefix
- shrink_lv - Rename internal variables with role name prefix
fragments:
- bigboot-internal-variable-names.yml
- initramfs-internal-variable-names.yml
- shrinklv-internal-variable-names.yml
- split-lvm-snapshot_role.yml
release_date: '2024-01-10'
2.0.1:
changes:
bugfixes:
- Fix release workflow prechecks
minor_changes:
- Add publish to Automation Hub to release workflow
fragments:
- automation_hub_release.yml
release_date: '2024-01-11'
2.0.2:
changes:
bugfixes:
- Clean up bad math in bigboot.sh
- Fix bigboot device not found error
- Fix bigboot fail when autoactivation property not set
- Fix vgs not found error
- Round down requested size to multiple of extent size
- Shorten bigboot.sh usage help message to not exceed the kmsg buffer
- Use sectors with sfdisk
minor_changes:
- Add bigboot progress messages so inpatient operators don't think their server
is hung
fragments:
- autoactivate.yml
- fix_maths.yml
- nvme_fix.yml
release_date: '2024-03-07'
2.0.3:
changes:
bugfixes:
- Fix how locking is disabled for newer LVM versions
- Fix missing role metadata
- Fix potential space exhaustion when restoring previous initramfs
fragments:
- autoactivate.yml
- fix_image_copy.yml
- fix_lvm_config.yml
release_date: '2024-04-25'
2.1.0:
changes:
major_changes:
- add bigboot support for Btrfs next partition
minor_changes:
- do bigboot LVM changes with Ansible instead of pre-mount hook
- new bigboot_partition_size variable to make bigboot role more idempotent
- show console log output from bigboot even if quiet kernel arg is set
fragments:
- btrfs_bigboot.yml
- more_idempotent.yml
release_date: '2024-07-15'
2.1.1:
changes:
bugfixes:
- Fix bigboot repeatedly increasing the partition size
minor_changes:
- Improve documentation and example usage of initramfs role
- Improved console logging of bigboot progress to include percent complete
- Support check mode when using the bigboot role
fragments:
- 78-improve-bigboot-check-mode.yml
- bigboot_progress_meter.yml
- fix_80.yml
- initramfs_docs.yml
release_date: '2024-12-10'

View File

@ -0,0 +1,32 @@
changelog_filename_template: ../CHANGELOG.rst
changelog_filename_version_depth: 0
changes_file: changelog.yaml
changes_format: combined
ignore_other_fragment_extensions: true
keep_fragments: false
mention_ancestor: true
new_plugins_after_name: removed_features
notesdir: fragments
prelude_section_name: release_summary
prelude_section_title: Release Summary
sanitize_changelog: true
sections:
- - major_changes
- Major Changes
- - minor_changes
- Minor Changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- Deprecated Features
- - removed_features
- Removed Features (previously deprecated)
- - security_fixes
- Security Fixes
- - bugfixes
- Bugfixes
- - known_issues
- Known Issues
title: LVM Snapshot Linux Role
trivial_section_name: trivial
use_fqcn: true

View File

@ -0,0 +1,2 @@
minor_changes:
- Prevent snapshot creation when newest installed kernel is not in use

View File

@ -0,0 +1,3 @@
minor_changes:
- Updates to support hosts with bind/overlay mounts attached
to the device intended to be operated on.

View File

@ -0,0 +1,31 @@
---
namespace: infra
name: lvm_snapshots
version: 2.1.1
readme: README.md
authors:
- Ygal Blum <yblum@redhat.com>
- Bob Mader <bob@redhat.com>
description: Ansible role for creating and rolling back LVM snapshots
license_file: LICENSE
tags:
- ansible
- lvm
- storage
dependencies:
"community.general": "*"
repository: https://github.com/redhat-cop/infra.lvm_snapshots
documentation: https://github.com/redhat-cop/infra.lvm_snapshots
homepage: https://github.com/redhat-cop/infra.lvm_snapshots
issues: https://github.com/redhat-cop/infra.lvm_snapshots/issues
build_ignore:
- .ansible-lint
- .pylintrc
- .yamllint
- .git
- .gitignore
- ansible.cfg
- requirements.yml
- tests
- vendor
...

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

View File

@ -0,0 +1,2 @@
---
requires_ansible: '>=2.15.0'

View File

@ -0,0 +1,4 @@
---
collections:
- name: community.general
...

View File

@ -0,0 +1,87 @@
# bigboot
The `bigboot` role is used to increase boot partition.
The role is designed to support the automation of RHEL in-place upgrades, but can also be used for other purposes.
## Contents
The role configures a dracut pre-mount hook that executes during a reboot to increase the size of the boot partition and filesystem. To make room for the boot size increase, the role first shrinks the size of the next partition after the boot partition. This next partition must contain either an LVM physical volume or a Btrfs filesystem volume. There must be sufficient free space in the LVM volume group or Btrfs filesystem to accommodate the reduced size.
> **WARNING!**
>
> All blocks of the partition above the boot partition are copied using `sfdisk` during the reboot and this can take several minutes or more depending on the size of that partition. The bigboot script periodically outputs progress messages to the system console to make it clear that the system is not in a "hung" state, but these progress messages may not be seen if `rhgb` or `quiet` kernel arguments are set. If the system is reset while the blocks are being copied, the partition will be irrecoverably corrupted. Do not assume the system is hung or force a reset during the bigboot reboot!
To learn more about how bigboot works, check out this [video](https://people.redhat.com/bmader/bigboot-demo.mp4).
Get bigboot stickers for your laptop [here](../../STICKERS.md).
## Role Variables
### `bigboot_partition_size` (String)
The variable `bigboot_partition_size` specifies the minimum required size of the boot partition. If the boot partition is already equal to or greater than the given size, the role will end gracefully making no changes. The value can be either in bytes or with optional single letter suffix (1024 bases) using [human_to_bytes](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/human_to_bytes_filter.html) filter plugin.
### `bigboot_size` (String)
This variable is deprecated and will be removed in a future release. Use `bigboot_partition_size` instead.
The variable `bigboot_size` specifies by how much the size of the boot partition is to be increased. The value can be either in bytes or with optional single letter suffix (1024 bases) using [human_to_bytes](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/human_to_bytes_filter.html) filter plugin.
> **Note**
>
> The size increase may be slightly less than the specified value as the role will round down to the nearest multiple of the LVM volume group extent size or Btrfs sector size used for the next partition after the boot partition.
## Example playbook
The following yaml demonstrates an example playbook that runs the role to increase the size of the target hosts boot partition to 1.5G:
```yaml
- name: Extend boot partition playbook
hosts: all
vars:
bigboot_partition_size: 1.5G
roles:
- bigboot
```
# Validate execution
The "Validate boot filesystem new size" task at the end of the run will indicate success or failure of the boot partition size increase. For example:
```
TASK [bigboot : Validate boot filesystem new size] ****************************************
ok: [fedora] => {
"changed": false,
"msg": "Boot filesystem size is now 1.44 GB (503.46 MB increase)"
```
If the boot partition was already equal to or greater than the given size, the bigboot pre-mount hook configuration is skipped and the host will not reboot. In this case, the run will end with the "Validate increase requested" task indicating nothing happened. For example:
```
TASK [bigboot : Validate increase requested] **********************************************
ok: [fedora] => {
"msg": "Nothing to do! Boot partition already equal to or greater than requested size."
}
```
During the reboot, the bigboot pre-mount hook logs progress messages to the console. After the reboot, `journalctl` can be used to review the log output. For example, a successful run will look similar to this:
```bash
# journalctl --boot --unit=dracut-pre-mount
Jul 02 09:40:12 fedora systemd[1]: Starting dracut-pre-mount.service - dracut pre-mount hook...
Jul 02 09:40:12 fedora dracut-pre-mount[498]: bigboot: Shrinking partition vda3 by 536870912
Jul 02 09:40:12 fedora dracut-pre-mount[498]: bigboot: Moving up partition vda3 by 536870912
Jul 02 09:40:16 fedora dracut-pre-mount[508]: bigboot: Partition move is progressing, please wait! (00:00:01)
Jul 02 09:40:48 fedora dracut-pre-mount[498]: bigboot: Increasing boot partition vda2 by 536870912
Jul 02 09:40:49 fedora dracut-pre-mount[498]: bigboot: Updating kernel partition table
Jul 02 09:40:50 fedora dracut-pre-mount[498]: bigboot: Growing the /boot ext4 filesystem
Jul 02 09:40:50 fedora dracut-pre-mount[528]: e2fsck 1.47.0 (5-Feb-2023)
Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 1: Checking inodes, blocks, and sizes
Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 2: Checking directory structure
Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 3: Checking directory connectivity
Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 4: Checking reference counts
Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 5: Checking group summary information
Jul 02 09:40:50 fedora dracut-pre-mount[528]: /dev/vda2: 38/65536 files (10.5% non-contiguous), 83665/262144 blocks
Jul 02 09:40:50 fedora dracut-pre-mount[529]: resize2fs 1.47.0 (5-Feb-2023)
Jul 02 09:40:50 fedora dracut-pre-mount[529]: Resizing the filesystem on /dev/vda2 to 393216 (4k) blocks.
Jul 02 09:40:50 fedora dracut-pre-mount[529]: The filesystem on /dev/vda2 is now 393216 (4k) blocks long.
Jul 02 09:40:50 fedora dracut-pre-mount[493]: Boot partition vda2 successfully increased by 536870912 (38 seconds)
```

View File

@ -0,0 +1,2 @@
bigboot_partition_size:
bigboot_size:

View File

@ -0,0 +1,129 @@
#!/bin/bash
#
# This is the new bigboot reboot script. Unlike the old script, this one
# only deals with the partitioning and boot filesystem changes required.
# The preparations to reduce the LVM physical volume or Btrfs filesystem
# volume are now done in advance by Ansible before rebooting.
#
# This script performs the following steps in this order:
#
# 1. Move the end of the next partition to make it smaller
# 2. Use sfdisk to copy the blocks of the next partition
# 3. Move the end of the boot partition making it bigger
# 4. Grow the boot filesystem
#
# Usage: bigboot.sh boot_partition_name next_partition_name boot_size_increase_in_bytes
#
# For example, this command would increase a /boot filesystem on /dev/sda1 by 500M:
#
# bigboot.sh sda1 sda2 524288000
#
# Get input values
boot_part_name="$1"
next_part_name="$2"
boot_size_increase_in_bytes="$3"
# Validate inputs
name="bigboot"
if [[ ! -b "/dev/$boot_part_name" ]]; then
echo "$name: Boot partition is not a block device: $boot_part_name"
exit 1
fi
if [[ ! -b "/dev/$next_part_name" ]]; then
echo "$name: Next partition is not a block device: $next_part_name"
exit 1
fi
if [[ ! $boot_size_increase_in_bytes -gt 0 ]]; then
echo "$name: Invalid size increase value: $boot_size_increase_in_bytes"
exit 1
fi
# Calculate device and partition details
boot_disk_device=/dev/"$(/usr/bin/basename "$(readlink -f /sys/class/block/"$boot_part_name"/..)")"
boot_part_num="$(</sys/class/block/"$boot_part_name"/partition)"
next_part_num="$(</sys/class/block/"$next_part_name"/partition)"
next_part_start="$(($(</sys/class/block/"$next_part_name"/start)*512))"
next_part_size="$(($(</sys/class/block/"$next_part_name"/size)*512))"
next_part_end="$((next_part_start+next_part_size-1))"
next_part_new_end="$((next_part_end-boot_size_increase_in_bytes))"
# Validate boot filesystem
eval "$(/usr/sbin/blkid /dev/"$boot_part_name" -o udev)"
boot_fs_type="$ID_FS_TYPE"
if [[ ! "$boot_fs_type" =~ ^ext[2-4]$|^xfs$ ]]; then
echo "$name: Boot filesystem type is not extendable: $boot_fs_type"
exit 1
fi
# Validate next partition
eval "$(/usr/sbin/blkid /dev/"$next_part_name" -o udev)"
if [[ "$ID_FS_TYPE" == "LVM2_member" ]]; then
eval "$(/usr/sbin/lvm pvs --noheadings --nameprefixes -o vg_name /dev/"$next_part_name")"
next_part_vg="$LVM2_VG_NAME"
fi
# Shrink next partition
echo "$name: Shrinking partition $next_part_name by $boot_size_increase_in_bytes"
if ! ret=$(echo Yes | /usr/sbin/parted "$boot_disk_device" ---pretend-input-tty unit B resizepart "$next_part_num" "$next_part_new_end" 2>&1); then
echo "$name: Failed shrinking partition $next_part_name: $ret"
exit 1
fi
# Output progress messages to help impatient operators recognize the server is not "hung"
( sleep 9
while pid="$(ps -C sfdisk -o pid:1=)"; do
pct='??'
for fd in /proc/"$pid"/fd/*; do
if [[ "$(readlink "$fd")" == "$boot_disk_device" ]]; then
offset="$(awk '/pos:/ {print $2}' /proc/"$pid"/fdinfo/"${fd##*/}")"
pct="$((-100*offset/next_part_size+100))"
break
fi
done
echo "$name: Partition move is progressing, please wait! ($pct% complete)"
sleep 20
done ) &
# Shift next partition
echo "$name: Moving up partition $next_part_name by $boot_size_increase_in_bytes"
if ! ret=$(echo "+$((boot_size_increase_in_bytes/512))," | /usr/sbin/sfdisk --move-data "$boot_disk_device" -N "$next_part_num" --force 2>&1); then
echo "$name: Failed moving up partition $next_part_name: $ret"
exit 1
fi
# Increase boot partition
echo "$name: Increasing boot partition $boot_part_name by $boot_size_increase_in_bytes"
if ! ret=$(echo "- +" | /usr/sbin/sfdisk "$boot_disk_device" -N "$boot_part_num" --no-reread --force 2>&1); then
echo "$name: Failed increasing boot partition $boot_part_name: $ret"
exit 1
fi
# Update kernel partition table
echo "$name: Updating kernel partition table"
[[ "$next_part_vg" ]] && /usr/sbin/lvm vgchange -an "$next_part_vg" && sleep 1
/usr/sbin/partprobe "$boot_disk_device" && sleep 1
[[ "$next_part_vg" ]] && /usr/sbin/lvm vgchange -ay "$next_part_vg" && sleep 1
# Grow the /boot filesystem
echo "$name: Growing the /boot $boot_fs_type filesystem"
if [[ "$boot_fs_type" =~ ^ext[2-4]$ ]]; then
/usr/sbin/e2fsck -fy "/dev/$boot_part_name"
if ! /usr/sbin/resize2fs "/dev/$boot_part_name"; then
echo "$name: resize2fs error while growing the /boot filesystem"
exit 1
fi
fi
if [[ "$boot_fs_type" == "xfs" ]]; then
tmp_dir=$(/usr/bin/mktemp -d)
/usr/bin/mount -t xfs "/dev/$boot_part_name" "$tmp_dir"
/usr/sbin/xfs_growfs "/dev/$boot_part_name"
status=$?
/usr/bin/umount "/dev/$boot_part_name"
if [[ $status -ne 0 ]]; then
echo "$name: xfs_growfs error while growing the /boot filesystem"
exit 1
fi
fi
exit 0

View File

@ -0,0 +1,15 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
check(){
return 0
}
install() {
inst_multiple -o /usr/bin/mount /usr/bin/umount /usr/sbin/parted /usr/bin/mktemp /usr/bin/date /usr/bin/basename /usr/sbin/resize2fs /usr/sbin/partprobe /usr/sbin/lvm /usr/sbin/blkid /usr/sbin/e2fsck /usr/sbin/xfs_growfs /usr/sbin/xfs_db
# shellcheck disable=SC2154
inst_hook pre-mount 99 "$moddir/increase-boot-partition.sh"
inst_binary "$moddir/sfdisk.static" "/usr/sbin/sfdisk"
inst_simple "$moddir/bigboot.sh" "/usr/bin/bigboot.sh"
}

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Increase the size of the boot partition
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,52 @@
- name: Copy dracut pre-mount hook files
ansible.builtin.copy:
src: "{{ item }}"
dest: /usr/lib/dracut/modules.d/99extend_boot/
mode: "0554"
loop:
- bigboot.sh
- module-setup.sh
- sfdisk.static
- name: Resolve and copy pre-mount hook wrapper script
ansible.builtin.template:
src: increase-boot-partition.sh.j2
dest: /usr/lib/dracut/modules.d/99extend_boot/increase-boot-partition.sh
mode: '0554'
- name: Configure hook removal reboot cron
ansible.builtin.cron:
name: bigboot hook removal
cron_file: bigboot_hook_removal
user: root
special_time: reboot
job: '(rm -rf /usr/lib/dracut/modules.d/99extend_boot; rm -f /etc/cron.d/bigboot_hook_removal) > /dev/null 2>&1'
- name: Create the initramfs and reboot to run the module
vars:
initramfs_add_modules: "extend_boot"
ansible.builtin.include_role:
name: initramfs
- name: Retrieve mount points
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- mounts
- name: Capture boot filesystem new size
ansible.builtin.set_fact:
bigboot_boot_fs_new_size: "{{ (ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first).size_total | int }}"
- name: Validate boot filesystem new size
ansible.builtin.assert:
that:
- bigboot_boot_fs_new_size != bigboot_boot_fs_original_size
fail_msg: >-
Boot filesystem size '{{ bigboot_boot_fs_new_size }}' did not change
success_msg: >-
Boot filesystem size is now
{{ bigboot_boot_fs_new_size | int | human_readable }}
({{ (bigboot_boot_fs_new_size | int - bigboot_boot_fs_original_size | int) | human_readable }} increase)
when: not ansible_check_mode

View File

@ -0,0 +1,57 @@
- name: Find the boot mount entry
ansible.builtin.set_fact:
bigboot_boot_mount_entry: "{{ ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first | default('', true) }}"
- name: Validate boot mount entry
ansible.builtin.assert:
that:
- bigboot_boot_mount_entry.device is defined
fail_msg: "No /boot mount point found."
- name: Calculate the partition to look for
ansible.builtin.set_fact:
bigboot_boot_partition_name: "{{ (bigboot_boot_mount_entry.device | split('/'))[-1] }}"
- name: Find the boot device parent
ansible.builtin.set_fact:
bigboot_boot_disk: "{{ item.key }}"
with_dict: "{{ ansible_facts.devices }}"
when: bigboot_boot_partition_name in item.value.partitions
- name: Capture boot device details
ansible.builtin.set_fact:
bigboot_boot_device_name: "/dev/{{ bigboot_boot_disk }}"
bigboot_boot_fs_original_size: "{{ bigboot_boot_mount_entry.size_total | int }}"
bigboot_boot_device_sectors: "{{ ansible_facts.devices[bigboot_boot_disk].partitions[bigboot_boot_partition_name].sectors | int }}"
bigboot_boot_device_sectorsize: "{{ ansible_facts.devices[bigboot_boot_disk].partitions[bigboot_boot_partition_name].sectorsize | int }}"
- name: Calculate boot device current size
ansible.builtin.set_fact:
bigboot_boot_device_bytes: "{{ bigboot_boot_device_sectors | int * bigboot_boot_device_sectorsize | int }}"
- name: Find the next partition
ansible.builtin.set_fact:
bigboot_next_partition_name: "{{ ansible_loop.nextitem.0 | default(omit, true) }}"
when: item.0 == bigboot_boot_partition_name
loop: "{{ ansible_facts.devices[bigboot_boot_disk].partitions | dictsort }}"
loop_control:
extended: true
- name: Validate next partition exists
ansible.builtin.assert:
that:
- bigboot_next_partition_name is defined
fail_msg: "There is no partition found after the /boot partition."
- name: Find Btrfs or LVM
ansible.builtin.set_fact:
bigboot_next_partition_btrfs: "{{ ansible_facts.mounts | selectattr('device', 'equalto', '/dev/' + bigboot_next_partition_name) |
selectattr('fstype', 'equalto', 'btrfs') | map(attribute='mount') | first | default(omit, true) }}"
bigboot_next_partition_vg: "{{ ansible_facts.lvm.pvs['/dev/' + bigboot_next_partition_name].vg | default(omit, true) }}"
bigboot_next_partition_type_checked: true
- name: Validate next partition type
ansible.builtin.assert:
that:
- bigboot_next_partition_btrfs is defined or bigboot_next_partition_vg is defined
fail_msg: "The partition after the /boot partition is neither LVM or Btrfs."

View File

@ -0,0 +1,58 @@
---
- name: Make sure the required related facts are available
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- mounts
- devices
- name: Validate initramfs preflight
ansible.builtin.include_role:
name: initramfs
tasks_from: preflight
- name: Get boot device info
ansible.builtin.include_tasks:
file: get_boot_device_info.yml
- name: Convert bigboot_partition_size to bytes
ansible.builtin.set_fact:
bigboot_partition_size_bytes: "{{ bigboot_partition_size | ansible.builtin.human_to_bytes }}"
when: bigboot_partition_size | default('', true) | length > 0
- name: Convert bigboot_size to bytes
ansible.builtin.set_fact:
bigboot_size_bytes: "{{ bigboot_size | ansible.builtin.human_to_bytes }}"
when: bigboot_partition_size_bytes is undefined and bigboot_size | default('', true) | length > 0
- name: Calculate bigboot increase
ansible.builtin.set_fact:
bigboot_increase_bytes: "{{ bigboot_partition_size_bytes | default(bigboot_boot_device_bytes, true) | int -
bigboot_boot_device_bytes | int +
bigboot_size_bytes | default('0', true) | int }}"
- name: Prepare Btrfs for bigboot
ansible.builtin.include_tasks:
file: prep_btrfs.yml
when:
- bigboot_increase_bytes | int > 0
- bigboot_next_partition_btrfs is defined
- name: Prepare LVM for bigboot
ansible.builtin.include_tasks:
file: prep_lvm.yml
when:
- bigboot_increase_bytes | int > 0
- bigboot_next_partition_vg is defined
- name: Configure pre-mount hook and reboot
ansible.builtin.include_tasks:
file: do_bigboot_reboot.yml
when:
- bigboot_increase_bytes | int > 0
- name: Validate increase requested
ansible.builtin.debug:
msg: "Nothing to do! Boot partition already equal to or greater than requested size."
when: bigboot_increase_bytes | int <= 0

View File

@ -0,0 +1,19 @@
- name: Find Btrfs sector size
ansible.builtin.slurp:
src: "/sys/fs/btrfs/{{ ansible_facts.mounts | selectattr('mount', 'equalto', bigboot_next_partition_btrfs) | map(attribute='uuid') | first }}/sectorsize"
register: sectorsize
- name: Align bigboot increase to sector size
ansible.builtin.set_fact:
bigboot_increase_bytes: "{{ bigboot_increase_bytes | int - (bigboot_increase_bytes | int % sectorsize.content | b64decode | int) }}"
- name: Btrfs volume reduce
ansible.builtin.command:
cmd: >-
/usr/sbin/btrfs
filesystem resize
1:-{{ bigboot_increase_bytes }}
{{ bigboot_next_partition_btrfs }}
when: bigboot_increase_bytes | int > 0
changed_when: true
register: resize_cmd

View File

@ -0,0 +1,55 @@
- name: Find physical volume size
ansible.builtin.command:
cmd: >-
/usr/sbin/lvm pvs
--noheadings --nosuffix --units b
-o pv_size /dev/{{ bigboot_next_partition_name }}
changed_when: false
register: pv_size
- name: Find volume group extent size
ansible.builtin.command:
cmd: >
/usr/sbin/lvm vgs
--noheadings --nosuffix --units b
-o vg_extent_size {{ bigboot_next_partition_vg }}
changed_when: false
register: vg_extent_size
check_mode: false
- name: Align bigboot increase to extent size
ansible.builtin.set_fact:
bigboot_increase_bytes: "{{ bigboot_increase_bytes | int - (bigboot_increase_bytes | int % vg_extent_size.stdout | int) }}"
- name: Test mode pvresize
ansible.builtin.command:
cmd: >-
/usr/sbin/lvm pvresize
--test --yes
--setphysicalvolumesize {{ pv_size.stdout | int - bigboot_increase_bytes | int }}B
/dev/{{ bigboot_next_partition_name }}
when: bigboot_increase_bytes | int > 0
changed_when: false
failed_when: pvresize_test.rc not in [0, 5]
register: pvresize_test
- name: Evict extents from end of physical volume
ansible.builtin.command:
cmd: >-
/usr/sbin/lvm pvmove
--alloc anywhere
/dev/{{ bigboot_next_partition_name }}:{{ (((pv_size.stdout | int - bigboot_increase_bytes | int) / vg_extent_size.stdout | int) - 1) | int }}-
when: pvresize_test.rc | default(0, true) == 5
changed_when: true
register: pvmove
- name: Real pvresize
ansible.builtin.command:
cmd: >-
/usr/sbin/lvm pvresize
--yes
--setphysicalvolumesize {{ pv_size.stdout | int - bigboot_increase_bytes | int }}B
/dev/{{ bigboot_next_partition_name }}
when: bigboot_increase_bytes | int > 0
changed_when: true
register: pvresize_real

View File

@ -0,0 +1,17 @@
#!/bin/bash
main() {
start=$(/usr/bin/date +%s)
# run bigboot.sh to increase boot partition and file system size
sh /usr/bin/bigboot.sh "{{ bigboot_boot_partition_name }}" "{{ bigboot_next_partition_name }}" "{{ bigboot_increase_bytes }}"
status=$?
end=$(/usr/bin/date +%s)
# write the log file
if [[ $status -eq 0 ]]; then
echo "Boot partition {{ bigboot_boot_partition_name }} successfully increased by {{ bigboot_increase_bytes }} ("$((end-start))" seconds)"
else
echo "Failed to extend boot partition ("$((end-start))" seconds)"
fi
}
main "$0" >&2

View File

@ -0,0 +1,91 @@
# initramfs
The `initramfs` role is included by the `shrink_lv` and `bigboot` roles to run an atomic flow of building and using a temporary initramfs in a reboot and restoring the original one.
The role is designed to be internal for this collection and support the automation of RHEL in-place upgrades, but can also be used for other purposes.
## Contents
To allow fast fail, the role provides a [`preflight.yml`](./tasks/preflight.yml) tasks file that should be included early in the play that ultimately includes the [`main`](./tasks/main.yml) role that actually reboots the host. Refer the usage section below for example.
## Role Variables
All variables are optional
### `initramfs_add_modules`
`initramfs_add_modules` is a space-separated list of dracut modules to be added to the default set of modules.
See [`dracut --add`](https://man7.org/linux/man-pages/man8/dracut.8.html) option for details.
### `initramfs_backup_extension`
`initramfs_backup_extension` is the file extension for the backup initramfs file.
Defaults to `old`
### `initramfs_post_reboot_delay`
`initramfs_post_reboot_delay` sets the amount of Seconds to wait after the reboot command was successful before attempting to validate the system rebooted successfully.
The value is used for [`post_reboot_delay`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/reboot_module.html#parameter-post_reboot_delay) parameter
Defaults to `30`
### `initramfs_reboot_timeout`
`initramfs_reboot_timeout` sets the maximum seconds to wait for machine to reboot and respond to a test command.
The value is used for [`reboot_timeout`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/reboot_module.html#parameter-reboot_timeout) parameter
Defaults to `7200`
## Example role usage
We will refer to the `bigboot` role of this collection to explain how the `initramfs` role can be used. Let's look at the `tasks/main.yaml` of the `bigboot` role. After the required facts have been gathered, the [Validate initramfs preflight](https://github.com/redhat-cop/infra.lvm_snapshots/blob/2.1.0/roles/bigboot/tasks/main.yaml#L10-L13) task includes the `initramfs` role preflight tasks:
```yaml
- name: Validate initramfs preflight
ansible.builtin.include_role:
name: initramfs
tasks_from: preflight
```
If this is successful, the `bigboot` role continues to perform additional tasks and checks specific to its function. With that done, it moves on to `tasks/do_bigboot_reboot.yml` which [configures a dracut pre-mount hook](https://github.com/redhat-cop/infra.lvm_snapshots/blob/2.1.0/roles/bigboot/tasks/do_bigboot_reboot.yml#L1-L15) to prepare for the customized initramfs reboot:
```yaml
- name: Copy dracut pre-mount hook files
ansible.builtin.copy:
src: "{{ item }}"
dest: /usr/lib/dracut/modules.d/99extend_boot/
mode: "0554"
loop:
- bigboot.sh
- module-setup.sh
- sfdisk.static
- name: Resolve and copy pre-mount hook wrapper script
ansible.builtin.template:
src: increase-boot-partition.sh.j2
dest: /usr/lib/dracut/modules.d/99extend_boot/increase-boot-partition.sh
mode: '0554'
```
After that, it [includes](https://github.com/redhat-cop/infra.lvm_snapshots/blob/2.1.0/roles/bigboot/tasks/do_bigboot_reboot.yml#L17-L21) the main `initramfs` role which will create a custom initramfs built with the dracut hook configured above, reboot the host to run the hook, and lastly, restore the original initramfs after the reboot:
```yaml
- name: Create the initramfs and reboot to run the module
vars:
initramfs_add_modules: "extend_boot"
ansible.builtin.include_role:
name: initramfs
```
Also, note that while the `initramfs` role handles restoring the original initramfs, it is up to the including play to clean up the dracut hook files it configured. We see this with the [Remove dracut extend boot module](https://github.com/redhat-cop/infra.lvm_snapshots/blob/2.1.0/roles/bigboot/tasks/do_bigboot_reboot.yml#L23-L26) task that immediately follows the task including the `initramfs` role:
```yaml
- name: Remove dracut extend boot module
ansible.builtin.file:
path: /usr/lib/dracut/modules.d/99extend_boot
state: absent
```
The `shrink_lv` role of this collection is another [example](https://github.com/redhat-cop/infra.lvm_snapshots/blob/2.1.0/roles/shrink_lv/tasks/main.yaml#L13-L37) of using the `initramfs` role that you may study.

View File

@ -0,0 +1,4 @@
initramfs_backup_extension: old
initramfs_add_modules: ""
initramfs_post_reboot_delay: 30
initramfs_reboot_timeout: 7200

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Included by other roles to run an atomic flow of building and using a temporary initramfs in a reboot and restoring the original one
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,36 @@
---
- name: Make sure the required related facts are available
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- kernel
- name: Get kernel version
ansible.builtin.set_fact:
initramfs_kernel_version: "{{ ansible_facts.kernel }}"
- name: Create a backup of the current initramfs
ansible.builtin.copy:
remote_src: true
src: /boot/initramfs-{{ initramfs_kernel_version }}.img
dest: /root/initramfs-{{ initramfs_kernel_version }}.img.{{ initramfs_backup_extension }}
mode: "0600"
- name: Create a new initramfs with the optional additional modules
# yamllint disable-line rule:line-length
ansible.builtin.command: '/usr/bin/dracut {{ ((initramfs_add_modules | length) > 0) | ternary("-a", "") }} "{{ initramfs_add_modules }}" --kver {{ initramfs_kernel_version }} --force'
changed_when: true
- name: Configure initramfs restore reboot cron
ansible.builtin.cron:
name: initramfs restore
cron_file: initramfs_restore
user: root
special_time: reboot
# yamllint disable-line rule:line-length
job: '(mv -f /root/initramfs-{{ initramfs_kernel_version }}.img.{{ initramfs_backup_extension }} /boot/initramfs-{{ initramfs_kernel_version }}.img; rm -f /etc/cron.d/initramfs_restore) > /dev/null 2>&1'
- name: Reboot the server
ansible.builtin.import_role:
name: verified_reboot

View File

@ -0,0 +1,28 @@
---
- name: Make sure the required related facts are available
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- kernel
- name: Get kernel version
ansible.builtin.set_fact:
initramfs_kernel_version: "{{ ansible_facts.kernel }}"
- name: Get default kernel
ansible.builtin.command:
cmd: /sbin/grubby --default-kernel
register: initramfs_grubby_rc
changed_when: false
check_mode: false
- name: Parse default kernel version
ansible.builtin.set_fact:
initramfs_default_kernel: "{{ ((((initramfs_grubby_rc.stdout_lines[0] | split('/'))[2] | split('-'))[1:]) | join('-')) | trim }}"
- name: Check the values
ansible.builtin.assert:
that: initramfs_default_kernel == initramfs_kernel_version
fail_msg: "Current kernel version '{{ initramfs_kernel_version }}' is not the default version '{{ initramfs_default_kernel }}'"
success_msg: "Current kernel version {{ initramfs_kernel_version }} and default version {{ initramfs_default_kernel }} match"

View File

@ -0,0 +1,55 @@
# shrink_lv
The `shrink_lv` role is used to decrease the size of logical volumes and the file system within them.
The role is designed to support the automation of RHEL in-place upgrades, but can also be used for other purposes.
## Contents
The role contains the shell scripts to shrink the logical volume and file system, as well as the script wrapping it to run as part of the pre-mount step during the boot process.
## Role Variables
### `shrink_lv_devices`
The variable `shrink_lv_devices` is the list of logical volumes to shrink and the target size for those volumes.
#### `device`
The device that is mounted as listed under `/proc/mount`.
If the same device has multiple paths, e.g. `/dev/vg/lv` and `/dev/mapper/vg/lv` pass the path that is mounted
#### `size`
The target size of the logical volume and filesystem after the role has completed.
The value can be either in bytes or with optional single letter suffix (1024 bases).
See `Unit options` type `iec` of [`numfmt`](https://man7.org/linux/man-pages/man1/numfmt.1.html)
## Example of a playbook to run the role
The following yaml is an example of a playbook that runs the role against all hosts to shrink the logical volume `lv` in volume group `vg` to 4G.
```yaml
- name: Shrink Logical Volumes playbook
hosts: all
vars:
shrink_lv_devices:
- device: /dev/vg/lv
size: 4G
roles:
- shrink_lv
```
# Validate execution
The script will add an entry to the kernel messages (`/dev/kmsg` or `/var/log/messages`) with success or failure.
In case of failure, it may also include an error message retrieved from the execution of the script.
A successful execution will look similar to this:
```bash
[root@localhost ~]# cat /var/log/messages |grep Resizing -A 2 -B 2
Oct 16 17:55:00 localhost /dev/mapper/rhel-root: 29715/2686976 files (0.2% non-contiguous), 534773/10743808 blocks
Oct 16 17:55:00 localhost dracut-pre-mount: resize2fs 1.42.9 (28-Dec-2013)
Oct 16 17:55:00 localhost journal: Resizing the filesystem on /dev/mapper/rhel-root to 9699328 (4k) blocks.#012The filesystem on /dev/mapper/rhel-root is now 9699328 blocks long.
Oct 16 17:55:00 localhost journal: Size of logical volume rhel/root changed from 40.98 GiB (10492 extents) to 37.00 GiB (9472 extents).
Oct 16 17:55:00 localhost journal: Logical volume rhel/root successfully resized.
```

View File

@ -0,0 +1,14 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
check(){
return 0
}
install() {
inst_multiple -o /usr/bin/numfmt /usr/bin/findmnt /usr/bin/lsblk /usr/sbin/lvm /usr/bin/awk /usr/bin/sed /usr/bin/sort /usr/bin/mktemp /usr/bin/date /usr/bin/head /usr/sbin/blockdev /usr/sbin/tune2fs /usr/sbin/resize2fs /usr/bin/cut /usr/sbin/fsadm /usr/sbin/fsck.ext4 /usr/libexec/lvresize_fs_helper /usr/sbin/cryptsetup /usr/bin/logger /usr/bin/basename /usr/bin/getopt
# shellcheck disable=SC2154
inst_hook pre-mount 99 "$moddir/shrink-start.sh"
inst_simple "$moddir/shrink.sh" "/usr/bin/shrink.sh"
}

View File

@ -0,0 +1,253 @@
#!/bin/bash
VOLUME_SIZE_ALIGNMENT=4096
function get_device_name() {
if [[ "$1" == "UUID="* ]]; then
dev_name=$( parse_uuid "$1" )
else
dev_name=$(/usr/bin/cut -d " " -f 1 <<< "$1")
fi
status=$?
if [[ status -ne 0 ]]; then
return $status
fi
echo "$dev_name"
return $status
}
function ensure_size_in_bytes() {
local expected_size
expected_size=$(/usr/bin/numfmt --from iec "$1")
(( expected_size=(expected_size+VOLUME_SIZE_ALIGNMENT)/VOLUME_SIZE_ALIGNMENT*VOLUME_SIZE_ALIGNMENT ))
echo $expected_size
}
function is_device_mounted() {
/usr/bin/findmnt --source "$1" 1>&2>/dev/null
status=$?
if [[ status -eq 0 ]]; then
echo "Device $1 is mounted" >&2
return 1
fi
return 0
}
function get_current_volume_size() {
val=$(/usr/bin/lsblk -b "$1" -o SIZE --noheadings)
status=$?
if [[ $status -ne 0 ]]; then
return $status
fi
echo "$val"
return 0
}
function is_lvm(){
val=$( /usr/bin/lsblk "$1" --noheadings -o TYPE 2>&1)
status=$?
if [[ status -ne 0 ]]; then
echo "Failed to list block device properties for $2: $val" >&2
return 1
fi
if [[ "$val" != "lvm" ]]; then
echo "Device $device_name is not of lvm type" >&2
return 1
fi
return 0
}
function parse_uuid() {
uuid=$(/usr/bin/awk '{print $1}'<<< "$1"|/usr/bin/awk -F'UUID=' '{print $2}')
val=$(/usr/bin/lsblk /dev/disk/by-uuid/"$uuid" -o NAME --noheadings 2>/dev/null)
status=$?
if [[ $status -ne 0 ]]; then
echo "Failed to retrieve device name for UUID=$uuid" >&2
return $status
fi
echo "/dev/mapper/$val"
return 0
}
function shrink_volume() {
/usr/sbin/lvm lvreduce "$NOLOCKING" --resizefs -L "$2"b "$1"
return $?
}
function check_volume_size() {
current_size=$(get_current_volume_size "$1")
if [[ $current_size -lt $2 ]];then
echo "Current volume size for device $1 ($current_size bytes) is lower to expected $2 bytes" >&2
return 1
fi
if [[ $current_size -eq $2 ]]; then
echo "Current volume size for device $1 already equals $2 bytes" >&2
return 1
fi
return $?
}
function convert_size_to_fs_blocks(){
local device=$1
local size=$2
block_size_in_bytes=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block size:/{print $3}')
echo $(( size / block_size_in_bytes ))
}
function calculate_expected_resized_file_system_size_in_blocks(){
local device=$1
increment_boot_partition_in_blocks=$(convert_size_to_fs_blocks "$device" "$INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES")
total_block_count=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block count:/{print $3}')
new_fs_size_in_blocks=$(( total_block_count - increment_boot_partition_in_blocks ))
echo $new_fs_size_in_blocks
}
function check_filesystem_size() {
local device=$1
local new_fs_size_in_blocks=$2
new_fs_size_in_blocks=$(calculate_expected_resized_file_system_size_in_blocks "$device")
# it is possible that running this command after resizing it might give an even smaller number.
minimum_blocks_required=$(/usr/sbin/resize2fs -P "$device" 2> /dev/null | /usr/bin/awk '{print $NF}')
if [[ "$new_fs_size_in_blocks" -le "0" ]]; then
echo "Unable to shrink volume: New size is 0 blocks"
return 1
fi
if [[ $minimum_blocks_required -gt $new_fs_size_in_blocks ]]; then
echo "Unable to shrink volume: Estimated minimum size of the file system $1 ($minimum_blocks_required blocks) is greater than the new size $new_fs_size_in_blocks blocks" >&2
return 1
fi
return 0
}
function process_entry() {
is_lvm "$1" "$3"
status=$?
if [[ $status -ne 0 ]]; then
return "$status"
fi
expected_size_in_bytes=$(ensure_size_in_bytes "$2")
check_filesystem_size "$1" "$expected_size_in_bytes"
status=$?
if [[ $status -ne 0 ]]; then
return "$status"
fi
check_volume_size "$1" "$expected_size_in_bytes"
status=$?
if [[ $status -ne 0 ]]; then
return "$status"
fi
is_device_mounted "$1"
status=$?
if [[ $status -ne 0 ]]; then
return "$status"
fi
shrink_volume "$1" "$expected_size_in_bytes"
return $?
}
function display_help() {
echo "Program to shrink ext4 file systems hosted in Logical Volumes.
Usage: '$(basename "$0")' [-h] [-d=|--device=]
Example:
where:
-h show this help text
-d|--device= name or UUID of the device that holds an ext4 and the new size separated by a ':'
for example /dev/my_group/my_vol:2G
Sizes will be rounded to be 4K size aligned"
}
function parse_flags() {
for i in "$@"
do
case $i in
-d=*|--device=*)
entries+=("${i#*=}")
;;
-h)
display_help
exit 0
;;
*)
# unknown option
echo "Unknown flag $i"
display_help
exit 1
;;
esac
done
if [[ ${#entries[@]} == 0 ]]; then
display_help
exit 0
fi
}
function parse_entry() {
IFS=':'
read -ra strarr <<< "$1"
if [[ ${#strarr[@]} != 2 ]]; then
echo "Invalid device entry $1"
display_help
return 1
fi
device="${strarr[0]}"
expected_size="${strarr[1]}"
}
function get_nolocking_opts() {
local lvm_version
lvm_version="$(/usr/sbin/lvm version | /usr/bin/grep 'LVM version:')"
status=$?
if [[ $status -ne 0 ]]; then
echo "Error getting LVM version '$lvm_version'"
exit $status
fi
# true when LVM version is older than 2.03
if echo -e "${lvm_version##*:}\n2.03" | /usr/bin/sed 's/^ *//' | /usr/bin/sort -V -C; then
NOLOCKING='--config=global{locking_type=0}'
else
NOLOCKING='--nolocking'
fi
}
function main() {
local -a entries=()
local run_status=0
parse_flags "$@"
get_nolocking_opts
for entry in "${entries[@]}"
do
local device
local expected_size
parse_entry "$entry"
status=$?
if [[ $status -ne 0 ]]; then
run_status=$status
continue
fi
device_name=$( get_device_name "$device" )
status=$?
if [[ $status -ne 0 ]]; then
run_status=$status
continue
fi
process_entry "$device_name" "$expected_size" "$device"
status=$?
if [[ $status -ne 0 ]]; then
run_status=$status
fi
done
exit $run_status
}
main "$@"

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Decrease logical volume size along with the filesystem
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,20 @@
- name: Get the mount point info
ansible.builtin.set_fact:
shrink_lv_mount_info: "{{ ansible_facts['mounts'] | selectattr('device', 'equalto', item['device']) | first }}"
- name: Assert that the mount point exists
ansible.builtin.assert:
that: shrink_lv_mount_info['device'] is defined
fail_msg: "Mount point {{ item['device'] }} does not exist"
- name: Assert that the filesystem is supported
ansible.builtin.assert:
that: shrink_lv_mount_info['fstype'] in ['ext4']
fail_msg: "Unsupported filesystem '{{ shrink_lv_mount_info['fstype'] }}' on '{{ item['device'] }}'"
- name: Assert that the filesystem has enough free space
ansible.builtin.assert:
that: shrink_lv_mount_info['block_size'] * shrink_lv_mount_info['block_used'] < (item['size'] | ansible.builtin.human_to_bytes)
fail_msg: >
Requested size {{ item['size'] }} is smaller than currently used
{{ (shrink_lv_mount_info['block_size'] * shrink_lv_mount_info['block_used']) | ansible.builtin.human_readable }}

View File

@ -0,0 +1,12 @@
---
- name: Set device for mount
ansible.builtin.set_fact:
shrink_lv_set_device: "{{ ansible_facts['mounts'] | selectattr('device', 'equalto', item['device']) | first }}"
- name: Assert that the filesystem has shrunk
ansible.builtin.assert:
that: (shrink_lv_set_device['size_total'] | int) <= (item['size'] | ansible.builtin.human_to_bytes)
fail_msg: >
Logical Volume {{ item['device'] }} was NOT shrunk as requested.
success_msg: >
Logical Volume {{ item['device'] }} has been shrunk as requested.

View File

@ -0,0 +1,48 @@
---
- name: Make sure the required facts are available
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- kernel
- mounts
- name: Run preflight checks
ansible.builtin.include_tasks: preflight.yaml
- name: Copy shrink LV dracut module
ansible.builtin.copy:
src: "{{ item }}"
dest: /usr/lib/dracut/modules.d/99shrink_lv/
mode: "0554"
loop:
- module-setup.sh
- shrink.sh
- name: Resolve and copy the shrink-start script
ansible.builtin.template:
src: shrink-start.sh.j2
dest: /usr/lib/dracut/modules.d/99shrink_lv/shrink-start.sh
mode: '0554'
- name: Create the initramfs and reboot to run the module
vars:
initramfs_add_modules: "shrink_lv lvm"
ansible.builtin.include_role:
name: initramfs
- name: Remove dracut extend boot module
ansible.builtin.file:
path: /usr/lib/dracut/modules.d/99shrink_lv
state: absent
- name: Retrieve mount points
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- mounts
- name: Check if device has shrunken successfully
ansible.builtin.include_tasks: check_if_shrunk.yml
loop: "{{ shrink_lv_devices }}"

View File

@ -0,0 +1,17 @@
---
- name: Assert shrink_lv_devices
ansible.builtin.assert:
that:
- shrink_lv_devices is defined
- shrink_lv_devices | type_debug == "list"
- shrink_lv_devices | length > 0
fail_msg: shrink_lv_devices must be a list and include at least one element
- name: Validate initramfs preflight
ansible.builtin.include_role:
name: initramfs
tasks_from: preflight
- name: Check all devices
ansible.builtin.include_tasks: check_device.yaml
loop: "{{ shrink_lv_devices }}"

View File

@ -0,0 +1,14 @@
#!/bin/bash
activate_volume_groups(){
for vg in `/usr/sbin/lvm vgs -o name --noheading 2>/dev/null`; do
/usr/sbin/lvm vgchange -ay $vg
done
}
main() {
activate_volume_groups
/usr/bin/shrink.sh {% for device in shrink_lv_devices %}--device={{ device.device }}:{{ device.size }} {% endfor %} 1>&2 >/dev/kmsg
}
main "$0"

View File

@ -0,0 +1,84 @@
# snapshot_create role
The `snapshot_create` 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 `snapshot_create_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
### `snapshot_create_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`
### `snapshot_create_set_name`
The variable `snapshot_create_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:
`<Origin LV name>_<snapshot_create_set_name>`
### `snapshot_create_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-<snapshot_create_set_name>.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.
### `snapshot_create_snapshot_autoextend_threshold`
Configure the given `snapshot_create_autoextend_threshold` setting in lvm.conf before creating snapshots.
### `snapshot_create_snapshot_autoextend_percent`
Configure the given `snapshot_create_snapshot_autoextend_percent` setting in lvm.conf before creating snapshots.
### `snapshot_create_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: snapshot_create
snapshot_create_set_name: ripu
snapshot_create_snapshot_autoextend_threshold: 70
snapshot_create_snapshot_autoextend_percent: 20
snapshot_create_boot_backup: true
snapshot_create_volumes:
- vg: rootvg
lv: root
size: 2G
- vg: rootvg
lv: var
size: 2G
```

View File

@ -0,0 +1,2 @@
snapshot_create_volumes: []
snapshot_create_boot_backup: false

View File

@ -0,0 +1,238 @@
'''
Check is there is enough space to created all the requested snapshots
The input should be a json string array.
Each element should have the following keys:
- vg: Name of the volume group
- lv: Name of the Logical Volume
- size: The size of the requested snapshot.
Follow (https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-size)
without support for sign
'''
import argparse
import json
import math
import os
import subprocess
import sys
_VGS_COMMAND = '/usr/sbin/vgs'
_LVS_COMMAND = '/usr/sbin/lvs'
_EXIT_CODE_SUCCESS = 0
_EXIT_CODE_VOLUME_GROUP_SPACE = 1
_EXIT_CODE_FILE_SYSTEM_TYPE = 2
_EXIT_CODE_VOLUME_SPACE = 3
_supported_filesystems = [
'',
'ext2',
'ext3',
'ext4'
]
class CheckException(Exception):
""" Exception wrapper """
parser = argparse.ArgumentParser()
parser.add_argument('that', help='What should the script check', type=str, choices=['snapshots', 'resize'])
parser.add_argument('volumes', help='Volumes JSON array in a string', type=str)
def _main():
args = parser.parse_args()
try:
volumes = json.loads(args.volumes)
except json.decoder.JSONDecodeError:
print("Provided volume list '{volumes}' it not a valid json string".format(volumes=sys.argv[1]))
sys.exit(1)
groups_names = set(vol['vg'] for vol in volumes)
groups_info = {
group: _get_group_info(group) for group in groups_names
}
for vol in volumes:
vol['normalized_size'] = _calc_requested_size(groups_info[vol["vg"]], vol)
groups_info[vol["vg"]]['requested_size'] += vol['normalized_size']
if args.that == 'snapshots':
exit_code = _check_free_size_for_snapshots(groups_info)
if exit_code == _EXIT_CODE_SUCCESS:
norm_vols = [
{
'vg': vol['vg'],
'lv': vol['lv'],
'size': "{size}B".format(size=vol['normalized_size'])
} for vol in volumes
]
print(json.dumps(norm_vols))
if args.that == 'resize':
exit_code = _check_free_size_for_resize(volumes, groups_info)
sys.exit(exit_code)
def _check_free_size_for_snapshots(groups_info):
return _check_requested_size(groups_info, 'free')
def _check_free_size_for_resize(volumes, groups_info):
exit_code = _check_requested_size(groups_info, 'size')
if exit_code != _EXIT_CODE_SUCCESS:
return exit_code
mtab = _parse_mtab()
for volume in volumes:
mtab_entry = mtab.get("/dev/mapper/{vg}-{lv}".format(vg=volume['vg'], lv=volume['lv']))
volume['fs_type'] = mtab_entry['type'] if mtab_entry else ''
volume['fs_size'] = _calc_filesystem_size(mtab_entry) if mtab_entry else 0
filesystems_supported = all(volume['fs_type'] in _supported_filesystems for volume in volumes)
if not filesystems_supported:
exit_code = _EXIT_CODE_FILE_SYSTEM_TYPE
enough_space = all(vol['normalized_size'] > vol['fs_size'] for vol in volumes)
if not enough_space:
exit_code = _EXIT_CODE_VOLUME_SPACE
if exit_code != _EXIT_CODE_SUCCESS:
print(json.dumps(_to_printable_volumes(volumes)))
return exit_code
def _check_requested_size(groups_info, group_field):
enough_space = all(group['requested_size'] <= group[group_field] for _, group in groups_info.items())
if not enough_space:
print(json.dumps(groups_info))
return _EXIT_CODE_VOLUME_GROUP_SPACE
return _EXIT_CODE_SUCCESS
def _get_group_info(group):
group_info_str = subprocess.check_output([_VGS_COMMAND, group, '-v', '--units', 'b', '--reportformat', 'json'])
group_info_json = json.loads(group_info_str)
group_info = group_info_json['report'][0]['vg'][0]
return {
'name': group,
'size': _get_size_from_report(group_info['vg_size']),
'free': _get_size_from_report(group_info['vg_free']),
'extent_size': _get_size_from_report(group_info['vg_extent_size']),
'requested_size': 0
}
def _calc_requested_size(group_info, volume):
unit = 'm'
requested_size = volume.get('size', 0)
if requested_size == 0:
# handle thin provisioning
pass
if isinstance(requested_size, int) or isinstance(requested_size, float):
size = requested_size
else:
parts = requested_size.split('%')
if len(parts) == 2:
unit = 'b'
percent = float(parts[0])
percent_of = parts[1]
if percent_of == 'VG':
size = group_info['size'] * percent / 100
elif percent_of == 'FREE':
size = group_info['free'] * percent / 100
elif percent_of == 'ORIGIN':
origin_size = _get_volume_size(volume)
size = origin_size * percent / 100
else:
raise CheckException("Unsupported base type {base_type}".format(base_type=percent_of))
else:
try:
size = float(requested_size[:-1])
unit = requested_size[-1].lower()
except ValueError:
raise CheckException('Failed to read requested size {size}'.format(size=requested_size))
return _align_to_extent(_convert_to_bytes(size, unit), group_info['extent_size'])
def _get_volume_size(vol):
volume_info_str = subprocess.check_output(
[_LVS_COMMAND, "{vg}/{lv}".format(vg=vol['vg'], lv=vol['lv']), '-v', '--units', 'b', '--reportformat', 'json']
)
volume_info_json = json.loads(volume_info_str)
volume_info = volume_info_json['report'][0]['lv'][0]
return _get_size_from_report(volume_info['lv_size'])
def _get_size_from_report(reported_size):
try:
size = float(reported_size)
unit = 'm'
except ValueError:
if reported_size[0] == '<':
reported_size = reported_size[1:]
size = float(reported_size[:-1])
unit = reported_size[-1].lower()
return _convert_to_bytes(size, unit)
def _align_to_extent(size, extent_size):
return math.ceil(size / extent_size) * extent_size
def _calc_filesystem_size(mtab_entry):
fs_stat = os.statvfs(mtab_entry['mount_point'])
return (fs_stat.f_blocks - fs_stat.f_bfree) * fs_stat.f_bsize
def _parse_mtab():
mtab = {}
with open('/etc/mtab') as f:
for m in f:
fs_spec, fs_file, fs_vfstype, _fs_mntops, _fs_freq, _fs_passno = m.split()
mtab[fs_spec] = {
'mount_point': fs_file,
'type': fs_vfstype
}
return mtab
def _convert_to_bytes(size, unit):
convertion_table = {
'b': 1024 ** 0,
'k': 1024 ** 1,
'm': 1024 ** 2,
'g': 1024 ** 3,
't': 1024 ** 4,
'p': 1024 ** 5,
'e': 1024 ** 6,
}
return size * convertion_table[unit]
def _convert_to_unit_size(bytes):
units = ['b', 'k', 'm', 'g', 't', 'p', 'e']
i = 0
while bytes >= 1024:
i += 1
bytes /= 1024
# Round down bytes to two digits
bytes = math.floor(bytes * 100) / 100
return "{size}{unit}".format(size=bytes, unit=units[i])
def _to_printable_volumes(volumes):
return {
volume['vg'] + "_" + volume['lv']: {
'file_system_type': volume['fs_type'],
'used': _convert_to_unit_size(volume['fs_size']),
'requested_size': _convert_to_unit_size(volume['normalized_size'])
} for volume in volumes
}
if __name__ == '__main__':
_main()

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Create a defined set of LVM snapshot volumes
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,32 @@
- name: Validate default kernel is booted
ansible.builtin.include_role:
name: initramfs
tasks_from: preflight
when: snapshot_create_boot_backup
- name: Verify that all volumes exist
ansible.builtin.include_tasks: verify_volume_exists.yml
loop: "{{ snapshot_create_volumes }}"
- name: Verify that there are no existing snapshots
ansible.builtin.include_tasks: verify_no_existing_snapshot.yml
loop: "{{ snapshot_create_volumes }}"
- name: Verify that there is enough storage space
ansible.builtin.script: check.py snapshots '{{ snapshot_create_volumes | to_json }}'
args:
executable: "{{ ansible_python.executable }}"
register: snapshot_create_check_status
failed_when: false
changed_when: false
- name: Store check return in case of failure
ansible.builtin.set_fact:
snapshot_create_check_failure_json: "{{ snapshot_create_check_status.stdout | from_json }}"
when: snapshot_create_check_status.rc != 0
- name: Assert results
ansible.builtin.assert:
that: snapshot_create_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

View File

@ -0,0 +1,68 @@
- name: Update lvm configuration
block:
- name: Stringify snapshot_autoextend_percent setting
ansible.builtin.set_fact:
snapshot_create_snapshot_autoextend_percent_config: "activation/snapshot_autoextend_percent={{ snapshot_create_snapshot_autoextend_percent }}"
when: snapshot_create_snapshot_autoextend_percent is defined
- name: Stringify snapshot_autoextend_threshold setting
ansible.builtin.set_fact:
snapshot_create_snapshot_autoextend_threshold_config: "activation/snapshot_autoextend_threshold={{ snapshot_create_snapshot_autoextend_threshold }}"
when: snapshot_create_snapshot_autoextend_threshold is defined
- name: Stringify the new config
ansible.builtin.set_fact:
snapshot_create_new_lvm_config: >
{{ snapshot_create_snapshot_autoextend_percent_config | default('') }}
{{ snapshot_create_snapshot_autoextend_threshold_config | default('') }}
- name: Set LVM configuration
ansible.builtin.command: 'lvmconfig --mergedconfig --config "{{ snapshot_create_new_lvm_config }}" --file /etc/lvm/lvm.conf'
changed_when: true
when: ((snapshot_create_new_lvm_config | trim) | length) > 0
- name: Check for grubenv saved_entry
ansible.builtin.lineinfile:
name: /boot/grub2/grubenv
regexp: ^saved_entry=
state: absent
check_mode: true
changed_when: false
failed_when: false
register: snapshot_create_grubenv
- name: Add grubenv saved_entry
ansible.builtin.shell: 'grubby --set-default-index=$(grubby --default-index)'
changed_when: true
when: snapshot_create_grubenv.found is defined and snapshot_create_grubenv.found == 0
- name: Create snapshots
community.general.lvol:
vg: "{{ item.vg }}"
lv: "{{ item.lv }}"
snapshot: "{{ item.lv }}_{{ snapshot_create_set_name }}"
size: "{{ item.size | default(omit) }}"
loop: "{{ snapshot_create_volumes }}"
- name: Required packages are present
ansible.builtin.package:
name:
- gzip
- tar
state: present
- name: Create boot backup
community.general.archive:
format: gz
mode: '0644'
dest: "/root/boot-backup-{{ snapshot_create_set_name }}.tgz"
path:
- "/boot/initramfs-{{ ansible_kernel }}.img"
- "/boot/vmlinuz-{{ ansible_kernel }}"
- "/boot/System.map-{{ ansible_kernel }}"
- "/boot/symvers-{{ ansible_kernel }}.gz"
- "/boot/config-{{ ansible_kernel }}"
- "/boot/.vmlinuz-{{ ansible_kernel }}.hmac"
- /boot/grub/grub.conf
- /boot/grub2/grub.cfg
- /boot/grub2/grubenv
- /boot/loader/entries
- /boot/efi/EFI/redhat/grub.cfg
when: snapshot_create_boot_backup

View File

@ -0,0 +1,8 @@
- name: Check available disk space
ansible.builtin.include_tasks: check.yml
- name: Create Snapshot
vars:
snapshot_create_volumes: "{{ snapshot_create_check_status.stdout | from_json }}"
ansible.builtin.include_tasks: create.yml
when: not (snapshot_create_check_only | default(false))

View File

@ -0,0 +1,21 @@
- name: Run lvs
ansible.builtin.command: >
lvs
--select 'vg_name = {{ item.vg }}
&& origin = {{ item.lv }}
&& lv_name = {{ item.lv }}_{{ snapshot_create_set_name }}'
--reportformat json
register: snapshot_create_lvs_response
changed_when: false
- name: Parse report
ansible.builtin.set_fact:
snapshot_create_lv_snapshot_report_array: "{{ (snapshot_create_lvs_response.stdout | from_json).report[0].lv }}"
- name: Verify that the no snapshot exists for the volume
ansible.builtin.assert:
that: (snapshot_create_lv_snapshot_report_array | length) == 0
fail_msg: >
The volume '{{ item.lv }}' in volume group '{{ item.vg }}'
already has at least one snapshot
'{{ snapshot_create_lv_snapshot_report_array[0].lv_name | default('none') }}'

View File

@ -0,0 +1,9 @@
- name: Run lvs
ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg }} && lv_name = {{ item.lv }}' --reportformat json"
register: snapshot_create_lvs_response
changed_when: false
- name: Verify that the volume was found
ansible.builtin.assert:
that: (((snapshot_create_lvs_response.stdout | from_json).report[0].lv) | length) > 0
fail_msg: "Could not find volume '{{ item.lv }}' in volume group '{{ item.vg }}'"

View File

@ -0,0 +1,32 @@
# snapshot_remove role
The `snapshot_remove` 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 `snapshot_create` 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
### `snapshot_remove_set_name`
The variable `snapshot_remove_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:
`<Origin LV name>_<snapshot_remove_set_name>`
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: snapshot_remove
snapshot_remove_set_name: ripu
```

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Remove snapshots previously created using the snapshot_create role
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,23 @@
- name: Calculate the list of snapshots
block:
- name: Get list of volumes
ansible.builtin.command: "lvs --select 'lv_name =~ {{ snapshot_remove_set_name }}$ && origin != \"\"' --reportformat json "
register: snapshot_remove_lvs_response
changed_when: false
- name: Get LV dict List
ansible.builtin.set_fact:
snapshot_remove_snapshots: "{{ (snapshot_remove_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: "{{ snapshot_remove_snapshots }}"
- name: Remove boot backup
ansible.builtin.file:
path: "/root/boot-backup-{{ snapshot_remove_set_name }}.tgz"
state: absent

View File

@ -0,0 +1,36 @@
# snapshot_revert role
The `snapshot_revert` 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 `snapshot_create` 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
### `snapshot_revert_set_name`
The variable `snapshot_revert_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:
`<Origin LV name>_<snapshot_revert_set_name>`
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 `snapshot_create` 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: snapshot_revert
snapshot_revert_set_name: ripu
```

View File

@ -0,0 +1,14 @@
---
galaxy_info:
author: Ygal Blum, Bob Mader
description: Revert to snapshots previously created using the snapshot_create role
company: Red Hat
license: MIT
min_ansible_version: "2.14"
platforms:
- name: EL
versions:
- all
galaxy_tags: []
dependencies: []
...

View File

@ -0,0 +1,73 @@
- name: Calculate the list of snapshots
block:
- name: Get list of volumes
ansible.builtin.command: "lvs --select 'lv_name =~ {{ snapshot_revert_set_name }}$ && origin != \"\"' --reportformat json "
register: snapshot_revert_lvs_response
changed_when: false
- name: Get LV dict List
ansible.builtin.set_fact:
snapshot_revert_snapshots: "{{ (snapshot_revert_lvs_response.stdout | from_json).report[0].lv }}"
- name: Verify that all snapshots are active
ansible.builtin.include_tasks: verify_snapshot_active.yml
loop: "{{ snapshot_revert_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-{{ snapshot_revert_set_name }}.tgz"
register: snapshot_revert_boot_archive_stat
- name: Restore boot backup
ansible.builtin.unarchive:
remote_src: true
src: "{{ snapshot_revert_boot_archive_stat.stat.path }}"
dest: /boot
when: snapshot_revert_boot_archive_stat.stat.exists
- name: Revert to snapshots
ansible.builtin.command: "lvconvert --merge /dev/{{ item.vg_name }}/{{ item.lv_name }}"
loop: "{{ snapshot_revert_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: snapshot_revert_boot_abstraction
- name: Reinstall Grub to boot device
when: snapshot_revert_boot_abstraction.stdout == 'lvm'
block:
- name: Get boot device
ansible.builtin.shell: "lsblk -spnlo name $(grub2-probe --target=device /boot)"
changed_when: false
register: snapshot_revert_boot_dev_deps
- name: Run grub2-install
ansible.builtin.command: "grub2-install {{ snapshot_revert_boot_dev_deps.stdout_lines | last }}"
changed_when: true
- name: Remove boot backup
ansible.builtin.file:
path: "{{ snapshot_revert_boot_archive_stat.stat.path }}"
state: absent
when: snapshot_revert_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: snapshot_revert_lv_drain_check
until: (snapshot_revert_lv_drain_check.stdout | from_json).report[0].lv[0].data_percent == ""
retries: 20
delay: 30
loop: "{{ snapshot_revert_snapshots }}"
changed_when: false

View File

@ -0,0 +1,14 @@
- name: Run lvs
ansible.builtin.command: "lvs --select 'lv_name = {{ item.lv_name }}' --reportformat json"
register: snapshot_revert_lvs_response
changed_when: false
- name: Parse report
ansible.builtin.set_fact:
snapshot_revert_lv_attr: "{{ (snapshot_revert_lvs_response.stdout | from_json).report[0].lv[0].lv_attr }}"
- name: Verify that the snapshot is active
ansible.builtin.assert:
that:
- snapshot_revert_lv_attr[0] == 's'
- snapshot_revert_lv_attr[4] == 'a'

View File

@ -0,0 +1,39 @@
# Testing the LVM Snapshot Role
## Prerequisites
- All the tests are in the form of ansible playbooks.
- All playbooks expect that the target machine will have a secondary storage device to be used for testing.
## Variables
The variables may be passed as part of the inventory or using a separate file.
```yaml
device: < device node without `/dev`. e.g. vdb >
```
## Ansible Configuration
In order to run the tests from the repo without having to install them,
the tests directory includes an [ansible.cfg](./ansible.cfg) file.
Make sure to point to it when running the test playbook
## Running a test
### Inventory file
In this example, the `device` parameter is passed in the `inventory.yml` file
```yaml
all:
hosts:
<FQDN of test machine>:
device: vdb
```
### Command line
Running the [snapshot revert playbook](./test-revert-playbook.yml) test from the repo
```bash
ANSIBLE_CONFIG=./tests/ansible.cfg ansible-playbook -K -i inventory.yml tests/test-revert-playbook.yml
```

View File

@ -0,0 +1,2 @@
[defaults]
roles_path=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:../roles

View File

@ -0,0 +1,15 @@
- name: Generate snapshot list out of volumes list
ansible.builtin.set_fact:
_snapshots: "{{ (_snapshots | default([])) + [{'vg': volume_group, 'lv': item.name, 'size': item.size}] }}"
loop: "{{ volumes }}"
- name: Create the snapshot
vars:
snapshot_create_volumes: "{{ _snapshots }}"
snapshot_create_set_name: "{{ snapshot_set_name }}"
ansible.builtin.include_role:
name: snapshot_create
- name: Verify that the snapshot was created
ansible.builtin.include_tasks: verify-snapshot-created.yml
loop: "{{ volumes }}"

View File

@ -0,0 +1,40 @@
- name: Fill the volume
block:
- name: Set the retry count
ansible.builtin.set_fact:
_retry_count: "{{ (_retry_count | default('-1') | int) + 1 }}"
- name: Generate the Sub-Directory name
ansible.builtin.set_fact:
_sub_dir_name: "{{ lookup('community.general.random_string', upper=false, numbers=false, special=false) }}"
- name: Make a copy of the boot partition
ansible.builtin.copy:
src: /boot
dest: "{{ test_directory }}/{{ _sub_dir_name }}"
remote_src: true
mode: '0777'
- name: Get the status of the snapshot
ansible.builtin.command: "lvs --select 'lv_name = {{ volume_name }}_{{ snapshot_set_name }}' --reportformat json"
register: _lv_status_check
changed_when: false
- name: Store the snapshot data_percent
ansible.builtin.set_fact:
_snapshot_data_percent: "{{ ((_lv_status_check.stdout | from_json).report[0].lv[0].data_percent) }}"
- name: Check if snapshot is full enough
ansible.builtin.assert:
that: _snapshot_data_percent|float > snapshot_fill_percent|float
quiet: true
rescue:
- name: Check the retry count to avoid endless loop
ansible.builtin.assert:
that: (_retry_count|int) < (snapshot_max_retry|int)
fail_msg: "Ended after {{ snapshot_max_retry }} retries"
success_msg: "Volume is not full enough ({{ _snapshot_data_percent }}) - Run again..."
- name: Include the same tasks file again
ansible.builtin.include_tasks: fill-snapshot.yml

View File

@ -0,0 +1,11 @@
- name: Unmount the "{{ item.directory }}"
ansible.posix.mount:
path: "{{ item.directory }}"
state: absent
- name: Remove the logical volume
community.general.lvol:
vg: "{{ volume_group }}"
lv: "{{ item.name }}"
force: true
state: absent

View File

@ -0,0 +1,25 @@
- name: Cleanup the volumes
ansible.builtin.include_tasks: post-test-clean-volume.yml
loop: "{{ volumes }}"
- name: Remove the volume group
community.general.lvg:
vg: "{{ volume_group }}"
pvs: "/dev/{{ device }}1"
state: absent
- name: Remove the PV
ansible.builtin.command: "pvremove /dev/{{ device }}1"
changed_when: true
- name: Cleanup the system.devices file
ansible.builtin.lineinfile:
path: /etc/lvm/devices/system.devices
regexp: "IDTYPE=devname IDNAME=/dev/{{ device }}1 DEVNAME=/dev/{{ device }}1 PVID=. PART=1"
state: absent
- name: Delete the partition
community.general.parted:
device: "/dev/{{ device }}"
number: 1
state: absent

View File

@ -0,0 +1,18 @@
- name: Create the logical volume
community.general.lvol:
vg: "{{ volume_group }}"
lv: "{{ item.name }}"
size: "{{ item.size }}"
force: true
- name: Format the ext4 filesystem
community.general.filesystem:
fstype: ext4
dev: "/dev/{{ volume_group }}/{{ item.name }}"
- name: Mount the lv on "{{ item.directory }}"
ansible.posix.mount:
path: "{{ item.directory }}"
src: "/dev/{{ volume_group }}/{{ item.name }}"
fstype: ext4
state: mounted

View File

@ -0,0 +1,23 @@
- name: Create partition
community.general.parted:
device: "/dev/{{ device }}"
number: 1
part_end: 9GiB
flags:
- lvm
state: present
- name: Install lvm2 dependency
ansible.builtin.package:
name: lvm2
state: present
- name: Create the volume group
community.general.lvg:
vg: "{{ volume_group }}"
pvs: "/dev/{{ device }}1"
pesize: 16
- name: Create and prepare the volumes
ansible.builtin.include_tasks: pre-test-prepare-volume.yml
loop: "{{ volumes }}"

View File

@ -0,0 +1,24 @@
- name: Run lvs
ansible.builtin.command: lvs --select 'vg_name = {{ volume_group }} && origin = {{ item.name }}' --reportformat json
register: lvs_response
changed_when: false
- name: Parse report
ansible.builtin.set_fact:
lv_snapshot_array: "{{ (lvs_response.stdout | from_json).report[0].lv }}"
- name: Verify that the the snapshot exists
ansible.builtin.assert:
that: (lv_snapshot_array | length) == 1
fail_msg: >
The snapshot for {{ item.name }} was not created
- name: Get the snapshot name
ansible.builtin.set_fact:
snapshot_name: "{{ lv_snapshot_array[0].lv_name | default('n/a') }}"
- name: Verify that the the snapshot was named correctly
ansible.builtin.assert:
that: snapshot_name == item.name + '_' + snapshot_set_name
fail_msg: >
Snapshot name '{{ snapshot_name }}' is not as expected {{ item.name }}_{{ snapshot_set_name }}

View File

@ -0,0 +1,16 @@
- name: Run lvs
ansible.builtin.command: lvs --select 'vg_name = {{ volume_group }} && lv_name = {{ volume_name }}_{{ snapshot_set_name }}' --reportformat json
register: lvs_response
changed_when: false
- name: Parse report
ansible.builtin.set_fact:
lv_snapshot_report_array: "{{ (lvs_response.stdout | from_json).report[0].lv }}"
- name: Verify that the snapshot no longer exists
ansible.builtin.assert:
that: (lv_snapshot_report_array | length) == 0
fail_msg: >
The snapshot '{{ volume_name }}_{{ snapshot_set_name }}'' for
volume '{{ volume_name }}' in volume group '{{ volume_group }}'
still exists

View File

@ -1,9 +1,14 @@
---
collections:
# - name: infra.lvm_snapshots.shrink_lv
# source: https://github.com/redhat-cop/infra.lvm_snapshots.git
# type: git
# version: main
# - name: infra.lvm_snapshots.shrink_lv
# source: https://github.com/redhat-cop/infra.lvm_snapshots.git
# type: git
# version: main
# - name: infra.lvm_snapshots.shrink_lv
# source: https://github.com/jchristianh/infra.lvm_snapshots.git
# type: git
# version: check-device-fix
- name: rhc.rear
source: https://gitea.thezengarden.net/ansible_collections/ansible-collection-rear.git
@ -16,7 +21,5 @@ collections:
- name: ansible.posix
version: 1.5.4
- name: infra.lvm_snapshots
version: 2.0.2
...
# - name: infra.lvm_snapshots
# version: 2.1.0

View File

@ -0,0 +1,66 @@
#!/usr/bin/python3
# bigboot_size: "{{ bigboot_size_expansion_mb[:-2] | int | get_block_size_up }}"
class FilterModule (object):
def filters (self):
return {
'bigboot_fallback_size': self.bigboot_fallback_size,
'moo': self.moo,
}
def bigboot_fallback_size(self, fallback_size):
return ('{}{}'.format(fallback_size, 'M'))
def moo(self, a_variable):
a_new_variable = (str(a_variable) + '~~~~MOOOOOOOOOOOOOOOOOOOOOOOOO! => ' + '%s' % type(a_variable))
return a_new_variable
# import shutil
# import os
# def check_mount_space(mount_path, min_space_gb=1):
# try:
# # Get disk usage statistics for the mount point
# disk_usage = shutil.disk_usage(mount_path)
# # Convert bytes to gigabytes
# free_space_gb = disk_usage.free / (1024 ** 3)
# print(f"Mount point: {mount_path}")
# print(f"Free space: {free_space_gb:.2f} GB")
# if free_space_gb > min_space_gb:
# print("✓ More than 1GB available")
# return True
# else:
# print("✗ Less than 1GB available")
# return False
# except Exception as e:
# print(f"Error checking {mount_path}: {str(e)}")
# return False
# def check_all_mounts():
# # Read mount points from /proc/mounts
# with open('/proc/mounts', 'r') as f:
# mounts = f.readlines()
# results = []
# for mount in mounts:
# # Split the mount line and get the mount point (second field)
# mount_point = mount.split()[1]
# # Skip some virtual filesystems
# if mount_point.startswith(('/proc', '/sys', '/dev', '/run')):
# continue
# results.append(check_mount_space(mount_point))
# return results
# if __name__ == "__main__":
# print("Checking mount points for available space...")
# check_all_mounts()

View File

@ -7,7 +7,9 @@ class FilterModule (object):
return {
'get_block_size_up': self.get_block_size_up,
'get_block_size_down': self.get_block_size_down,
'moo': self.moo,
'get_block_size_up_bytes': self.get_block_size_up_bytes,
'get_block_size_down_bytes': self.get_block_size_down_bytes,
'moo': self.moo
}
@ -21,6 +23,16 @@ class FilterModule (object):
start_size -= 1
return ('{}{}'.format(start_size, 'M'))
def get_block_size_up_bytes(self, start_size):
while start_size % BLOCK_SIZE != 0:
start_size += 1
return ('{}'.format(start_size))
def get_block_size_down_bytes(self, start_size):
while start_size % BLOCK_SIZE != 0:
start_size -= 1
return ('{}'.format(start_size))
def moo(self, a_variable):
a_new_variable = (str(a_variable) + '~~~~MOOOOOOOOOOOOOOOOOOOOOOOOO! => ' + '%s' % type(a_variable))
return a_new_variable

View File

@ -3,3 +3,8 @@
src: https://gitea.thezengarden.net/ansible_roles/autofsck.git
scm: git
version: main
- name: verified_reboot
src: https://gitea.thezengarden.net/ansible_plays/verified_reboot.git
scm: git
version: main

73
scripts/pvsqueeze.sh Normal file
View File

@ -0,0 +1,73 @@
#!/bin/bash
#
# pvsqueeze - Squeeze linear extents into free holes
#
# This script iteratively moves linear (allocated to LV) extents to fill in
# free holes found in a PV segment map. The end result is that all free
# extents are at the end of the PV. Use this script in anger as required!
#
# The script outputs the PV segment map before moving anything, then iterates
# stuffing extents in each free hole until there are no more except for the
# last one, and finally outputs the PV segment map again showing the changes.
#
# Usage
if [[ ! $1 =~ ^/dev/ ]]; then
echo "Usage: $0 PV"
exit 1
fi
# Set some vars
pvdev=$1
pvsargs='--noheadings --nameprefixes --segments -o pvseg_start,pvseg_size,segtype'
# Show current segment map
echo 'PV segment map before squeeze:'
pvs "$pvdev" --segments -o lvname,pvseg_start,pvseg_size,seg_le_ranges
if [[ $? -ne 0 ]]; then
echo "$0: Fatal error getting PV info"
exit 1
fi
# Iterate stuffing PEs in each free hole
while true; do
# Find first free segment
LVM2_PVSEG_START=
eval $(pvs "$pvdev" $pvsargs | grep SEGTYPE=.free. | head -1)
if [[ -z $LVM2_PVSEG_START ]]; then
echo "$0: No free segments found"
exit 1
fi
free_start=$LVM2_PVSEG_START
free_size=$LVM2_PVSEG_SIZE
# Find the last linear segment
LVM2_PVSEG_START=
eval $(pvs "$pvdev" $pvsargs | grep SEGTYPE=.linear. | tail -1)
if [[ -z $LVM2_PVSEG_START ]]; then
echo "$0: No linear segments found"
exit 1
fi
move_start=$LVM2_PVSEG_START
move_size=$LVM2_PVSEG_SIZE
# We're done when first free is after the last linear
[[ $free_start -gt $move_start ]] && break
# Move last linear PEs to the free segment
[[ $free_size -gt $move_size ]] && free_size=$move_size
from_range="$pvdev:$((move_start+move_size-free_size))-$((move_start+move_size-1))"
to_range="$pvdev:$((free_start))-$((free_start+free_size))"
echo "Moving $from_range to $to_range..."
pvmove --atomic --alloc anywhere "$from_range" "$to_range"
if [[ $? -ne 0 ]]; then
echo "$0: Fatal error doing pvmove"
exit 1
fi
done
# Show squeezed segment map
echo 'PV segment map after squeeze:'
pvs "$pvdev" --segments -o lvname,pvseg_start,pvseg_size,seg_le_ranges
exit 0

View File

@ -21,14 +21,6 @@
bigboot_expansion_diff:
"{{ bigboot_size_target | human_to_bytes - bigboot_boot_partsize | human_to_bytes }}"
- name: Convert size difference to MB
ansible.builtin.set_fact:
bigboot_size_expansion_mb: "{{ bigboot_expansion_diff | int | human_readable(unit='M') }}"
- name: Set bigboot size 4k aligned
ansible.builtin.set_fact:
bigboot_size: "{{ bigboot_size_expansion_mb[:-2] | int | get_block_size_up }}"
- name: Validate if we need to expand boot
block:
- name: Set flag for Bigboot execution
@ -41,6 +33,19 @@
fail_msg: The /boot partition will need to be resized
success_msg: The /boot partition is already at the desired size
rescue:
- name: "Check if /boot is already at or above {{ bigboot_size_min }}"
block:
- name: "Assert that /boot is already at or above {{ bigboot_size_min }}"
ansible.builtin.assert:
that: bigboot_boot_partsize | human_to_bytes >= bigboot_size_min | human_to_bytes
success_msg: >
/boot partition size is already at least {{ bigboot_size_min }}
or greater. Nothing to do."
fail_msg: >
/boot partition size is less than {{ bigboot_size_min }}.
Expansion of /boot is required.
rescue:
- name: Set flag for Bigboot execution
ansible.builtin.set_fact:
@ -48,4 +53,6 @@
- name: Expansion of /boot required
ansible.builtin.debug:
msg: "Will need to expand /boot by an additional {{ bigboot_size }}."
msg: >
Will attempt to increase to {{ bigboot_size_target }}, or
{{ bigboot_size_min }} at the minimum."

View File

@ -42,14 +42,36 @@
bigboot_lv_info: "{{ ansible_facts.mounts \
| selectattr('device', 'equalto', bigboot_adjacent_lvm_device) | first }}"
- name: Assert that there is space on the logical volume for shrinkage
ansible.builtin.assert:
that: bigboot_lv_info.size_available > bigboot_expansion_diff | int
fail_msg: There is not enough space available for LV shrinking.
# - name: Assert that there is space on the logical volume for shrinkage
# ansible.builtin.assert:
# that: bigboot_lv_info.size_available > bigboot_expansion_diff | int
# fail_msg: There is not enough space available for LV shrinking.
- name: Capture shrink size for logical volume
- name: Capture UUID for target volume
ansible.builtin.set_fact:
bigboot_lv_shrink_size: "{{ bigboot_lv_info.size_total - bigboot_expansion_diff | int }}"
bigboot_lv_uuid: "{{ ansible_facts['mounts'] | selectattr('device', 'equalto', bigboot_adjacent_lvm_device) | map(attribute='uuid') | first }}"
- name: Capture block device information for target logical volume
ansible.builtin.set_fact:
bigboot_lv_dm: "{{ ansible_device_links['uuids'] | dict2items | selectattr('value', 'contains', bigboot_lv_uuid) | map(attribute='key') | first }}"
- name: Set initial sizes for logical volume and fallback size target
ansible.builtin.set_fact:
bigboot_lv_partsize: "{{ ansible_devices[bigboot_lv_dm]['sectors'] | int * ansible_devices[bigboot_lv_dm]['sectorsize'] | int }}"
bigboot_size_target_fallback: "{{ bigboot_size_target | human_to_bytes }}"
- name: Check available space and fallback to lower /boot size if required
ansible.builtin.include_tasks: check_space_fallback.yml
- name: Capture shrink and fallback sizes
ansible.builtin.set_fact:
# we need to get the BLOCK size total, not the filesystem
bigboot_lv_shrink_size: "{{ (bigboot_lv_partsize | int - bigboot_expansion_diff | int) | get_block_size_down_bytes }}"
bigboot_size_target_fallback: "{{ bigboot_size_target_fallback | int | human_readable(unit='G') | regex_replace('\\sGB', 'G') }}"
- name: Debug bigboot_lv_shrink_size and bigboot_size_target_fallback
ansible.builtin.debug:
msg: "{{ bigboot_lv_shrink_size | int | human_readable(unit='G') }} ({{ bigboot_lv_shrink_size | int | human_readable(unit='M') | regex_replace('\\.\\d+\\s+MB') | int | get_block_size_down }}) <-> {{ bigboot_size_target_fallback }} | {{ bigboot_expansion_diff }}"
- name: Capture logical volume name
ansible.builtin.shell:
@ -62,7 +84,7 @@
- name: Format logical volume name
ansible.builtin.set_fact:
bigboot_lv_vg_name: "{{ bigboot_lv_vg_name.stdout | regex_replace('VG\\s+Name\\s+(.*)$', '\\1') }}"
bigboot_lv_vg_name: "{{ bigboot_lv_vg_name['stdout'] | regex_replace('VG\\s+Name\\s+(.*)$', '\\1') }}"
- name: Capture volume group free PE
ansible.builtin.shell:
@ -73,6 +95,30 @@
changed_when: false
register: bigboot_lv_vg_free_pe
- name: Capture volume group PE size
ansible.builtin.shell:
cmd: |
set -o pipefail
vgdisplay {{ bigboot_lv_vg_name | trim }} | grep -i 'pe size'
executable: /bin/bash
changed_when: false
register: bigboot_lv_vg_pe_capture
- name: Set PE size
ansible.builtin.set_fact:
bigboot_lv_vg_pe_size: "{{ bigboot_lv_vg_pe_capture | regex_replace('^.*\\s+(\\d+.\\d+).*$', '\\1M') | human_to_bytes }}"
- name: Subtract one additional PE for shrinking
ansible.builtin.set_fact:
bigboot_lv_shrink_size: "{{ bigboot_lv_shrink_size | int - bigboot_lv_vg_pe_size | int }}"
# - name: Kill the play
# ansible.builtin.meta: end_host
- name: Capture the PV device
ansible.builtin.set_fact:
bigboot_pv: "{{ bigboot_boot_mount['device'][:-1] }}{{ bigboot_boot_mount['device'][-1:] | int + 1 }}"
- name: Format logical volume free PE
ansible.builtin.set_fact:
# Ex:
@ -80,23 +126,24 @@
# Free PE / Size 189 / 756.00 MiB"
# Free PE / Size 414 / <1.62 GiB
# Free PE / Size 0 / 0
bigboot_lv_vg_free_pe: "{{ bigboot_lv_vg_free_pe.stdout | regex_replace('^.*/.*/\\s+[<]?(.*)', '\\1') }}"
bigboot_lv_vg_free_pe: "{{ bigboot_lv_vg_free_pe['stdout'] | regex_replace('^.*/.*/\\s+[<]?(.*)', '\\1') }}"
- name: Get size in MB for PE and
- name: Get size in MB for available PE
ansible.builtin.set_fact:
bigboot_lv_pe_size_in_mb:
"{{ bigboot_lv_vg_free_pe | regex_replace('i|\\s+|<', '') | human_to_bytes | human_readable(unit='M') }}"
- name: Verify if there's available PE or not and execute Shrink_LV
- name: Verify available PE for the volume group
block:
- name: Set flag for Shrink_LV execution
- name: Set variables required for shrinking the target LV
ansible.builtin.set_fact:
bigboot_execute_shrink_lv: false
bigboot_expansion_diff_mb: "{{ bigboot_expansion_diff | int | human_readable(unit='M') }}"
- name: Assert if we need to execute the shrink_lv role to gain free PE
- name: Assert if we need to shrink the logical volume to gain PE
ansible.builtin.assert:
that: (bigboot_lv_pe_size_in_mb[:-3] | int | round) | int > bigboot_size[:-1] | int
fail_msg: Not enough PE to expand /boot.
that: (bigboot_lv_pe_size_in_mb[:-3] | int) >= (bigboot_expansion_diff_mb[:-3] | int)
fail_msg: Not enough PE to expand /boot. Will need to shrink target LV.
rescue:
- name: Set flag for Shrink_LV execution

56
tasks/check_services.yml Normal file
View File

@ -0,0 +1,56 @@
---
- name: Ensure service facts are available
ansible.builtin.service_facts:
# - name: Capture a list of running services
# ansible.builtin.set_fact:
# bigboot_systemd_running_services:
# "{{ bigboot_systemd_running_services | default([]) + [item['key']] }}"
# loop: "{{ ansible_facts['services'] | dict2items }}"
# loop_control:
# label: "{{ item['key'] }}"
# when:
# - "'running' in item['value']['state']"
# - name: Get the stop timeout value for running services
# ansible.builtin.shell:
# cmd: |
# set -o pipefail
# systemctl show {{ item }} | grep TimeoutStopUSec
# changed_when: false
# register: bigboot_systemd_service_timeout
# loop: "{{ bigboot_systemd_running_services }}"
# - name: Adding services exceeding the timeout threshold to the list of services to disable
# ansible.builtin.set_fact:
# bigboot_services_disabled: "{{ bigboot_services_disabled | default([]) + [item['item']] }}"
# loop: "{{ bigboot_systemd_service_timeout['results'] }}"
# loop_control:
# label: "{{ item['item'] }}"
# when:
# - item['item'] not in bigboot_protected_services
# - item['stdout'] | regex_replace('^.*=(.*$)', '\\1') | community.general.to_minutes >= bigboot_service_max_timeout | int
- name: Adding incompatible services to the list of services to disable
ansible.builtin.set_fact:
bigboot_services_disabled: "{{ bigboot_services_disabled | default([]) + [item] }}"
loop: "{{ bigboot_incompatible_services }}"
when:
- ansible_facts['services'][item] is defined
- ansible_facts['services'][item]['state'] == "running"
- name: Log and disable services
when:
- bigboot_services_disabled is defined
- bigboot_services_disabled | length > 0
block:
- name: Disable services and log their state
ansible.builtin.include_tasks: tasks/disable_systemd_services.yml
loop: "{{ bigboot_services_disabled }}"
- name: Services disabled notice
ansible.builtin.debug:
msg: >-
The following services were disabled, and will be re-enabled post
Bigboot execution: {{ bigboot_services_disabled | join(', ') }}

View File

@ -0,0 +1,36 @@
---
- name: Debug bigboot_expansion_diff
ansible.builtin.debug:
msg: "{{ bigboot_expansion_diff }} + {{ bigboot_size_target_fallback }} <-> {{ bigboot_boot_partsize }}"
- name: Fail if /boot can't expand to at least 1GB
ansible.builtin.assert:
that: bigboot_size_target_fallback | int >= bigboot_size_min | human_to_bytes
fail_msg:
"{{ bigboot_size_target_fallback | int | human_readable(unit='M') }} is less than the minimum required size of {{ bigboot_size_min }}."
- name: Verify available space and re-check if needed
block:
- name: Assert that there is space on the logical volume for shrinkage
ansible.builtin.assert:
that: bigboot_lv_info['size_available'] > bigboot_expansion_diff | int
fail_msg: There is not enough free space available to shrink the filesystem
- name: Assert that there will be enough space left over on the target LV
ansible.builtin.assert:
that: (bigboot_lv_info['size_available'] - bigboot_expansion_diff | int) >= bigboot_lv_size_min | human_to_bytes
# success_msg: "There will be enough space left over with a boot size of {{ bigboot_size_target_fallback | int | human_readable(unit='G') }}. ({{ (bigboot_lv_info['size_total'] - bigboot_expansion_diff | int) | human_readable(unit='B') }})"
fail_msg: "There will NOT be enough space left over with a boot size of {{ bigboot_size_target_fallback | int | human_readable(unit='G') }}."
rescue:
- name: "Decrement size target by {{ bigboot_size_decrement }}"
ansible.builtin.set_fact:
bigboot_size_target_fallback: "{{ bigboot_size_target_fallback | int - bigboot_size_decrement | human_to_bytes }}"
- name: Update required expansion space
ansible.builtin.set_fact:
bigboot_expansion_diff:
"{{ (bigboot_size_target_fallback | int - bigboot_boot_partsize | human_to_bytes) }}"
- name: Re-check disk space - RESCUE
ansible.builtin.include_tasks: check_space_fallback.yml

View File

@ -1,40 +0,0 @@
---
- name: Get the list of services on the host
ansible.builtin.service_facts:
- name: Capture a list of running services
ansible.builtin.set_fact:
bigboot_systemd_running_services:
"{{ bigboot_systemd_running_services | default([]) + [item['key']] }}"
loop: "{{ ansible_facts['services'] | dict2items }}"
loop_control:
label: "{{ item['key'] }}"
when:
- "'running' in item['value']['state']"
- name: Get the stop timeout value for running services
ansible.builtin.shell:
cmd: |
set -o pipefail
systemctl show {{ item }} | grep TimeoutStopUSec
changed_when: false
register: bigboot_systemd_service_timeout
loop: "{{ bigboot_systemd_running_services }}"
- name: Disabling services exceeding the timeout threshold
ansible.builtin.include_tasks: tasks/disable_systemd_services.yml
loop: "{{ bigboot_systemd_service_timeout['results'] }}"
loop_control:
label: "{{ item['item'] }}"
when:
- item['item'] not in bigboot_protected_services
- item['stdout'] | regex_replace('^.*=(.*$)', '\\1') | community.general.to_minutes >= bigboot_service_max_timeout | int
- name: Services disabled notice
ansible.builtin.debug:
msg: >-
The following services were disabled, and will be re-enabled post
Bigboot execution: {{ bigboot_systemd_disabled_services | join(',') }}
when:
- bigboot_systemd_disabled_services is defined
- bigboot_systemd_disabled_services | length > 0

Some files were not shown because too many files have changed in this diff Show More