Repository: ansible-collections/community.general

Issue:
Inconsistent pvs interface in lvol and lvg 
### Summary

`pvs` field of `lvg` is a list of strings. 
`pvs` field of `lvol` is a comma separated string.


### Issue Type

Bug Report

### Component Name

lvg
lvol

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.14.11]
  config file = /home/vscode/.ansible.cfg
  configured module search path = ['/workspaces/linux-2023-Q4/lib/ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/vscode/.local/lib/python3.11/site-packages/ansible
  ansible collection location = /workspaces/linux-2023-Q4/lib/ansible/collections:/usr/share/ansible/collections
  executable location = /home/vscode/.local/bin/ansible
  python version = 3.11.2 (main, Sep 12 2023, 00:00:00) [GCC 11.3.1 20221121 (Red Hat 11.3.1-4)] (/usr/bin/python3.11)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /home/vscode/.local/lib/python3.11/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 6.6.1  
```


### Configuration

_No response_

### OS / Environment

_No response_

### Steps to Reproduce

1. https://docs.ansible.com/ansible/latest/collections/community/general/lvg_module.html#parameter-pvs
3. https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-pvs

### Expected Results

both are a list of string, obviously

### Actual Results

confusion

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/lvg.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/lvg.py)
* [`plugins/modules/lvol.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/lvol.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @abulimov @jhoekx @unkaputtbar112 @zigaSRC
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/lvol.py b/plugins/modules/lvol.py
--- a/plugins/modules/lvol.py
+++ b/plugins/modules/lvol.py
@@ -75,9 +75,10 @@
     description:
     - The name of a snapshot volume to be configured. When creating a snapshot volume, the O(lv) parameter specifies the origin volume.
   pvs:
-    type: str
+    type: list
+    elements: str
     description:
-    - Comma separated list of physical volumes (e.g. /dev/sda,/dev/sdb).
+    - List of physical volumes (for example V(/dev/sda, /dev/sdb)).
   thinpool:
     type: str
     description:
@@ -110,7 +111,9 @@
     vg: firefly
     lv: test
     size: 512
-    pvs: /dev/sda,/dev/sdb
+    pvs:
+      - /dev/sda
+      - /dev/sdb
 
 - name: Create cache pool logical volume
   community.general.lvol:
@@ -299,7 +302,7 @@ def main():
             shrink=dict(type='bool', default=True),
             active=dict(type='bool', default=True),
             snapshot=dict(type='str'),
-            pvs=dict(type='str'),
+            pvs=dict(type='list', elements='str'),
             resizefs=dict(type='bool', default=False),
             thinpool=dict(type='str'),
         ),
@@ -340,7 +343,7 @@ def main():
     if pvs is None:
         pvs = ""
     else:
-        pvs = pvs.replace(",", " ")
+        pvs = " ".join(pvs)
 
     if opts is None:
         opts = ""


Test patch:
diff --git a/tests/integration/targets/lvol/aliases b/tests/integration/targets/lvol/aliases
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/aliases
@@ -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
+
+azp/posix/1
+azp/posix/vm
+destructive
+needs/privileged
+skip/aix
+skip/freebsd
+skip/osx
+skip/macos
diff --git a/tests/integration/targets/lvol/meta/main.yml b/tests/integration/targets/lvol/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/meta/main.yml
@@ -0,0 +1,8 @@
+---
+# 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
+
+dependencies:
+  - setup_pkg_mgr
+  - setup_remote_tmp_dir
diff --git a/tests/integration/targets/lvol/tasks/main.yml b/tests/integration/targets/lvol/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/tasks/main.yml
@@ -0,0 +1,24 @@
+---
+####################################################################
+# WARNING: These are designed specifically for Ansible tests       #
+# and should not be used as examples of how to write Ansible roles #
+####################################################################
+
+# 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: Install required packages (Linux)
+  package:
+    name: lvm2
+    state: present
+  when: ansible_system == 'Linux'
+
+- name: Test lvol module
+  block:
+    - import_tasks: setup.yml
+
+    - import_tasks: test_pvs.yml
+
+  always:
+    - import_tasks: teardown.yml
diff --git a/tests/integration/targets/lvol/tasks/setup.yml b/tests/integration/targets/lvol/tasks/setup.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/tasks/setup.yml
@@ -0,0 +1,57 @@
+---
+# 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: "Create files to use as a disk devices"
+  command: "dd if=/dev/zero of={{ remote_tmp_dir }}/img{{ item }} bs=1M count=36"
+  with_sequence: 'count=4'
+
+- name: "Show next free loop device"
+  command: "losetup -f"
+  register: loop_device1
+
+- name: "Create loop device for file"
+  command: "losetup -f {{ remote_tmp_dir }}/img1"
+
+- name: "Show next free loop device"
+  command: "losetup -f"
+  register: loop_device2
+
+- name: "Create loop device for file"
+  command: "losetup -f {{ remote_tmp_dir }}/img2"
+
+- name: "Show next free loop device"
+  command: "losetup -f"
+  register: loop_device3
+
+- name: "Create loop device for file"
+  command: "losetup -f {{ remote_tmp_dir }}/img3"
+
+- name: "Show next free loop device"
+  command: "losetup -f"
+  register: loop_device4
+
+- name: "Create loop device for file"
+  command: "losetup -f {{ remote_tmp_dir }}/img4"
+
+- name: "Set loop device names"
+  set_fact:
+    loop_device1: "{{ loop_device1.stdout }}"
+    loop_device2: "{{ loop_device2.stdout }}"
+    loop_device3: "{{ loop_device3.stdout }}"
+    loop_device4: "{{ loop_device4.stdout }}"
+
+- name: Create testvg1 volume group on disk devices
+  lvg:
+    vg: testvg1
+    pvs:
+      - "{{ loop_device1 }}"
+      - "{{ loop_device2 }}"
+
+- name: Create testvg2 volume group on disk devices
+  lvg:
+    vg: testvg2
+    pvs:
+      - "{{ loop_device3 }}"
+      - "{{ loop_device4 }}"
diff --git a/tests/integration/targets/lvol/tasks/teardown.yml b/tests/integration/targets/lvol/tasks/teardown.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/tasks/teardown.yml
@@ -0,0 +1,40 @@
+---
+# 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: Remove test volume groups
+  loop:
+    - testvg1
+    - testvg2
+  lvg:
+    vg: "{{ item }}"
+    force: true
+    state: absent
+
+- name: Remove LVM devices
+  loop:
+    - "{{ loop_device1 | default('') }}"
+    - "{{ loop_device2 | default('') }}"
+    - "{{ loop_device3 | default('') }}"
+    - "{{ loop_device4 | default('') }}"
+  when:
+    - item|length > 0
+  command: "lvmdevices --deldev {{ item }}"
+  ignore_errors: true
+
+- name: Detach loop devices
+  command: "losetup -d {{ item }}"
+  loop:
+    - "{{ loop_device1 | default('') }}"
+    - "{{ loop_device2 | default('') }}"
+    - "{{ loop_device3 | default('') }}"
+    - "{{ loop_device4 | default('') }}"
+  when:
+    - item != ''
+
+- name: Remove device files
+  file:
+    path: "{{ remote_tmp_dir }}/img{{ item }}"
+    state: absent
+  with_sequence: 'count=4'
diff --git a/tests/integration/targets/lvol/tasks/test_pvs.yml b/tests/integration/targets/lvol/tasks/test_pvs.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/lvol/tasks/test_pvs.yml
@@ -0,0 +1,64 @@
+---
+# 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: Create logical volume (testlv1) with pvs set as comma separated string
+  lvol:
+    vg: testvg1
+    lv: testlv1
+    size: 50%PVS
+    pvs: "{{ loop_device1 }},{{ loop_device2 }}"
+  register: css_pvs_create_testlv1_result
+
+- name: Assert logical volume (testlv1) created with pvs set as comma separated string
+  assert:
+    that:
+      - css_pvs_create_testlv1_result is success
+      - css_pvs_create_testlv1_result is changed
+
+- name: Create logical volume (testlv1) with pvs set as list
+  lvol:
+    vg: testvg1
+    lv: testlv1
+    size: 50%PVS
+    pvs:
+      - "{{ loop_device1 }}"
+      - "{{ loop_device2 }}"
+  register: list_pvs_create_testlv1_result
+
+- name: Assert logical volume (testlv1) creation idempotency with pvs set as list on second execution
+  assert:
+    that:
+      - list_pvs_create_testlv1_result is success
+      - list_pvs_create_testlv1_result is not changed
+
+- name: Create logical volume (testlv2) with pvs set as list
+  lvol:
+    vg: testvg2
+    lv: testlv2
+    size: 50%PVS
+    pvs:
+      - "{{ loop_device3 }}"
+      - "{{ loop_device4 }}"
+  register: list_pvs_create_testlv2_result
+
+- name: Assert logical volume (testlv2) created with pvs set as list
+  assert:
+    that:
+      - list_pvs_create_testlv2_result is success
+      - list_pvs_create_testlv2_result is changed
+
+- name: Create logical volume (testlv2) with pvs set as comma separated string
+  lvol:
+    vg: testvg2
+    lv: testlv2
+    size: 50%PVS
+    pvs: "{{ loop_device3 }},{{ loop_device4 }}"
+  register: css_pvs_create_testlv2_result
+
+- name: Assert logical volume (testlv2) creation idempotency with pvs set as comma separated string on second execution
+  assert:
+    that:
+      - css_pvs_create_testlv2_result is success
+      - css_pvs_create_testlv2_result is not changed