Merge pull request #1445 from ogayot/LP1991413-LP1991929

Multiple fixes for size: -1 partitions
This commit is contained in:
Olivier Gayot 2022-11-02 09:39:30 +01:00 committed by GitHub
commit d41c3b9ec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 209 additions and 39 deletions

View File

@ -26,6 +26,7 @@ python3-dev
python3-distutils-extra
python3-flake8
python3-jsonschema
python3-more-itertools
python3-nose
python3-parameterized
python3-pip

View File

@ -26,6 +26,8 @@ import pathlib
import platform
import tempfile
import more_itertools
from curtin import storage_config
from curtin.block import partition_kname
from curtin.util import human2bytes
@ -552,7 +554,7 @@ class _Device(_Formattable, ABC):
@property
def available_for_partitions(self):
return align_down(self.size, 1 << 20) - GPT_OVERHEAD
raise NotImplementedError
def available(self):
# A _Device is available if:
@ -605,6 +607,12 @@ class Disk(_Device):
_info = attr.ib(default=None)
@property
def available_for_partitions(self):
margin_before = self.alignment_data().min_start_offset
margin_after = self.alignment_data().min_end_offset
return align_down(self.size, 1 << 20) - margin_before - margin_after
def alignment_data(self):
ptable = self.ptable_for_new_partition()
return self._m._partition_alignment_data[ptable]
@ -1167,6 +1175,48 @@ class FilesystemModel(object):
return candidates[0]
return None
def assign_omitted_offsets(self):
""" Assign offsets to partitions that do not already have one.
This method does nothing for storage version 1. """
if self.storage_version != 2:
return
for disk in self._all(type="disk"):
info = disk.alignment_data()
def au(v): # au == "align up"
r = v % info.part_align
if r:
return v + info.part_align - r
else:
return v
def ad(v): # ad == "align down"
return v - v % info.part_align
# Extended is considered a primary partition too.
primary_parts, logical_parts = map(list, more_itertools.partition(
is_logical_partition,
disk.partitions()))
prev_end = info.min_start_offset
for part in primary_parts:
if part.offset is None:
part.offset = au(prev_end)
prev_end = part.offset + part.size
if not logical_parts:
return
extended_part = next(
filter(lambda x: x.flag == "extended", disk.partitions()))
prev_end = extended_part.offset
for part in logical_parts:
if part.offset is None:
part.offset = au(prev_end + info.ebr_space)
prev_end = part.offset + part.size
def apply_autoinstall_config(self, ai_config):
disks = self.all_disks()
for action in ai_config:
@ -1192,6 +1242,9 @@ class FilesystemModel(object):
action['serial'] = disk.serial
self._actions = self._actions_from_config(
ai_config, self._probe_data['blockdev'], is_probe_data=False)
self.assign_omitted_offsets()
for p in self._all(type="partition") + self._all(type="lvm_partition"):
# NOTE For logical partitions (DOS), the parent is set to the disk,
# not the extended partition.
@ -1213,6 +1266,10 @@ class FilesystemModel(object):
"{} has negative size but is not final partition "
"of {}".format(p, parent))
# Exclude the current partition itself so that its
# incomplete size is not used as is.
filtered_parent._partitions.remove(p)
from subiquity.common.filesystem.gaps import (
largest_gap_size,
)

View File

@ -600,23 +600,20 @@ class TestAutoInstallConfig(unittest.TestCase):
def test_extended_partition_remaining_size(self):
model = make_model(bootloader=Bootloader.BIOS, storage_version=2)
disk = make_disk(model, serial='aaaa', size=dehumanize_size("100M"),
ptable='msdos')
ebr_space = disk.alignment_data().ebr_space
start_offset = disk.alignment_data().min_start_offset
make_disk(model, serial='aaaa', size=dehumanize_size("100M"))
fake_up_blockdata(model)
model.apply_autoinstall_config([
{
'type': 'disk',
'id': 'disk0',
'ptable': 'msdos',
},
{
'type': 'partition',
'id': 'part0',
'device': 'disk0',
'size': dehumanize_size('40M'),
'offset': start_offset,
},
{
'type': 'partition',
@ -624,8 +621,6 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': -1,
'flag': 'extended',
# p0 offset + p0 size
'offset': start_offset + dehumanize_size('40M'),
},
{
'type': 'partition',
@ -634,27 +629,32 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': dehumanize_size('10M'),
'flag': 'logical',
# p1 (extended) offset + ebr_space
'offset': start_offset + dehumanize_size('40M') + ebr_space,
},
])
extended = model._one(type="partition", id="part1")
self.assertEqual(
extended.size,
disk.available_for_partitions - dehumanize_size('40M'))
# Disk test.img: 100 MiB, 104857600 bytes, 204800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: dos
# Disk identifier: 0x2cbec179
#
# Device Boot Start End Sectors Size Id Type
# test.img1 2048 83967 81920 40M 83 Linux
# test.img2 83968 204799 120832 59M 5 Extended
# test.img5 86016 106495 20480 10M 83 Linux
self.assertEqual(extended.size, 120832 * 512)
def test_logical_partition_remaining_size(self):
model = make_model(bootloader=Bootloader.BIOS, storage_version=2)
disk = make_disk(model, serial='aaaa', size=dehumanize_size("100M"),
ptable='msdos')
ebr_space = disk.alignment_data().ebr_space
start_offset = disk.alignment_data().min_start_offset
make_disk(model, serial='aaaa', size=dehumanize_size("100M"))
fake_up_blockdata(model)
model.apply_autoinstall_config([
{
'type': 'disk',
'id': 'disk0',
'ptable': 'msdos',
},
{
'type': 'partition',
@ -662,7 +662,6 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': dehumanize_size('40M'),
'flag': 'extended',
'offset': start_offset,
},
{
'type': 'partition',
@ -671,35 +670,47 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': -1,
'flag': 'logical',
# p0 (extended) offset + ebr_space
'offset': start_offset + ebr_space,
},
])
disk = model._one(type="disk")
extended = model._one(type="partition", id="part0")
logical = model._one(type="partition", id="part5")
ebr_space = disk.alignment_data().ebr_space
# Disk test.img: 100 MiB, 104857600 bytes, 204800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: dos
# Disk identifier: 0x16011ba9
#
# Device Boot Start End Sectors Size Id Type
# test.img1 2048 83967 81920 40M 5 Extended
# test.img5 4096 83967 79872 39M 83 Linux
# At this point, there should be one large gap outside the extended
# partition and one smaller one inside the extended partition.
# partition and a smaller one inside the extended partition.
# Make sure our logical partition picks up the smaller one.
self.assertEqual(extended.offset, 2048 * 512)
self.assertEqual(logical.offset, 4096 * 512)
self.assertEqual(logical.size, extended.size - ebr_space)
def test_partition_remaining_size_in_extended_and_logical(self):
model = make_model(bootloader=Bootloader.BIOS, storage_version=2)
disk = make_disk(model, serial='aaaa', size=dehumanize_size("100M"),
ptable='msdos')
ebr_space = disk.alignment_data().ebr_space
start_offset = disk.alignment_data().min_start_offset
make_disk(model, serial='aaaa', size=dehumanize_size("100M"))
fake_up_blockdata(model)
model.apply_autoinstall_config([
{
'type': 'disk',
'id': 'disk0',
'ptable': 'msdos',
},
{
'type': 'partition',
'id': 'part0',
'device': 'disk0',
'size': dehumanize_size('40M'),
'offset': start_offset,
},
{
'type': 'partition',
@ -707,8 +718,6 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': -1,
'flag': 'extended',
# p0 offset + size of p0
'offset': start_offset + dehumanize_size('40M'),
},
{
'type': 'partition',
@ -717,8 +726,6 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': dehumanize_size('10M'),
'flag': 'logical',
# p1 offset + ebr_space
'offset': start_offset + dehumanize_size('40M') + ebr_space,
},
{
'type': 'partition',
@ -727,20 +734,125 @@ class TestAutoInstallConfig(unittest.TestCase):
'device': 'disk0',
'size': -1,
'flag': 'logical',
# p5 offset + p5 size + ebr_space
'offset': start_offset + dehumanize_size('40M') + ebr_space \
+ dehumanize_size('10M') + ebr_space
},
])
extended = model._one(type="partition", id="part1")
p5 = model._one(type="partition", id="part5")
p6 = model._one(type="partition", id="part6")
self.assertEqual(
extended.size,
disk.available_for_partitions - dehumanize_size('40M'))
self.assertEqual(
p6.size,
extended.size - p5.size - 2 * ebr_space)
# Disk test.img: 100 MiB, 104857600 bytes, 204800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: dos
# Disk identifier: 0x0b01e1ca
#
# Device Boot Start End Sectors Size Id Type
# test.img1 2048 83967 81920 40M 83 Linux
# test.img2 83968 204799 120832 59M 5 Extended
# test.img5 86016 106495 20480 10M 83 Linux
# test.img6 108544 204799 96256 47M 83 Linux
self.assertEqual(extended.offset, 83968 * 512)
self.assertEqual(extended.size, 120832 * 512)
self.assertEqual(p5.offset, 86016 * 512)
self.assertEqual(p6.offset, 108544 * 512)
self.assertEqual(p6.size, 96256 * 512)
def test_partition_remaining_size_in_extended_and_logical_multiple(self):
model = make_model(bootloader=Bootloader.BIOS, storage_version=2)
make_disk(model, serial='aaaa', size=dehumanize_size("20G"))
fake_up_blockdata(model)
model.apply_autoinstall_config([
{
'type': 'disk',
'id': 'disk0',
'ptable': 'msdos',
},
{
'type': 'partition',
'flag': 'boot',
'device': 'disk0',
'size': dehumanize_size('1G'),
'id': 'partition-boot',
},
{
'type': 'partition',
'device': 'disk0',
'size': dehumanize_size('6G'),
'id': 'partition-root',
},
{
'type': 'partition',
'device': 'disk0',
'size': dehumanize_size('100M'),
'id': 'partition-swap',
},
{
'type': 'partition',
'device': 'disk0',
'size': -1,
'flag': 'extended',
'id': 'partition-extended',
},
{
'type': 'partition',
'device': 'disk0',
'size': dehumanize_size('1G'),
'flag': 'logical',
'id': 'partition-tmp',
},
{
'type': 'partition',
'device': 'disk0',
'size': dehumanize_size('2G'),
'flag': 'logical',
'id': 'partition-var',
},
{
'type': 'partition',
'device': 'disk0',
'size': -1,
'flag': 'logical',
'id': 'partition-home',
}
])
p_boot = model._one(type="partition", id="partition-boot")
p_root = model._one(type="partition", id="partition-root")
p_swap = model._one(type="partition", id="partition-swap")
p_extended = model._one(type="partition", id="partition-extended")
p_tmp = model._one(type="partition", id="partition-tmp")
p_var = model._one(type="partition", id="partition-var")
p_home = model._one(type="partition", id="partition-home")
# Disk test.img: 20 GiB, 21474836480 bytes, 41943040 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: dos
# Disk identifier: 0xfbc457e5
#
# Device Boot Start End Sectors Size Id Type
# test.img1 2048 2099199 2097152 1G 83 Linux
# test.img2 2099200 14682111 12582912 6G 83 Linux
# test.img3 14682112 14886911 204800 100M 82 Linux swap ...
# test.img4 14886912 41943039 27056128 12,9G 5 Extended
# test.img5 14888960 16986111 2097152 1G 83 Linux
# test.img6 16988160 21182463 4194304 2G 83 Linux
# test.img7 21184512 41943039 20758528 9,9G 83 Linux
self.assertEqual(p_boot.offset, 2048 * 512)
self.assertEqual(p_boot.size, 2097152 * 512)
self.assertEqual(p_root.offset, 2099200 * 512)
self.assertEqual(p_root.size, 12582912 * 512)
self.assertEqual(p_swap.offset, 14682112 * 512)
self.assertEqual(p_swap.size, 204800 * 512)
self.assertEqual(p_extended.offset, 14886912 * 512)
self.assertEqual(p_extended.size, 27056128 * 512)
self.assertEqual(p_tmp.offset, 14888960 * 512)
self.assertEqual(p_tmp.size, 2097152 * 512)
self.assertEqual(p_var.offset, 16988160 * 512)
self.assertEqual(p_var.size, 4194304 * 512)
self.assertEqual(p_home.offset, 21184512 * 512)
self.assertEqual(p_home.size, 20758528 * 512)
def test_lv_percent(self):
model = make_model()