Blob Blame History Raw
diff --git a/library/blivet.py b/library/blivet.py
index e927121..f59f821 100644
--- a/library/blivet.py
+++ b/library/blivet.py
@@ -130,6 +130,9 @@ if BLIVET_PACKAGE:
     set_up_logging()
     log = logging.getLogger(BLIVET_PACKAGE + ".ansible")
 
+
+MAX_TRIM_PERCENT = 2
+
 use_partitions = None  # create partitions on pool backing device disks?
 disklabel_type = None  # user-specified disklabel type
 safe_mode = None       # do not remove any existing devices or formatting
@@ -445,8 +448,16 @@ class BlivetVolume(BlivetBase):
             if not self._device.resizable:
                 return
 
-            if self._device.format.resizable:
-                self._device.format.update_size_info()
+            trim_percent = (1.0 - float(self._device.max_size / size))*100
+            log.debug("resize: size=%s->%s ; trim=%s", self._device.size, size, trim_percent)
+            if size > self._device.max_size and trim_percent <= MAX_TRIM_PERCENT:
+                log.info("adjusting %s resize target from %s to %s to fit in free space",
+                         self._volume['name'],
+                         size,
+                         self._device.max_size)
+                size = self._device.max_size
+                if size == self._device.size:
+                    return
 
             if not self._device.min_size <= size <= self._device.max_size:
                 raise BlivetAnsibleError("volume '%s' cannot be resized to '%s'" % (self._volume['name'], size))
@@ -610,10 +621,18 @@ class BlivetLVMVolume(BlivetVolume):
             raise BlivetAnsibleError("invalid size '%s' specified for volume '%s'" % (self._volume['size'], self._volume['name']))
 
         fmt = self._get_format()
+        trim_percent = (1.0 - float(parent.free_space / size))*100
+        log.debug("size: %s ; %s", size, trim_percent)
         if size > parent.free_space:
-            raise BlivetAnsibleError("specified size for volume '%s' exceeds available space in pool '%s' (%s)" % (size,
-                                                                                                                   parent.name,
-                                                                                                                   parent.free_space))
+            if trim_percent > MAX_TRIM_PERCENT:
+                raise BlivetAnsibleError("specified size for volume '%s' exceeds available space in pool '%s' (%s)"
+                                         % (size, parent.name, parent.free_space))
+            else:
+                log.info("adjusting %s size from %s to %s to fit in %s free space", self._volume['name'],
+                                                                                    size,
+                                                                                    parent.free_space,
+                                                                                    parent.name)
+                size = parent.free_space
 
         try:
             device = self._blivet.new_lv(name=self._volume['name'],
diff --git a/tests/tests_create_lv_size_equal_to_vg.yml b/tests/tests_create_lv_size_equal_to_vg.yml
new file mode 100644
index 0000000..21a5788
--- /dev/null
+++ b/tests/tests_create_lv_size_equal_to_vg.yml
@@ -0,0 +1,48 @@
+---
+- hosts: all
+  become: true
+  vars:
+    storage_safe_mode: false
+    mount_location: '/opt/test1'
+    volume_group_size: '10g'
+    lv_size: '10g'
+    unused_disk_subfact: '{{ ansible_devices[unused_disks[0]] }}'
+    disk_size: '{{ unused_disk_subfact.sectors|int *
+                   unused_disk_subfact.sectorsize|int }}'
+
+  tasks:
+    - include_role:
+        name: linux-system-roles.storage
+
+    - include_tasks: get_unused_disk.yml
+      vars:
+        min_size: "{{ volume_group_size }}"
+        max_return: 1
+
+    - name: Create one lv which size is equal to vg size
+      include_role:
+        name: linux-system-roles.storage
+      vars:
+          storage_pools:
+            - name: foo
+              disks: "{{ unused_disks }}"
+              volumes:
+                - name: test1
+                  size: "{{ lv_size }}"
+                  mount_point: "{{ mount_location }}"
+
+    - include_tasks: verify-role-results.yml
+
+    - name: Clean up
+      include_role:
+        name: linux-system-roles.storage
+      vars:
+          storage_pools:
+            - name: foo
+              disks: "{{ unused_disks }}"
+              state: "absent"
+              volumes:
+                - name: test1
+                  mount_point: "{{ mount_location }}"
+
+    - include_tasks: verify-role-results.yml
diff --git a/tests/tests_lvm_auto_size_cap.yml b/tests/tests_lvm_auto_size_cap.yml
new file mode 100644
index 0000000..fb17c23
--- /dev/null
+++ b/tests/tests_lvm_auto_size_cap.yml
@@ -0,0 +1,89 @@
+---
+- hosts: all
+  become: true
+
+  tasks:
+    - include_role:
+        name: linux-system-roles.storage
+
+    - include_tasks: get_unused_disk.yml
+      vars:
+        min_size: 10g
+        max_return: 1
+
+    - command: lsblk -b -l --noheadings -o NAME,SIZE
+      register: storage_test_lsblk
+
+    - set_fact:
+        test_disk_size: "{{ storage_test_lsblk.stdout_lines|map('regex_search', '^' + unused_disks[0] + '\\s+\\d+$')|select('string')|first|regex_replace('^\\w+\\s+', '') }}"
+
+    - package:
+        name: bc
+        state: installed
+
+    - command:
+        cmd: bc
+        stdin: "{{ test_disk_size }} *2"
+      register: doubled_size
+
+    - name: Test handling of too-large LVM volume size
+      block:
+        - name: Try to create a pool containing one volume twice the size of the backing disk
+          include_role:
+            name: linux-system-roles.storage
+          vars:
+            storage_pools:
+                - name: foo
+                  type: lvm
+                  disks: "{{ unused_disks }}"
+                  volumes:
+                    - name: test1
+                      size: "{{ doubled_size.stdout|trim }}"
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+              - blivet_output.failed and
+                blivet_output.msg|regex_search('specified size for volume.+exceeds available')
+            msg: "Role has not failed when it should have"
+
+    - name: Create a pool containing one volume the same size as the backing disk
+      include_role:
+        name: linux-system-roles.storage
+      vars:
+        storage_pools:
+            - name: foo
+              disks: "{{ unused_disks }}"
+              volumes:
+                - name: test1
+                  size: "{{ test_disk_size }}"
+
+    - include_tasks: verify-role-results.yml
+
+    - name: Repeat the previous invocation to verify idempotence
+      include_role:
+        name: linux-system-roles.storage
+      vars:
+        storage_pools:
+            - name: foo
+              type: lvm
+              disks: "{{ unused_disks }}"
+              volumes:
+                - name: test1
+                  size: "{{ test_disk_size }}"
+
+    - include_tasks: verify-role-results.yml
+
+    - name: Clean up
+      include_role:
+        name: linux-system-roles.storage
+      vars:
+        storage_pools:
+            - name: foo
+              disks: "{{ unused_disks }}"
+              state: absent
+              volumes: []
diff --git a/tests/tests_lvm_errors.yml b/tests/tests_lvm_errors.yml
index 37d41dc..e8dc4f4 100644
--- a/tests/tests_lvm_errors.yml
+++ b/tests/tests_lvm_errors.yml
@@ -11,8 +11,6 @@
       - '/non/existent/disk'
     invalid_size: 'xyz GiB'
     unused_disk_subfact: '{{ ansible_devices[unused_disks[0]] }}'
-    too_large_size: '{{ (unused_disk_subfact.sectors|int + 1) *
-                        unused_disk_subfact.sectorsize|int }}'
 
   tasks:
     - include_role:
@@ -86,39 +84,6 @@
               - ansible_failed_result.msg != 'UNREACH'
             msg: "Role has not failed when it should have"
 
-    # the following does not work properly
-    # - name: Verify the output
-    #   assert:
-    #     that: "{{ blivet_output.failed and
-    #               blivet_output.msg|regex_search('invalid size.+for volume') and
-    #               not blivet_output.changed }}"
-    #     msg: "Unexpected behavior w/ invalid volume size"
-
-    - name: Test for correct handling of too-large volume size.
-      block:
-        - name: Try to create LVM with a too-large volume size.
-          include_role:
-            name: linux-system-roles.storage
-          vars:
-            storage_pools:
-              - name: foo
-                disks: "{{ unused_disks }}"
-                volumes:
-                  - name: test1
-                    size: "{{ too_large_size }}"
-                    mount_point: "{{ mount_location1 }}"
-
-        - name: unreachable task
-          fail:
-            msg: UNREACH
-
-      rescue:
-        - name: Check that we failed in the role
-          assert:
-            that:
-              - ansible_failed_result.msg != 'UNREACH'
-            msg: "Role has not failed when it should have"
-
     # the following does not work properly
     # - name: Verify the output
     #   assert:
@@ -138,7 +103,7 @@
                 disks: "{{ unused_disks[0] }}"
                 volumes:
                   - name: test1
-                    size: "{{ too_large_size }}"
+                    size: "{{ volume_size }}"
                     mount_point: "{{ mount_location1 }}"
 
         - name: unreachable task
@@ -171,7 +136,7 @@
                 disks: []
                 volumes:
                   - name: test1
-                    size: "{{ too_large_size }}"
+                    size: "{{ volume1_size }}"
                     mount_point: "{{ mount_location1 }}"
 
         - name: unreachable task
diff --git a/tests/tests_misc.yml b/tests/tests_misc.yml
index a69ee98..3139bc7 100644
--- a/tests/tests_misc.yml
+++ b/tests/tests_misc.yml
@@ -7,7 +7,7 @@
     volume_group_size: '5g'
     volume1_size: '4g'
     unused_disk_subfact: '{{ ansible_devices[unused_disks[0]] }}'
-    too_large_size: '{{ (unused_disk_subfact.sectors|int + 1) *
+    too_large_size: '{{ (unused_disk_subfact.sectors|int * 1.2) *
                         unused_disk_subfact.sectorsize|int }}'
 
   tasks:
diff --git a/tests/tests_resize.yml b/tests/tests_resize.yml
index 9eeb2b9..209d129 100644
--- a/tests/tests_resize.yml
+++ b/tests/tests_resize.yml
@@ -9,7 +9,7 @@
     invalid_size1: 'xyz GiB'
     invalid_size2: 'none'
     unused_disk_subfact: '{{ ansible_devices[unused_disks[0]] }}'
-    too_large_size: '{{ (unused_disk_subfact.sectors|int + 1) *
+    too_large_size: '{{ unused_disk_subfact.sectors|int * 1.2 *
                         unused_disk_subfact.sectorsize|int }}'
     disk_size: '{{ unused_disk_subfact.sectors|int *
                    unused_disk_subfact.sectorsize|int }}'
@@ -122,23 +122,7 @@
                     size: "{{ disk_size }}"
                     mount_point: "{{ mount_location }}"
 
-        - name: Unreachable task
-          fail:
-            msg: UNREACH
-
-      rescue:
-        - name: Check that we failed in the role
-          assert:
-            that:
-              - ansible_failed_result.msg != 'UNREACH'
-            msg: "Role has not failed when it should have"
-
-        - name: Verify the output
-          assert:
-            that: "blivet_output.failed and
-                   blivet_output.msg|regex_search('volume.+cannot be resized to.+') and
-                   not blivet_output.changed"
-            msg: "Unexpected behavior w/ invalid volume size"
+    - include_tasks: verify-role-results.yml
 
     - name: Test for correct handling of invalid size specification
       block: