Merge pull request #1250 from mwhudson/offset-pedantry

keep partition offsets up to date as they are deleted and resized
This commit is contained in:
Michael Hudson-Doyle 2022-04-12 10:54:24 +12:00 committed by GitHub
commit c384d5b617
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 4 deletions

View File

@ -23,6 +23,7 @@ from subiquity.models.filesystem import (
Disk, Disk,
LVM_CHUNK_SIZE, LVM_CHUNK_SIZE,
LVM_VolGroup, LVM_VolGroup,
Partition,
Raid, Raid,
) )
@ -165,3 +166,23 @@ def largest_gap_size(device):
if largest is not None: if largest is not None:
return largest.size return largest.size
return 0 return 0
def movable_trailing_partitions_and_gap_size(partition):
pgs = parts_and_gaps(partition.device)
part_idx = pgs.index(partition)
trailing_partitions = []
in_extended = partition.flag == "logical"
for pg in pgs[part_idx + 1:]:
if isinstance(pg, Partition):
if pg.preserve:
break
if in_extended and pg.flag != "logical":
break
trailing_partitions.append(pg)
else:
if pg.in_extended == in_extended:
return (trailing_partitions, pg.size)
else:
return (trailing_partitions, 0)
return (trailing_partitions, 0)

View File

@ -168,9 +168,15 @@ class FilesystemManipulator:
if partition is not None: if partition is not None:
if 'size' in spec: if 'size' in spec:
partition.size = align_up(spec['size']) trailing, gap_size = \
if gaps.largest_gap_size(disk) < 0: gaps.movable_trailing_partitions_and_gap_size(partition)
new_size = align_up(spec['size'])
size_change = new_size - partition.size
if size_change > gap_size:
raise Exception("partition size too large") raise Exception("partition size too large")
partition.size = new_size
for part in trailing:
part.offset += size_change
self.delete_filesystem(partition.fs()) self.delete_filesystem(partition.fs())
self.create_filesystem(partition, spec) self.create_filesystem(partition, spec)
return return

View File

@ -13,7 +13,9 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
import unittest import unittest
from unittest import mock
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
PartitionAlignmentData, PartitionAlignmentData,
@ -165,3 +167,153 @@ class TestDiskGaps(unittest.TestCase):
gaps.Gap(d, 35, 15, True), gaps.Gap(d, 35, 15, True),
gaps.Gap(d, 50, 50, False), gaps.Gap(d, 50, 50, False),
]) ])
class TestMovableTrailingPartitionsAndGapSize(unittest.TestCase):
def use_alignment_data(self, alignment_data):
m = mock.patch('subiquity.common.filesystem.gaps.parts_and_gaps')
p = m.start()
self.addCleanup(m.stop)
p.side_effect = partial(
gaps.find_disk_gaps_v2, info=alignment_data)
def test_no_next_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=10, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 ]#####
m, d = make_model_and_disk(size=100)
p = make_partition(m, d, offset=10, size=80)
self.assertEqual(
([], 0),
gaps.movable_trailing_partitions_and_gap_size(p))
def test_immediately_trailing_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=10, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 ] [ p2 ] #####
m, d = make_model_and_disk(size=100)
p1 = make_partition(m, d, offset=10, size=20)
p2 = make_partition(m, d, offset=50, size=20)
self.assertEqual(
([], 20),
gaps.movable_trailing_partitions_and_gap_size(p1))
self.assertEqual(
([], 20),
gaps.movable_trailing_partitions_and_gap_size(p2))
def test_one_trailing_movable_partition_and_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=10, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 ][ p2 ] #####
m, d = make_model_and_disk(size=100)
p1 = make_partition(m, d, offset=10, size=40)
p2 = make_partition(m, d, offset=50, size=10)
self.assertEqual(
([p2], 30),
gaps.movable_trailing_partitions_and_gap_size(p1))
def test_one_trailing_movable_partition_and_no_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=10, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 ][ p2 ]#####
m, d = make_model_and_disk(size=100)
p1 = make_partition(m, d, offset=10, size=40)
p2 = make_partition(m, d, offset=50, size=40)
self.assertEqual(
([p2], 0),
gaps.movable_trailing_partitions_and_gap_size(p1))
def test_full_extended_partition_then_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=1, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10, ebr_space=2))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 (extended) ] #####
# ######[ p5 (logical) ] #####
m, d = make_model_and_disk(size=100)
make_partition(m, d, offset=10, size=40, flag='extended')
p5 = make_partition(m, d, offset=12, size=38, flag='logical')
self.assertEqual(
([], 0),
gaps.movable_trailing_partitions_and_gap_size(p5))
def test_full_extended_partition_then_part(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=1, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10, ebr_space=2))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 (extended) ][ p2 ]#####
# ######[ p5 (logical) ] #####
m, d = make_model_and_disk(size=100)
make_partition(m, d, offset=10, size=40, flag='extended')
make_partition(m, d, offset=50, size=40)
p5 = make_partition(m, d, offset=12, size=38, flag='logical')
self.assertEqual(
([], 0),
gaps.movable_trailing_partitions_and_gap_size(p5))
def test_gap_in_extended_partition(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=1, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10, ebr_space=2))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 (extended) ] #####
# ######[ p5 (logical)] #####
m, d = make_model_and_disk(size=100)
make_partition(m, d, offset=10, size=40, flag='extended')
p5 = make_partition(m, d, offset=12, size=30, flag='logical')
self.assertEqual(
([], 6),
gaps.movable_trailing_partitions_and_gap_size(p5))
def test_trailing_logical_partition_then_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=1, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10, ebr_space=2))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 (extended) ]#####
# ######[ p5 (logical)] [ p6 (logical)] #####
m, d = make_model_and_disk(size=100)
make_partition(m, d, offset=10, size=80, flag='extended')
p5 = make_partition(m, d, offset=12, size=30, flag='logical')
p6 = make_partition(m, d, offset=44, size=30, flag='logical')
self.assertEqual(
([p6], 14),
gaps.movable_trailing_partitions_and_gap_size(p5))
def test_trailing_logical_partition_then_no_gap(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=1, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10, ebr_space=2))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 (extended) ]#####
# ######[ p5 (logical)] [ p6 (logical) ] #####
m, d = make_model_and_disk(size=100)
make_partition(m, d, offset=10, size=80, flag='extended')
p5 = make_partition(m, d, offset=12, size=30, flag='logical')
p6 = make_partition(m, d, offset=44, size=44, flag='logical')
self.assertEqual(
([p6], 0),
gaps.movable_trailing_partitions_and_gap_size(p5))
def test_trailing_preserved_partition(self):
self.use_alignment_data(PartitionAlignmentData(
part_align=10, min_gap_size=1, min_start_offset=10,
min_end_offset=10, primary_part_limit=10))
# 0----10---20---30---40---50---60---70---80---90---100
# #####[ p1 ][ p2 p ] #####
m, d = make_model_and_disk(size=100)
p1 = make_partition(m, d, offset=10, size=40)
make_partition(m, d, offset=50, size=20, preserve=True)
self.assertEqual(
([], 0),
gaps.movable_trailing_partitions_and_gap_size(p1))

View File

@ -1404,6 +1404,11 @@ class FilesystemModel(object):
def remove_partition(self, part): def remove_partition(self, part):
if part._fs or part._constructed_device: if part._fs or part._constructed_device:
raise Exception("can only remove empty partition") raise Exception("can only remove empty partition")
from subiquity.common.filesystem.gaps import (
movable_trailing_partitions_and_gap_size,
)
for p2 in movable_trailing_partitions_and_gap_size(part)[0]:
p2.offset -= part.size
self._remove(part) self._remove(part)
if len(part.device._partitions) == 0: if len(part.device._partitions) == 0:
part.device.ptable = None part.device.ptable = None

View File

@ -373,7 +373,6 @@ class PartitionStretchy(Stretchy):
self.model = parent.model self.model = parent.model
self.controller = parent.controller self.controller = parent.controller
self.parent = parent self.parent = parent
max_size = gaps.largest_gap_size(disk)
if partition is None and gap is None: if partition is None and gap is None:
raise Exception('bad PartitionStretchy - needs partition or gap') raise Exception('bad PartitionStretchy - needs partition or gap')
@ -395,7 +394,8 @@ class PartitionStretchy(Stretchy):
else: else:
label = _("Save") label = _("Save")
initial['size'] = humanize_size(self.partition.size) initial['size'] = humanize_size(self.partition.size)
max_size += self.partition.size max_size = partition.size + \
gaps.movable_trailing_partitions_and_gap_size(partition)[1]
if not boot.is_esp(partition): if not boot.is_esp(partition):
initial.update(initial_data_for_fs(self.partition.fs())) initial.update(initial_data_for_fs(self.partition.fs()))