-
Notifications
You must be signed in to change notification settings - Fork 1.7k
lvm_pv: new module for LVM Physical Volumes #10070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
fa5d3d2
Added LVM Physical Volume module
klention dc2ff03
Fixed CI checks
klention 4647659
python 2.7 compatibility
klention 4995718
Fixed another fprint line not compatible with python 2.x
klention bfb4954
Applied cosmetic changes
klention 123ec6b
Removed msg from RETURN section
klention 6e142e1
Updated the 'absent state' block logic
klention 9296261
Added integration tests
klention 55abf0c
Updated logic for creating loop devices on Alpine Linux
klention d4fa2f3
Updated loop device path
klention 7364708
Minor, cosmetic changes
klention f162d32
Adjust indentation.
felixfontein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright (c) 2025, Klention Mali <[email protected]> | ||
# Based on lvol module by Jeroen Hoekx <[email protected]> | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
|
||
DOCUMENTATION = r''' | ||
--- | ||
module: lvm_pv | ||
short_description: Manage LVM Physical Volumes | ||
version_added: "11.0.0" | ||
description: | ||
- Creates, resizes or removes LVM Physical Volumes. | ||
klention marked this conversation as resolved.
Show resolved
Hide resolved
|
||
author: | ||
- Klention Mali (@klention) | ||
options: | ||
device: | ||
description: | ||
- Path to the block device to manage. | ||
type: path | ||
required: true | ||
state: | ||
description: | ||
- Control if the physical volume exists. | ||
type: str | ||
choices: [ present, absent ] | ||
default: present | ||
force: | ||
description: | ||
- Force the operation. | ||
- When O(state=present) (creating a PV), this uses C(pvcreate -f) to force creation. | ||
- When O(state=absent) (removing a PV), this uses C(pvremove -ff) to force removal even if part of a volume group. | ||
type: bool | ||
default: false | ||
resize: | ||
description: | ||
- Resize PV to device size when O(state=present). | ||
type: bool | ||
default: false | ||
notes: | ||
- Requires LVM2 utilities installed on the target system. | ||
- Device path must exist when creating a PV. | ||
''' | ||
|
||
EXAMPLES = r''' | ||
- name: Creating physical volume on /dev/sdb | ||
community.general.lvm_pv: | ||
device: /dev/sdb | ||
|
||
- name: Creating and resizing (if needed) physical volume | ||
community.general.lvm_pv: | ||
device: /dev/sdb | ||
resize: true | ||
|
||
- name: Removing physical volume that is not part of any volume group | ||
community.general.lvm_pv: | ||
device: /dev/sdb | ||
state: absent | ||
|
||
- name: Force removing physical volume that is already part of a volume group | ||
community.general.lvm_pv: | ||
device: /dev/sdb | ||
force: true | ||
state: absent | ||
''' | ||
|
||
RETURN = r''' | ||
''' | ||
|
||
|
||
import os | ||
from ansible.module_utils.basic import AnsibleModule | ||
|
||
|
||
def get_pv_status(module, device): | ||
"""Check if the device is already a PV.""" | ||
cmd = ['pvs', '--noheadings', '--readonly', device] | ||
return module.run_command(cmd)[0] == 0 | ||
|
||
|
||
def get_pv_size(module, device): | ||
"""Get current PV size in bytes.""" | ||
cmd = ['pvs', '--noheadings', '--nosuffix', '--units', 'b', '-o', 'pv_size', device] | ||
rc, out, err = module.run_command(cmd, check_rc=True) | ||
return int(out.strip()) | ||
|
||
|
||
def rescan_device(module, device): | ||
"""Perform storage rescan for the device.""" | ||
# Extract the base device name (e.g., /dev/sdb -> sdb) | ||
base_device = os.path.basename(device) | ||
rescan_path = "/sys/block/{0}/device/rescan".format(base_device) | ||
|
||
if os.path.exists(rescan_path): | ||
try: | ||
with open(rescan_path, 'w') as f: | ||
f.write('1') | ||
return True | ||
except IOError as e: | ||
module.warn("Failed to rescan device {0}: {1}".format(device, str(e))) | ||
return False | ||
else: | ||
module.warn("Rescan path not found for device {0}".format(device)) | ||
return False | ||
|
||
|
||
def main(): | ||
module = AnsibleModule( | ||
argument_spec=dict( | ||
device=dict(type='path', required=True), | ||
state=dict(type='str', default='present', choices=['present', 'absent']), | ||
force=dict(type='bool', default=False), | ||
resize=dict(type='bool', default=False), | ||
), | ||
supports_check_mode=True, | ||
) | ||
|
||
device = module.params['device'] | ||
state = module.params['state'] | ||
force = module.params['force'] | ||
resize = module.params['resize'] | ||
changed = False | ||
actions = [] | ||
|
||
# Validate device existence for present state | ||
if state == 'present' and not os.path.exists(device): | ||
module.fail_json(msg="Device %s not found" % device) | ||
|
||
is_pv = get_pv_status(module, device) | ||
|
||
if state == 'present': | ||
# Create PV if needed | ||
if not is_pv: | ||
if module.check_mode: | ||
changed = True | ||
actions.append('would be created') | ||
else: | ||
cmd = ['pvcreate'] | ||
if force: | ||
cmd.append('-f') | ||
cmd.append(device) | ||
rc, out, err = module.run_command(cmd, check_rc=True) | ||
changed = True | ||
actions.append('created') | ||
is_pv = True | ||
|
||
# Handle resizing | ||
elif resize and is_pv: | ||
if module.check_mode: | ||
# In check mode, assume resize would change | ||
changed = True | ||
actions.append('would be resized') | ||
else: | ||
# Perform device rescan if each time | ||
if rescan_device(module, device): | ||
actions.append('rescanned') | ||
original_size = get_pv_size(module, device) | ||
rc, out, err = module.run_command(['pvresize', device], check_rc=True) | ||
new_size = get_pv_size(module, device) | ||
if new_size != original_size: | ||
changed = True | ||
actions.append('resized') | ||
|
||
elif state == 'absent': | ||
if is_pv: | ||
if module.check_mode: | ||
klention marked this conversation as resolved.
Show resolved
Hide resolved
|
||
changed = True | ||
actions.append('would be removed') | ||
else: | ||
cmd = ['pvremove', '-y'] | ||
if force: | ||
cmd.append('-ff') | ||
changed = True | ||
cmd.append(device) | ||
rc, out, err = module.run_command(cmd, check_rc=True) | ||
actions.append('removed') | ||
|
||
# Generate final message | ||
if actions: | ||
msg = "PV %s: %s" % (device, ', '.join(actions)) | ||
else: | ||
msg = "No changes needed for PV %s" % device | ||
module.exit_json(changed=changed, msg=msg) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright (c) Contributors to the Ansible project | ||
# Based on the integraton test for the lvg module | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
azp/posix/1 | ||
azp/posix/vm | ||
destructive | ||
needs/privileged | ||
skip/aix | ||
skip/freebsd | ||
skip/osx | ||
skip/macos |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# Based on the integraton test for the lvg module | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
dependencies: | ||
- setup_pkg_mgr | ||
- setup_remote_tmp_dir |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Detaching loop device | ||
ansible.builtin.command: losetup -d {{ loop_device.stdout }} | ||
|
||
- name: Removing loop device file | ||
ansible.builtin.file: | ||
path: "{{ remote_tmp_dir }}/test_lvm_pv.img" | ||
state: absent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Creating a 50MB file for loop device | ||
ansible.builtin.command: dd if=/dev/zero of={{ remote_tmp_dir }}/test_lvm_pv.img bs=1M count=50 | ||
args: | ||
creates: "{{ remote_tmp_dir }}/test_lvm_pv.img" | ||
|
||
- name: Creating loop device | ||
ansible.builtin.command: losetup -f | ||
register: loop_device | ||
|
||
- name: Associating loop device with file | ||
ansible.builtin.command: 'losetup {{ loop_device.stdout }} {{ remote_tmp_dir }}/test_lvm_pv.img' | ||
|
||
- name: Creating physical volume | ||
community.general.lvm_pv: | ||
device: "{{ loop_device.stdout }}" | ||
register: result | ||
|
||
- name: Checking physical volume size | ||
ansible.builtin.command: pvs --noheadings -o pv_size --units M {{ loop_device.stdout }} | ||
register: pv_size_output | ||
|
||
- name: Asserting physical volume was created | ||
ansible.builtin.assert: | ||
that: | ||
- result.changed == true | ||
- (pv_size_output.stdout | trim | regex_replace('M', '') | float) > 45 | ||
- (pv_size_output.stdout | trim | regex_replace('M', '') | float) < 55 | ||
- "'created' in result.msg" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
--- | ||
#################################################################### | ||
# WARNING: These are designed specifically for Ansible tests # | ||
# and should not be used as examples of how to write Ansible roles # | ||
#################################################################### | ||
|
||
# Copyright (c) Contributors to the Ansible project | ||
# Based on the integraton test for the lvg module | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Install required packages (Linux) | ||
when: ansible_system == 'Linux' | ||
ansible.builtin.package: | ||
name: lvm2 | ||
state: present | ||
|
||
- name: Testing lvg_pv module | ||
block: | ||
- import_tasks: creation.yml | ||
|
||
- import_tasks: resizing.yml | ||
|
||
- import_tasks: removal.yml | ||
|
||
always: | ||
- import_tasks: cleanup.yml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Removing physical volume | ||
community.general.lvm_pv: | ||
device: "{{ loop_device.stdout }}" | ||
state: absent | ||
register: remove_result | ||
|
||
- name: Asserting physical volume was removed | ||
ansible.builtin.assert: | ||
that: | ||
- remove_result.changed == true | ||
- "'removed' in remove_result.msg" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Growing the loop device file to 100MB | ||
ansible.builtin.shell: truncate -s 100M {{ remote_tmp_dir }}/test_lvm_pv.img | ||
|
||
- name: Refreshing the loop device | ||
ansible.builtin.shell: losetup -c {{ loop_device.stdout }} | ||
|
||
- name: Resizing the physical volume | ||
community.general.lvm_pv: | ||
device: "{{ loop_device.stdout }}" | ||
resize: true | ||
register: resize_result | ||
|
||
- name: Checking physical volume size | ||
ansible.builtin.command: pvs --noheadings -o pv_size --units M {{ loop_device.stdout }} | ||
register: pv_size_output | ||
|
||
- name: Asserting physical volume was resized | ||
ansible.builtin.assert: | ||
that: | ||
- resize_result.changed == true | ||
- (pv_size_output.stdout | trim | regex_replace('M', '') | float) > 95 | ||
- "'resized' in resize_result.msg" |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.