Skip to content

Commit 0605f94

Browse files
Merge pull request #1334 from vojtechtrefny/main_lvmpv-real-size
LVMPV format size
2 parents d714035 + 9261d00 commit 0605f94

File tree

6 files changed

+214
-30
lines changed

6 files changed

+214
-30
lines changed

blivet/devices/lvm.py

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,24 @@ def _remove(self, member):
343343
if lv.status and not status:
344344
lv.teardown()
345345

346+
# update LVMPV format size --> PV format has different size when in VG
347+
try:
348+
fmt._size = fmt._target_size = fmt._size_info.do_task()
349+
except errors.PhysicalVolumeError as e:
350+
log.warning("Failed to obtain current size for device %s: %s", fmt.device, e)
351+
346352
def _add(self, member):
347353
try:
348354
blockdev.lvm.vgextend(self.name, member.path)
349355
except blockdev.LVMError as err:
350356
raise errors.LVMError(err)
351357

358+
# update LVMPV format size --> PV format has different size when in VG
359+
try:
360+
member.format._size = member.format._target_size = member.format._size_info.do_task()
361+
except errors.PhysicalVolumeError as e:
362+
log.warning("Failed to obtain current size for device %s: %s", member.path, e)
363+
352364
def _add_log_vol(self, lv):
353365
""" Add an LV to this VG. """
354366
if lv in self._lvs:
@@ -522,40 +534,55 @@ def reserved_percent(self, value):
522534

523535
self._reserved_percent = value
524536

525-
def _get_pv_usable_space(self, pv):
537+
def _get_pv_metadata_space(self, pv):
538+
""" Returns how much space will be used by VG metadata in given PV
539+
This depends on type of the PV, PE size and PE start.
540+
"""
526541
if isinstance(pv, MDRaidArrayDevice):
527-
return self.align(pv.size - 2 * pv.format.pe_start)
542+
return 2 * pv.format.pe_start
528543
else:
529-
return self.align(pv.size - pv.format.pe_start)
544+
return pv.format.pe_start
545+
546+
def _get_pv_usable_space(self, pv):
547+
""" Return how much space can be actually used on given PV.
548+
This takes into account:
549+
- VG metadata that is/will be stored in this PV
550+
- the actual PV format size (which might differ from
551+
the underlying block device size)
552+
"""
553+
554+
if pv.format.exists and pv.format.size and self.exists:
555+
# PV format exists, we got its size and VG also exists
556+
# -> all metadata is already accounted in the PV format size
557+
return pv.format.size
558+
elif pv.format.exists and pv.format.size and not self.exists:
559+
# PV format exists, we got its size, but the VG doesn't exist
560+
# -> metadata size is not accounted in the PV format size
561+
return self.align(pv.format.size - self._get_pv_metadata_space(pv))
562+
else:
563+
# something else -> either the PV format is not yet created or
564+
# we for some reason failed to get size of the format, either way
565+
# lets use the underlying block device size and calculate the
566+
# metadata size ourselves
567+
return self.align(pv.size - self._get_pv_metadata_space(pv))
530568

531569
@property
532570
def lvm_metadata_space(self):
533-
""" The amount of the space LVM metadata cost us in this VG's PVs """
534-
# NOTE: we either specify data alignment in a PV or the default is used
535-
# which is both handled by pv.format.pe_start, but LVM takes into
536-
# account also the underlying block device which means that e.g.
537-
# for an MD RAID device, it tries to align everything also to chunk
538-
# size and alignment offset of such device which may result in up
539-
# to a twice as big non-data area
540-
# TODO: move this to either LVMPhysicalVolume's pe_start property once
541-
# formats know about their devices or to a new LVMPhysicalVolumeDevice
542-
# class once it exists
543-
diff = Size(0)
544-
for pv in self.pvs:
545-
diff += pv.size - self._get_pv_usable_space(pv)
546-
547-
return diff
571+
""" The amount of the space LVM metadata cost us in this VG's PVs
572+
Note: we either specify data alignment in a PV or the default is used
573+
which is both handled by pv.format.pe_start, but LVM takes into
574+
account also the underlying block device which means that e.g.
575+
for an MD RAID device, it tries to align everything also to chunk
576+
size and alignment offset of such device which may result in up
577+
to a twice as big non-data area
578+
"""
579+
return sum(self._get_pv_metadata_space(pv) for pv in self.pvs)
548580

549581
@property
550582
def size(self):
551583
""" The size of this VG """
552584
# TODO: just ask lvm if isModified returns False
553-
554-
# sum up the sizes of the PVs, subtract the unusable (meta data) space
555-
size = sum(pv.size for pv in self.pvs)
556-
size -= self.lvm_metadata_space
557-
558-
return size
585+
return sum(self._get_pv_usable_space(pv) for pv in self.pvs)
559586

560587
@property
561588
def extents(self):

blivet/formats/lvmpv.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ def __init__(self, **kwargs):
102102
# when set to True, blivet will try to resize the PV to fill all available space
103103
self._grow_to_fill = False
104104

105+
self._target_size = self._size
106+
105107
def __repr__(self):
106108
s = DeviceFormat.__repr__(self)
107109
s += (" vg_name = %(vg_name)s vg_uuid = %(vg_uuid)s"

blivet/populator/helpers/lvm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ def _get_kwargs(self):
120120
log.warning("PV %s has no pe_start", name)
121121
if pv_info.pv_free:
122122
kwargs["free"] = Size(pv_info.pv_free)
123+
if pv_info.pv_size:
124+
kwargs["size"] = Size(pv_info.pv_size)
123125

124126
return kwargs
125127

blivet/tasks/pvtask.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from ..errors import PhysicalVolumeError
2929
from ..size import Size, B
30+
from ..static_data import pvs_info
3031

3132
from . import availability
3233
from . import task
@@ -55,13 +56,12 @@ def do_task(self): # pylint: disable=arguments-differ
5556
:raises :class:`~.errors.PhysicalVolumeError`: if size cannot be obtained
5657
"""
5758

58-
try:
59-
pv_info = blockdev.lvm.pvinfo(self.pv.device)
60-
pv_size = pv_info.pv_size
61-
except blockdev.LVMError as e:
62-
raise PhysicalVolumeError(e)
59+
pvs_info.drop_cache()
60+
pv_info = pvs_info.cache.get(self.pv.device)
61+
if pv_info is None:
62+
raise PhysicalVolumeError("Failed to get PV info for %s" % self.pv.device)
6363

64-
return Size(pv_size)
64+
return Size(pv_info.pv_size)
6565

6666

6767
class PVResize(task.BasicApplication, dfresize.DFResizeTask):

tests/storage_tests/devices_test/lvm_test.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ def setUp(self):
2828
self.assertIsNone(disk.format.type)
2929
self.assertFalse(disk.children)
3030

31+
def _get_pv_size(self, pv):
32+
out = subprocess.check_output(["pvs", "-o", "pv_size", "--noheadings", "--nosuffix", "--units=b", pv])
33+
return blivet.size.Size(out.decode().strip())
34+
35+
def _get_vg_size(self, vg):
36+
out = subprocess.check_output(["vgs", "-o", "vg_size", "--noheadings", "--nosuffix", "--units=b", vg])
37+
return blivet.size.Size(out.decode().strip())
38+
39+
def _get_vg_free(self, vg):
40+
out = subprocess.check_output(["vgs", "-o", "vg_free", "--noheadings", "--nosuffix", "--units=b", vg])
41+
return blivet.size.Size(out.decode().strip())
42+
3143
def _clean_up(self):
3244
self.storage.reset()
3345
for disk in self.storage.disks:
@@ -77,6 +89,8 @@ def test_lvm_basic(self):
7789
self.assertIsInstance(pv, blivet.devices.PartitionDevice)
7890
self.assertIsNotNone(pv.format)
7991
self.assertEqual(pv.format.type, "lvmpv")
92+
pv_size = self._get_pv_size(pv.path)
93+
self.assertEqual(pv.format.size, pv_size)
8094

8195
vg = self.storage.devicetree.get_device_by_name(self.vgname)
8296
self.assertIsNotNone(vg)
@@ -87,6 +101,10 @@ def test_lvm_basic(self):
87101
self.assertEqual(pv.format.vg_uuid, vg.uuid)
88102
self.assertEqual(len(vg.parents), 1)
89103
self.assertEqual(vg.parents[0], pv)
104+
vg_size = self._get_vg_size(vg.name)
105+
self.assertEqual(vg_size, vg.size)
106+
vg_free = self._get_vg_free(vg.name)
107+
self.assertEqual(vg_free, vg.free_space)
90108

91109
lv = self.storage.devicetree.get_device_by_name("%s-blivetTestLV" % self.vgname)
92110
self.assertIsNotNone(lv)
@@ -139,6 +157,13 @@ def test_lvm_thin(self):
139157
self.storage.do_it()
140158
self.storage.reset()
141159

160+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
161+
self.assertIsNotNone(vg)
162+
vg_size = self._get_vg_size(vg.name)
163+
self.assertEqual(vg_size, vg.size)
164+
vg_free = self._get_vg_free(vg.name)
165+
self.assertEqual(vg_free, vg.free_space)
166+
142167
pool = self.storage.devicetree.get_device_by_name("%s-blivetTestPool" % self.vgname)
143168
self.assertIsNotNone(pool)
144169
self.assertTrue(pool.is_thin_pool)
@@ -185,6 +210,14 @@ def _test_lvm_raid(self, seg_type, raid_level, stripe_size=0):
185210
self.storage.do_it()
186211
self.storage.reset()
187212

213+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
214+
self.assertIsNotNone(vg)
215+
216+
vg_size = self._get_vg_size(vg.name)
217+
self.assertEqual(vg_size, vg.size)
218+
vg_free = self._get_vg_free(vg.name)
219+
self.assertEqual(vg_free, vg.free_space + vg.reserved_space)
220+
188221
raidlv = self.storage.devicetree.get_device_by_name("%s-blivetTestRAIDLV" % self.vgname)
189222
self.assertIsNotNone(raidlv)
190223
self.assertTrue(raidlv.is_raid_lv)
@@ -241,6 +274,13 @@ def test_lvm_cache(self):
241274
self.storage.do_it()
242275
self.storage.reset()
243276

277+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
278+
self.assertIsNotNone(vg)
279+
vg_size = self._get_vg_size(vg.name)
280+
self.assertEqual(vg_size, vg.size)
281+
vg_free = self._get_vg_free(vg.name)
282+
self.assertEqual(vg_free, vg.free_space)
283+
244284
cachedlv = self.storage.devicetree.get_device_by_name("%s-blivetTestCachedLV" % self.vgname)
245285
self.assertIsNotNone(cachedlv)
246286
self.assertTrue(cachedlv.cached)
@@ -280,6 +320,13 @@ def test_lvm_cache_attach(self):
280320
self.storage.do_it()
281321
self.storage.reset()
282322

323+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
324+
self.assertIsNotNone(vg)
325+
vg_size = self._get_vg_size(vg.name)
326+
self.assertEqual(vg_size, vg.size)
327+
vg_free = self._get_vg_free(vg.name)
328+
self.assertEqual(vg_free, vg.free_space)
329+
283330
cachedlv = self.storage.devicetree.get_device_by_name("%s-blivetTestCachedLV" % self.vgname)
284331
self.assertIsNotNone(cachedlv)
285332
cachepool = self.storage.devicetree.get_device_by_name("%s-blivetTestFastLV" % self.vgname)
@@ -334,6 +381,13 @@ def test_lvm_cache_create_and_attach(self):
334381
self.storage.do_it()
335382
self.storage.reset()
336383

384+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
385+
self.assertIsNotNone(vg)
386+
vg_size = self._get_vg_size(vg.name)
387+
self.assertEqual(vg_size, vg.size)
388+
vg_free = self._get_vg_free(vg.name)
389+
self.assertEqual(vg_free, vg.free_space)
390+
337391
cachedlv = self.storage.devicetree.get_device_by_name("%s-blivetTestCachedLV" % self.vgname)
338392
self.assertIsNotNone(cachedlv)
339393

@@ -349,6 +403,13 @@ def test_lvm_cache_create_and_attach(self):
349403
self.storage.do_it()
350404
self.storage.reset()
351405

406+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
407+
self.assertIsNotNone(vg)
408+
vg_size = self._get_vg_size(vg.name)
409+
self.assertEqual(vg_size, vg.size)
410+
vg_free = self._get_vg_free(vg.name)
411+
self.assertEqual(vg_free, vg.free_space)
412+
352413
cachedlv = self.storage.devicetree.get_device_by_name("%s-blivetTestCachedLV" % self.vgname)
353414
self.assertIsNotNone(cachedlv)
354415
self.assertTrue(cachedlv.cached)
@@ -378,6 +439,13 @@ def test_lvm_pvs_add_remove(self):
378439

379440
self.storage.do_it()
380441

442+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
443+
self.assertIsNotNone(vg)
444+
vg_size = self._get_vg_size(vg.name)
445+
self.assertEqual(vg_size, vg.size)
446+
vg_free = self._get_vg_free(vg.name)
447+
self.assertEqual(vg_free, vg.free_space)
448+
381449
# create a second PV
382450
disk2 = self.storage.devicetree.get_device_by_path(self.vdevs[1])
383451
self.assertIsNotNone(disk2)
@@ -392,6 +460,17 @@ def test_lvm_pvs_add_remove(self):
392460
self.storage.do_it()
393461
self.storage.reset()
394462

463+
pv1 = self.storage.devicetree.get_device_by_name(pv1.name)
464+
pv1_size = self._get_pv_size(pv1.path)
465+
self.assertEqual(pv1.format.size, pv1_size)
466+
467+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
468+
self.assertIsNotNone(vg)
469+
vg_size = self._get_vg_size(vg.name)
470+
self.assertEqual(vg_size, vg.size)
471+
vg_free = self._get_vg_free(vg.name)
472+
self.assertEqual(vg_free, vg.free_space)
473+
395474
# add the PV to the existing VG
396475
vg = self.storage.devicetree.get_device_by_name(self.vgname)
397476
pv2 = self.storage.devicetree.get_device_by_name(pv2.name)
@@ -400,6 +479,17 @@ def test_lvm_pvs_add_remove(self):
400479
self.storage.devicetree.actions.add(ac)
401480
self.storage.do_it()
402481

482+
pv2 = self.storage.devicetree.get_device_by_name(pv2.name)
483+
pv2_size = self._get_pv_size(pv2.path)
484+
self.assertEqual(pv2.format.size, pv2_size)
485+
486+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
487+
self.assertIsNotNone(vg)
488+
vg_size = self._get_vg_size(vg.name)
489+
self.assertEqual(vg_size, vg.size)
490+
vg_free = self._get_vg_free(vg.name)
491+
self.assertEqual(vg_free, vg.free_space)
492+
403493
self.assertEqual(pv2.format.vg_name, vg.name)
404494

405495
self.storage.reset()
@@ -421,6 +511,17 @@ def test_lvm_pvs_add_remove(self):
421511

422512
self.storage.do_it()
423513

514+
pv2 = self.storage.devicetree.get_device_by_name(pv2.name)
515+
pv2_size = self._get_pv_size(pv2.path)
516+
self.assertEqual(pv2.format.size, pv2_size)
517+
518+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
519+
self.assertIsNotNone(vg)
520+
vg_size = self._get_vg_size(vg.name)
521+
self.assertEqual(vg_size, vg.size)
522+
vg_free = self._get_vg_free(vg.name)
523+
self.assertEqual(vg_free, vg.free_space)
524+
424525
self.assertIsNone(pv1.format.type)
425526

426527
self.storage.reset()
@@ -430,3 +531,53 @@ def test_lvm_pvs_add_remove(self):
430531
self.assertIsNotNone(vg)
431532
self.assertEqual(len(vg.pvs), 1)
432533
self.assertEqual(vg.pvs[0].name, pv2.name)
534+
535+
def test_lvm_pv_size(self):
536+
disk = self.storage.devicetree.get_device_by_path(self.vdevs[0])
537+
self.assertIsNotNone(disk)
538+
self.storage.initialize_disk(disk)
539+
540+
pv = self.storage.new_partition(size=blivet.size.Size("100 MiB"), fmt_type="lvmpv",
541+
parents=[disk])
542+
self.storage.create_device(pv)
543+
544+
blivet.partitioning.do_partitioning(self.storage)
545+
546+
self.storage.do_it()
547+
self.storage.reset()
548+
549+
pv = self.storage.devicetree.get_device_by_name(pv.name)
550+
self.assertIsNotNone(pv)
551+
552+
pv.format.update_size_info()
553+
self.assertTrue(pv.format.resizable)
554+
555+
ac = blivet.deviceaction.ActionResizeFormat(pv, blivet.size.Size("50 MiB"))
556+
self.storage.devicetree.actions.add(ac)
557+
558+
self.storage.do_it()
559+
self.storage.reset()
560+
561+
pv = self.storage.devicetree.get_device_by_name(pv.name)
562+
self.assertIsNotNone(pv)
563+
self.assertEqual(pv.format.size, blivet.size.Size("50 MiB"))
564+
pv_size = self._get_pv_size(pv.path)
565+
self.assertEqual(pv_size, pv.format.size)
566+
567+
vg = self.storage.new_vg(name=self.vgname, parents=[pv])
568+
self.storage.create_device(vg)
569+
570+
self.storage.do_it()
571+
self.storage.reset()
572+
573+
pv = self.storage.devicetree.get_device_by_name(pv.name)
574+
self.assertIsNotNone(pv)
575+
pv_size = self._get_pv_size(pv.path)
576+
self.assertEqual(pv_size, pv.format.size)
577+
578+
vg = self.storage.devicetree.get_device_by_name(self.vgname)
579+
self.assertIsNotNone(vg)
580+
vg_size = self._get_vg_size(vg.name)
581+
self.assertEqual(vg_size, vg.size)
582+
vg_free = self._get_vg_free(vg.name)
583+
self.assertEqual(vg_free, vg.free_space)

0 commit comments

Comments
 (0)