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: