Repository: ansible-collections/community.general

Issue:
lvol - extending volumes with '+' only work for percentages
### Summary

If it's familiar it's because it should be. Please check: https://github.com/ansible/ansible/pull/54402

I'm not sure at what point the commits were lost, but the changes should be made in the collection as well. 

### Issue Type

Bug Report

### Component Name

lvol

### Ansible Version

```
Ansible 2.9.10
AWX 13.0.0
```


### Configuration

_No response_

### OS / Environment

_No response_

### Steps to Reproduce

- name: Extend logical volume and Grow the filesystem
    community.general.lvol:
      lv: "{{ lv_name }}"
      vg: "{{ vg_name }}"
      size: "+1024m"
      resizefs: yes

### Expected Results

The LV should be resized.

### Actual Results

_No response_

### Code of Conduct

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


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

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @abulimov @jhoekx
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
> If it's familiar it's because it should be. Please check: [ansible/ansible#54402](https://github.com/ansible/ansible/pull/54402)
> 
> I'm not sure at what point the commits were lost, but the changes should be made in the collection as well.

I'm not sure what you are trying to say. That PR you are referencing was not merged.
Oh, my mistake then. Should have checked more carefully. 

Just checked and it appears it was not migrated to the collection. Are there no intentions to support specifying size in such a format?
I don't know if anyone is working on that or interested enough to implement it / move the PR here. Maybe the maintainers (@abulimov and @jhoekx) know more.
Since the work has been already done I can create a new pull request and rebase the changes if that's all that is required for it to be merged in. Just let me know @abulimov and @jhoekx.
@zigaSRC if nobody replies in say a week, feel free to just create the PR.
@zigaSRC , please go ahead and create PR 

thanks

Resolving patch:
diff --git a/plugins/modules/system/lvol.py b/plugins/modules/system/lvol.py
--- a/plugins/modules/system/lvol.py
+++ b/plugins/modules/system/lvol.py
@@ -12,6 +12,8 @@
 author:
     - Jeroen Hoekx (@jhoekx)
     - Alexander Bulimov (@abulimov)
+    - Raoul Baudach (@unkaputtbar112)
+    - Ziga Kern (@zigaSRC)
 module: lvol
 short_description: Configure LVM logical volumes
 description:
@@ -33,7 +35,11 @@
       default in megabytes or optionally with one of [bBsSkKmMgGtTpPeE] units; or
       according to lvcreate(8) --extents as a percentage of [VG|PVS|FREE];
       Float values must begin with a digit.
-      Resizing using percentage values was not supported prior to 2.1.
+    - When resizing, apart from specifying an absolute size you may, according to
+      lvextend(8)|lvreduce(8) C(--size), specify the amount to extend the logical volume with
+      the prefix C(+) or the amount to reduce the logical volume by with prefix C(-).
+    - Resizing using C(+) or C(-) was not supported prior to community.general 3.0.0.
+    - Please note that when using C(+) or C(-), the module is B(not idempotent).
   state:
     type: str
     description:
@@ -136,6 +142,12 @@
     lv: test
     size: +100%FREE
 
+- name: Extend the logical volume by given space
+  community.general.lvol:
+    vg: firefly
+    lv: test
+    size: +512M
+
 - name: Extend the logical volume to take all remaining space of the PVs and resize the underlying filesystem
   community.general.lvol:
     vg: firefly
@@ -157,6 +169,13 @@
     size: 512
     force: yes
 
+- name: Reduce the logical volume by given space
+  community.general.lvol:
+    vg: firefly
+    lv: test
+    size: -512M
+    force: yes
+
 - name: Set the logical volume to 512m and do not try to shrink if size is lower than current one
   community.general.lvol:
     vg: firefly
@@ -209,7 +228,6 @@
 
 from ansible.module_utils.basic import AnsibleModule
 
-
 LVOL_ENV_VARS = dict(
     # make sure we use the C locale when running lvol-related commands
     LANG='C',
@@ -307,6 +325,7 @@ def main():
     thinpool = module.params['thinpool']
     size_opt = 'L'
     size_unit = 'm'
+    size_operator = None
     snapshot = module.params['snapshot']
     pvs = module.params['pvs']
 
@@ -325,7 +344,16 @@ def main():
         test_opt = ''
 
     if size:
-        # LVCREATE(8) -l --extents option with percentage
+        # LVEXTEND(8)/LVREDUCE(8) -l, -L options: Check for relative value for resizing
+        if size.startswith('+'):
+            size_operator = '+'
+            size = size[1:]
+        elif size.startswith('-'):
+            size_operator = '-'
+            size = size[1:]
+        # LVCREATE(8) does not support [+-]
+
+        # LVCREATE(8)/LVEXTEND(8)/LVREDUCE(8) -l --extents option with percentage
         if '%' in size:
             size_parts = size.split('%', 1)
             size_percent = int(size_parts[0])
@@ -339,10 +367,10 @@ def main():
             size_opt = 'l'
             size_unit = ''
 
+        # LVCREATE(8)/LVEXTEND(8)/LVREDUCE(8) -L --size option unit
         if '%' not in size:
-            # LVCREATE(8) -L --size option unit
             if size[-1].lower() in 'bskmgtpe':
-                size_unit = size[-1].lower()
+                size_unit = size[-1]
                 size = size[0:-1]
 
             try:
@@ -398,7 +426,6 @@ def main():
         else:
             module.fail_json(msg="Snapshot origin LV %s does not exist in volume group %s." % (lv, vg))
         check_lv = snapshot
-
     elif thinpool:
         if lv:
             # Check thin volume pre-conditions
@@ -423,6 +450,8 @@ def main():
     msg = ''
     if this_lv is None:
         if state == 'present':
+            if size_operator is not None:
+                module.fail_json(msg="Bad size specification of '%s%s' for creating LV" % (size_operator, size))
             # Require size argument except for snapshot of thin volumes
             if (lv or thinpool) and not size:
                 for test_lv in lvs:
@@ -476,13 +505,19 @@ def main():
             else:  # size_whole == 'FREE':
                 size_requested = size_percent * this_vg['free'] / 100
 
-            # Round down to the next lowest whole physical extent
-            size_requested -= (size_requested % this_vg['ext_size'])
-
-            if '+' in size:
+            # from LVEXTEND(8) - The resulting value is rounded upward.
+            # from LVREDUCE(8) - The resulting value for the substraction is rounded downward, for the absolute size it is rounded upward.
+            if size_operator == '+':
                 size_requested += this_lv['size']
+                size_requested += this_vg['ext_size'] - (size_requested % this_vg['ext_size'])
+            elif size_operator == '-':
+                size_requested = this_lv['size'] - size_requested
+                size_requested -= (size_requested % this_vg['ext_size'])
+            else:
+                size_requested += this_vg['ext_size'] - (size_requested % this_vg['ext_size'])
+
             if this_lv['size'] < size_requested:
-                if (size_free > 0) and (('+' not in size) or (size_free >= (size_requested - this_lv['size']))):
+                if (size_free > 0) and (size_free >= (size_requested - this_lv['size'])):
                     tool = module.get_bin_path("lvextend", required=True)
                 else:
                     module.fail_json(
@@ -490,7 +525,7 @@ def main():
                             (this_lv['name'], (size_requested - this_lv['size']), unit, size_free, unit)
                     )
             elif shrink and this_lv['size'] > size_requested + this_vg['ext_size']:  # more than an extent too large
-                if size_requested == 0:
+                if size_requested < 1:
                     module.fail_json(msg="Sorry, no shrinking of %s to 0 permitted." % (this_lv['name']))
                 elif not force:
                     module.fail_json(msg="Sorry, no shrinking of %s without force=yes" % (this_lv['name']))
@@ -501,7 +536,10 @@ def main():
             if tool:
                 if resizefs:
                     tool = '%s %s' % (tool, '--resizefs')
-                cmd = "%s %s -%s %s%s %s/%s %s" % (tool, test_opt, size_opt, size, size_unit, vg, this_lv['name'], pvs)
+                if size_operator:
+                    cmd = "%s %s -%s %s%s%s %s/%s %s" % (tool, test_opt, size_opt, size_operator, size, size_unit, vg, this_lv['name'], pvs)
+                else:
+                    cmd = "%s %s -%s %s%s %s/%s %s" % (tool, test_opt, size_opt, size, size_unit, vg, this_lv['name'], pvs)
                 rc, out, err = module.run_command(cmd)
                 if "Reached maximum COW size" in out:
                     module.fail_json(msg="Unable to resize %s to %s%s" % (lv, size, size_unit), rc=rc, err=err, out=out)
@@ -518,9 +556,9 @@ def main():
         else:
             # resize LV based on absolute values
             tool = None
-            if float(size) > this_lv['size']:
+            if float(size) > this_lv['size'] or size_operator == '+':
                 tool = module.get_bin_path("lvextend", required=True)
-            elif shrink and float(size) < this_lv['size']:
+            elif shrink and float(size) < this_lv['size'] or size_operator == '-':
                 if float(size) == 0:
                     module.fail_json(msg="Sorry, no shrinking of %s to 0 permitted." % (this_lv['name']))
                 if not force:
@@ -532,7 +570,10 @@ def main():
             if tool:
                 if resizefs:
                     tool = '%s %s' % (tool, '--resizefs')
-                cmd = "%s %s -%s %s%s %s/%s %s" % (tool, test_opt, size_opt, size, size_unit, vg, this_lv['name'], pvs)
+                if size_operator:
+                    cmd = "%s %s -%s %s%s%s %s/%s %s" % (tool, test_opt, size_opt, size_operator, size, size_unit, vg, this_lv['name'], pvs)
+                else:
+                    cmd = "%s %s -%s %s%s %s/%s %s" % (tool, test_opt, size_opt, size, size_unit, vg, this_lv['name'], pvs)
                 rc, out, err = module.run_command(cmd)
                 if "Reached maximum COW size" in out:
                     module.fail_json(msg="Unable to resize %s to %s%s" % (lv, size, size_unit), rc=rc, err=err, out=out)


Test patch: