Skip to content

Commit d0e6be8

Browse files
authored
Master thin support size fix (#299)
* Fixed calculation of relative thinp sizes - percent specified 'size' of thin pool volume is now properly calculated from size of parent thinpool * Fixed size and percentage handling for thin pools - percentage size thin volume now correctly references its parent device for size calculation - percentage values are now accepted size for thin pool size
1 parent 12197f6 commit d0e6be8

File tree

4 files changed

+160
-31
lines changed

4 files changed

+160
-31
lines changed

library/blivet.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,19 @@ def _get_device_id(self):
679679
return "%s-%s" % (self._blivet_pool._device.name, self._volume['name'])
680680

681681
def _get_size(self):
682-
size = super(BlivetLVMVolume, self)._get_size()
682+
683+
spec = self._volume['size']
684+
if self._volume['thin'] and isinstance(spec, str) and '%' in spec:
685+
try:
686+
percentage = int(spec[:-1].strip())
687+
except ValueError:
688+
raise BlivetAnsibleError("invalid percentage '%s' size specified for thin volume '%s'" % (self._volume['size'], self._volume['name']))
689+
690+
parent = self._get_parent_thinpool_by_name(self._volume.get('thin_pool_name'))
691+
size = parent.size * (percentage / 100.0)
692+
else:
693+
size = super(BlivetLVMVolume, self)._get_size()
694+
683695
if isinstance(self._volume['size'], str) and '%' in self._volume['size']:
684696
size = self._blivet_pool._device.align(size, roundup=True)
685697
return size
@@ -1326,19 +1338,21 @@ def _manage_encryption(self, members):
13261338

13271339
def _manage_thin_pools(self, pool_device):
13281340

1329-
def get_vol_size(volume):
1330-
if isinstance(volume, str) and '%' in volume:
1341+
def str_to_size(spec, hundredpercent=None):
1342+
# Convert given 'spec' string to Size. When input string can be in percent
1343+
# (e.g. '50%'), use the optional parameter (type Size) as a reference
1344+
if isinstance(spec, str) and ('%' in spec):
13311345
try:
1332-
percentage = int(volume[:-1].strip())
1346+
percentage = int(spec[:-1].strip())
13331347
except ValueError:
1334-
raise BlivetAnsibleError("invalid percentage '%s' size specified for volume in pool '%s'" % (volume, pool_device.name))
1348+
raise BlivetAnsibleError("invalid percentage '%s' size specified in pool '%s'" % (spec, pool_device.name))
13351349

1336-
size = pool_device.size * (percentage / 100.0)
1350+
size = Size(hundredpercent * (percentage / 100.0))
13371351
else:
13381352
try:
1339-
size = Size(volume)
1353+
size = Size(spec)
13401354
except Exception:
1341-
raise BlivetAnsibleError("invalid size specification for volume in pool '%s'" % pool_device.name)
1355+
raise BlivetAnsibleError("invalid size specification '%s' in pool '%s'" % (spec, pool_device.name))
13421356

13431357
return size
13441358

@@ -1360,35 +1374,39 @@ def get_vol_size(volume):
13601374
auto_size_dev_count = 0
13611375
reserved_space = Size(0)
13621376

1377+
# Thin pool will take 20% of VG space as a safety spare. At least 1GiB, at most 100GiB
1378+
thin_meta_space = DEFAULT_THPOOL_RESERVE.percent * (pool_device.size * 0.01)
1379+
if thin_meta_space < DEFAULT_THPOOL_RESERVE.min:
1380+
thin_meta_space = DEFAULT_THPOOL_RESERVE.min
1381+
elif thin_meta_space > DEFAULT_THPOOL_RESERVE.max:
1382+
thin_meta_space = DEFAULT_THPOOL_RESERVE.max
1383+
1384+
available_space = pool_device.size - thin_meta_space
1385+
13631386
for volume in self._pool.get('volumes'):
13641387
if not volume['state']:
13651388
continue
13661389

13671390
if volume['thin']:
13681391
thin_name = volume.get('thin_pool_name')
13691392
thin_size = volume.get('thin_pool_size')
1393+
if thin_size is not None:
1394+
thin_size = str_to_size(thin_size, available_space)
1395+
13701396
if thin_name not in [t['name'] for t in thinlvs_to_create] + [d['name'] for d in existing_thinlvs]:
13711397
thinlvs_to_create.append({'name': thin_name, 'size': thin_size})
1372-
if thin_size is None:
1373-
auto_size_dev_count += 1
1374-
else:
1375-
reserved_space += get_vol_size(thin_size)
1398+
1399+
if thin_size is None:
1400+
auto_size_dev_count += 1
1401+
else:
1402+
reserved_space += thin_size
13761403
else:
13771404
# regular LV just take its size
13781405
vol_size = volume.get('size')
13791406
if vol_size is None:
13801407
auto_size_dev_count += 1
13811408
else:
1382-
reserved_space += get_vol_size(vol_size)
1383-
1384-
# Thin pool will take 20% of VG space as a safety spare. At least 1GiB, at most 100GiB
1385-
thin_meta_space = DEFAULT_THPOOL_RESERVE.percent * (pool_device.size * 0.01)
1386-
if thin_meta_space < DEFAULT_THPOOL_RESERVE.min:
1387-
thin_meta_space = DEFAULT_THPOOL_RESERVE.min
1388-
elif thin_meta_space > DEFAULT_THPOOL_RESERVE.max:
1389-
thin_meta_space = DEFAULT_THPOOL_RESERVE.max
1390-
1391-
available_space = pool_device.size - thin_meta_space
1409+
reserved_space += str_to_size(vol_size, pool_device.size)
13921410

13931411
if auto_size_dev_count > 0:
13941412
calculated_thinlv_size = available_space / auto_size_dev_count
@@ -1398,7 +1416,7 @@ def get_vol_size(volume):
13981416
if thinlv['size'] is None:
13991417
tlv_size = Size(calculated_thinlv_size)
14001418
else:
1401-
tlv_size = Size(thinlv['size'])
1419+
tlv_size = thinlv['size']
14021420

14031421
thinlv_params = dict(thin_pool=True, size=tlv_size, parents=[pool_device])
14041422

tests/test-verify-volume-size.yml

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
- debug:
2121
var: storage_test_expected_size
2222

23+
- name: Get the size of parent/pool device
24+
bsize:
25+
size: "{{ ansible_lvm.vgs[storage_test_pool.name].size_g + 'G' }}"
26+
register: storage_test_pool_size
27+
when: _storage_test_volume_present and storage_test_volume.type == "lvm"
28+
2329
- name: Convert percentage-based size to normal size as needed
2430
block:
2531
- debug:
@@ -28,11 +34,6 @@
2834
- debug:
2935
var: storage_test_blkinfo
3036

31-
- name: Get the size of parent/pool device
32-
bsize:
33-
size: "{{ ansible_lvm.vgs[storage_test_pool.name].size_g + 'G' }}"
34-
register: storage_test_pool_size
35-
3637
- debug:
3738
var: storage_test_pool_size
3839

@@ -41,6 +42,77 @@
4142
storage_test_expected_size: "{{ (storage_test_pool_size.bytes * ((storage_test_volume.size[:-1]|int)/100.0)) }}"
4243
when: _storage_test_volume_present and storage_test_volume.type == "lvm" and "%" in storage_test_volume.size|string
4344

45+
- name: Process thin pool sizes when applicable
46+
block:
47+
- name: Default thin pool reserved space values
48+
set_fact:
49+
_storage_test_default_thpool_reserve_percent: "20"
50+
51+
- name: Default minimal thin pool reserved space size
52+
bsize:
53+
size: "1G"
54+
register: _storage_test_default_thpool_reserve_min
55+
56+
- name: Default maximal thin pool reserved space size
57+
bsize:
58+
size: "100G"
59+
register: _storage_test_default_thpool_reserve_max
60+
61+
- name: Calculate maximum usable space in thin pool
62+
bsize:
63+
size: "{{ storage_test_pool_size.bytes * (1 - (_storage_test_default_thpool_reserve_percent|int)/100.0) }}"
64+
register: _storage_test_max_thin_pool_size
65+
66+
- name: Apply upper size limit to max usable thin pool space
67+
set_fact:
68+
_storage_test_max_thin_pool_size: _storage_test_default_thpool_reserve_max
69+
when: storage_test_pool_size.bytes - _storage_test_max_thin_pool_size.bytes > _storage_test_default_thpool_reserve_max.bytes
70+
71+
- name: Apply lower size limit to max usable thin pool space
72+
set_fact:
73+
_storage_test_max_thin_pool_size: _storage_test_default_thpool_reserve_min
74+
when: storage_test_pool_size.bytes - _storage_test_max_thin_pool_size.bytes < _storage_test_default_thpool_reserve_min.bytes
75+
76+
- debug:
77+
var: storage_test_volume.thin_pool_size
78+
79+
- debug:
80+
var: storage_test_volume.size
81+
82+
- name: Establish base value for expected thin pool size
83+
set_fact:
84+
storage_test_expected_thin_pool_size: "{{ storage_test_pool_size.bytes }}"
85+
when:
86+
- storage_test_volume.thin_pool_size is not none
87+
- true and '%' not in storage_test_volume.thin_pool_size # workaround - starting condition with '%' leads to a syntax error
88+
89+
- name: Calculate the expected size based on pool size and percentage value
90+
set_fact:
91+
storage_test_expected_thin_pool_size: "{{ (_storage_test_max_thin_pool_size.bytes * ((storage_test_volume.thin_pool_size[:-1]|int)/100.0)) }}"
92+
when:
93+
- storage_test_volume.thin_pool_size is not none
94+
- true and '%' in storage_test_volume.thin_pool_size|string # workaround - starting condition with '%' leads to a syntax error
95+
96+
- name: Establish base value for expected thin pool volume size
97+
set_fact:
98+
storage_test_expected_tp_volume_size: "{{ storage_test_expected_size }}"
99+
100+
- name: Calculate the expected thin pool volume size based on percentage value
101+
set_fact:
102+
storage_test_expected_tp_volume_size: "{{ ((storage_test_expected_thin_pool_size|int) * ((storage_test_volume.size[:-1]|int)/100.0)) }}"
103+
when:
104+
- true and '%' in storage_test_volume.size|string # workaround - starting condition with '%' leads to a syn:tax error
105+
106+
- name: Replace expected volume size with calculated value
107+
set_fact:
108+
storage_test_expected_size: "{{ storage_test_expected_tp_volume_size }}"
109+
when:
110+
- storage_test_volume.thin_pool_size is not none
111+
112+
when:
113+
- storage_test_volume.thin
114+
- _storage_test_volume_present
115+
44116
- debug:
45117
var: storage_test_actual_size
46118

tests/tests_create_thinp_then_remove.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
storage_use_partitions: true
77
mount_location1: '/opt/test1'
88
mount_location2: '/opt/test2'
9+
mount_location3: '/opt/test3'
910
volume1_size: '3g'
1011
volume2_size: '4g'
1112
fs_after: "{{ (ansible_distribution == 'RedHat' and ansible_distribution_major_version == '6') | ternary('ext4', 'xfs') }}"
@@ -18,7 +19,7 @@
1819
vars:
1920
max_return: 3
2021

21-
- name: Create a thinpool device
22+
- name: create a thinpool device
2223
include_role:
2324
name: linux-system-roles.storage
2425
vars:
@@ -123,3 +124,41 @@
123124
mount_point: "{{ mount_location1 }}"
124125

125126
- include_tasks: verify-role-results.yml
127+
128+
- name: create a thinpool device using percentages
129+
include_role:
130+
name: linux-system-roles.storage
131+
vars:
132+
storage_pools:
133+
- name: vg1
134+
disks: "{{ unused_disks }}"
135+
type: lvm
136+
state: present
137+
volumes:
138+
- name: lv1
139+
thin_pool_name: tpool1
140+
thin_pool_size: '100%'
141+
size: "100%"
142+
thin: true
143+
mount_point: "{{ mount_location1 }}"
144+
145+
- include_tasks: verify-role-results.yml
146+
147+
- name: Cleanup
148+
include_role:
149+
name: linux-system-roles.storage
150+
vars:
151+
storage_pools:
152+
- name: vg1
153+
disks: "{{ unused_disks }}"
154+
type: lvm
155+
state: absent
156+
volumes:
157+
- name: lv1
158+
thin_pool_name: tpool1
159+
thin_pool_size: '10g'
160+
size: "{{ volume1_size }}"
161+
thin: true
162+
mount_point: "{{ mount_location1 }}"
163+
164+
- include_tasks: verify-role-results.yml

tests/tests_resize.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
- name: Verify the output
163163
assert:
164164
that: "blivet_output.failed and
165-
blivet_output.msg|regex_search('invalid size.+for volume') and
165+
blivet_output.msg|regex_search('invalid.+size') and
166166
not blivet_output.changed"
167167
msg: "Unexpected behavior w/ invalid volume size"
168168

@@ -195,7 +195,7 @@
195195
- name: Verify the output
196196
assert:
197197
that: "blivet_output.failed and
198-
blivet_output.msg|regex_search('invalid size.+for volume') and
198+
blivet_output.msg|regex_search('invalid.+size') and
199199
not blivet_output.changed"
200200
msg: "Unexpected behavior w/ invalid volume size"
201201

0 commit comments

Comments
 (0)