filesystem: better partition numbering
With a mix of preserved and non-preserved partitions, we must not allocate a partition number already in use.
This commit is contained in:
parent
855e5f2667
commit
5aa3bd3560
|
@ -689,10 +689,16 @@ class Partition(_Formattable):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _number(self):
|
def _number(self):
|
||||||
if self.preserve:
|
if self.number is not None:
|
||||||
return self.number
|
return self.number
|
||||||
else:
|
used_nums = {part.number for part in self.device._partitions
|
||||||
return self.device._partitions.index(self) + 1
|
if part.number is not None}
|
||||||
|
possible_nums = {i for i in range(1, len(self.device._partitions) + 1)}
|
||||||
|
unused_nums = sorted(list(possible_nums - used_nums))
|
||||||
|
for part in self.device._partitions:
|
||||||
|
if part.number is None:
|
||||||
|
part.number = unused_nums.pop(0)
|
||||||
|
return self.number
|
||||||
|
|
||||||
def _path(self):
|
def _path(self):
|
||||||
return partition_kname(self.device.path, self._number)
|
return partition_kname(self.device.path, self._number)
|
||||||
|
|
|
@ -177,7 +177,11 @@ def make_partition(model, device=None, *, preserve=False, size=None,
|
||||||
partition = Partition(m=model, device=device, size=size, offset=offset,
|
partition = Partition(m=model, device=device, size=size, offset=offset,
|
||||||
preserve=preserve, **kw)
|
preserve=preserve, **kw)
|
||||||
if preserve:
|
if preserve:
|
||||||
partition.number = len(device._partitions)
|
number = kw.get('number')
|
||||||
|
if number is not None:
|
||||||
|
partition.number = number
|
||||||
|
else:
|
||||||
|
partition.number = len(device._partitions)
|
||||||
model._actions.append(partition)
|
model._actions.append(partition)
|
||||||
return partition
|
return partition
|
||||||
|
|
||||||
|
@ -692,3 +696,56 @@ class TestAlignmentData(unittest.TestCase):
|
||||||
# information, so gaps produces numbers that are too small by 1MiB
|
# information, so gaps produces numbers that are too small by 1MiB
|
||||||
# for ptable != 'gpt'
|
# for ptable != 'gpt'
|
||||||
self.assertTrue(gaps_max <= align_max, f'ptable={ptable}')
|
self.assertTrue(gaps_max <= align_max, f'ptable={ptable}')
|
||||||
|
|
||||||
|
|
||||||
|
class TestPartitionNumbering(unittest.TestCase):
|
||||||
|
def test_basic(self):
|
||||||
|
m, d1 = make_model_and_disk(ptable='gpt')
|
||||||
|
p1 = make_partition(m, d1)
|
||||||
|
p2 = make_partition(m, d1)
|
||||||
|
p3 = make_partition(m, d1)
|
||||||
|
self.assertEqual(1, p1._number)
|
||||||
|
self.assertEqual(2, p2._number)
|
||||||
|
self.assertEqual(3, p3._number)
|
||||||
|
|
||||||
|
def test_p1_preserved(self):
|
||||||
|
m = make_model()
|
||||||
|
m.storage_version = 2
|
||||||
|
d1 = make_disk(m, ptable='gpt')
|
||||||
|
p1 = make_partition(m, d1, preserve=True, number=1)
|
||||||
|
p2 = make_partition(m, d1)
|
||||||
|
p3 = make_partition(m, d1)
|
||||||
|
self.assertEqual(1, p1._number)
|
||||||
|
self.assertEqual(True, p1.preserve)
|
||||||
|
self.assertEqual(2, p2._number)
|
||||||
|
self.assertEqual(False, p2.preserve)
|
||||||
|
self.assertEqual(3, p3._number)
|
||||||
|
self.assertEqual(False, p3.preserve)
|
||||||
|
|
||||||
|
def test_p2_preserved(self):
|
||||||
|
m = make_model()
|
||||||
|
m.storage_version = 2
|
||||||
|
d1 = make_disk(m, ptable='gpt')
|
||||||
|
p2 = make_partition(m, d1, preserve=True, number=2)
|
||||||
|
p1 = make_partition(m, d1)
|
||||||
|
p3 = make_partition(m, d1)
|
||||||
|
self.assertEqual(1, p1._number)
|
||||||
|
self.assertEqual(False, p1.preserve)
|
||||||
|
self.assertEqual(2, p2._number)
|
||||||
|
self.assertEqual(True, p2.preserve)
|
||||||
|
self.assertEqual(3, p3._number)
|
||||||
|
self.assertEqual(False, p3.preserve)
|
||||||
|
|
||||||
|
def test_trigger_part_num_allocation_for_disk(self):
|
||||||
|
m = make_model()
|
||||||
|
d1 = make_disk(m, ptable='gpt')
|
||||||
|
p1 = make_partition(m, d1)
|
||||||
|
p2 = make_partition(m, d1)
|
||||||
|
p3 = make_partition(m, d1)
|
||||||
|
self.assertIsNone(p1.number)
|
||||||
|
self.assertIsNone(p2.number)
|
||||||
|
self.assertIsNone(p3.number)
|
||||||
|
self.assertEqual(3, p3._number)
|
||||||
|
self.assertEqual(1, p1.number)
|
||||||
|
self.assertEqual(2, p2.number)
|
||||||
|
self.assertEqual(3, p3.number)
|
||||||
|
|
|
@ -299,36 +299,34 @@ class TestFlow(TestAPI):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_resp = await inst.post('/storage/v2/add_partition', data)
|
add_resp = await inst.post('/storage/v2/add_partition', data)
|
||||||
sda = first(add_resp['disks'], 'id', disk_id)
|
[sda] = add_resp['disks']
|
||||||
sda2 = first(sda['partitions'], 'number', 2)
|
[root] = match(sda['partitions'], mount='/')
|
||||||
self.assertEqual('ext3', sda2['format'])
|
self.assertEqual('ext3', root['format'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'disk_id': disk_id,
|
'disk_id': disk_id,
|
||||||
'partition': {
|
'partition': {
|
||||||
'number': 2,
|
'number': root['number'],
|
||||||
'format': 'ext4',
|
'format': 'ext4',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edit_resp = await inst.post('/storage/v2/edit_partition', data)
|
edit_resp = await inst.post('/storage/v2/edit_partition', data)
|
||||||
|
|
||||||
add_sda = first(add_resp['disks'], 'id', disk_id)
|
[add_sda] = add_resp['disks']
|
||||||
add_sda2 = first(add_sda['partitions'], 'number', 2)
|
[add_root] = match(add_sda['partitions'], mount='/')
|
||||||
|
|
||||||
edit_sda = first(edit_resp['disks'], 'id', disk_id)
|
[edit_sda] = edit_resp['disks']
|
||||||
edit_sda2 = first(edit_sda['partitions'], 'number', 2)
|
[edit_root] = match(edit_sda['partitions'], mount='/')
|
||||||
|
|
||||||
for key in 'size', 'number', 'mount', 'boot':
|
for key in 'size', 'number', 'mount', 'boot':
|
||||||
self.assertEqual(add_sda2[key], edit_sda2[key], key)
|
self.assertEqual(add_root[key], edit_root[key], key)
|
||||||
self.assertEqual('ext4', edit_sda2['format'])
|
self.assertEqual('ext4', edit_root['format'])
|
||||||
|
|
||||||
del_resp = await inst.post('/storage/v2/delete_partition', data)
|
del_resp = await inst.post('/storage/v2/delete_partition', data)
|
||||||
sda = first(del_resp['disks'], 'id', disk_id)
|
[sda] = del_resp['disks']
|
||||||
self.assertEqual(2, len(sda['partitions']))
|
[p, g] = sda['partitions']
|
||||||
|
self.assertEqual('Partition', p['$type'])
|
||||||
for type in 'Partition', 'Gap':
|
self.assertEqual('Gap', g['$type'])
|
||||||
pgs = [pg for pg in sda['partitions'] if pg['$type'] == type]
|
|
||||||
self.assertEqual(len(pgs), 1)
|
|
||||||
|
|
||||||
reset_resp = await inst.post('/storage/v2/reset')
|
reset_resp = await inst.post('/storage/v2/reset')
|
||||||
self.assertEqual(orig_resp, reset_resp)
|
self.assertEqual(orig_resp, reset_resp)
|
||||||
|
@ -374,8 +372,9 @@ class TestAdd(TestAPI):
|
||||||
|
|
||||||
await inst.post('/storage/v2/reset')
|
await inst.post('/storage/v2/reset')
|
||||||
|
|
||||||
# these manual steps are expected to be equivalent to just adding
|
# these manual steps are expected to be mostly equivalent to just
|
||||||
# the single partition and getting the automatic boot partition
|
# adding the single partition and getting the automatic boot
|
||||||
|
# partition
|
||||||
resp = await inst.post(
|
resp = await inst.post(
|
||||||
'/storage/v2/add_boot_partition', disk_id=disk_id)
|
'/storage/v2/add_boot_partition', disk_id=disk_id)
|
||||||
sda = first(resp['disks'], 'id', disk_id)
|
sda = first(resp['disks'], 'id', disk_id)
|
||||||
|
@ -390,6 +389,15 @@ class TestAdd(TestAPI):
|
||||||
}
|
}
|
||||||
manual_add = await inst.post('/storage/v2/add_partition', data)
|
manual_add = await inst.post('/storage/v2/add_partition', data)
|
||||||
|
|
||||||
|
# the only difference is the partition number assigned - when we
|
||||||
|
# explicitly add_boot_partition, that is the first partition
|
||||||
|
# created, versus when we add_partition and get a boot partition
|
||||||
|
# implicitly
|
||||||
|
for resp in single_add, manual_add:
|
||||||
|
for part in resp['disks'][0]['partitions']:
|
||||||
|
part.pop('number')
|
||||||
|
part.pop('path')
|
||||||
|
|
||||||
self.assertEqual(single_add, manual_add)
|
self.assertEqual(single_add, manual_add)
|
||||||
|
|
||||||
@timeout()
|
@timeout()
|
||||||
|
@ -978,10 +986,11 @@ class TestGap(TestAPI):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp = await inst.post('/storage/v2/add_partition', data)
|
resp = await inst.post('/storage/v2/add_partition', data)
|
||||||
sda = first(resp['disks'], 'id', 'disk-sda')
|
[sda] = resp['disks']
|
||||||
boot = first(sda['partitions'], 'number', 1)
|
[boot] = match(sda['partitions'], mount='/boot/efi')
|
||||||
gap = sda['partitions'][2]
|
[p1, p2, gap] = sda['partitions']
|
||||||
expected = (10 << 30) - boot['size'] - (4 << 30) - (2 << 20)
|
self.assertEqual('Gap', gap['$type'])
|
||||||
|
expected = (10 << 30) - p1['size'] - p2['size'] - (2 << 20)
|
||||||
self.assertEqual(expected, gap['size'])
|
self.assertEqual(expected, gap['size'])
|
||||||
|
|
||||||
async def SKIP_test_two_gaps(self):
|
async def SKIP_test_two_gaps(self):
|
||||||
|
@ -1040,10 +1049,10 @@ class TestRegression(TestAPI):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp = await inst.post('/storage/v2/add_partition', data)
|
resp = await inst.post('/storage/v2/add_partition', data)
|
||||||
sda = first(resp['disks'], 'id', disk_id)
|
[sda] = resp['disks']
|
||||||
sda2 = first(sda['partitions'], 'number', 2)
|
[part] = match(sda['partitions'], mount='/foo')
|
||||||
sda2.update({'format': 'ext3', 'mount': '/bar'})
|
part.update({'format': 'ext3', 'mount': '/bar'})
|
||||||
data['partition'] = sda2
|
data['partition'] = part
|
||||||
data.pop('gap')
|
data.pop('gap')
|
||||||
await inst.post('/storage/v2/edit_partition', data)
|
await inst.post('/storage/v2/edit_partition', data)
|
||||||
# should not throw an exception complaining about boot
|
# should not throw an exception complaining about boot
|
||||||
|
|
Loading…
Reference in New Issue