Merge pull request #1663 from dbungert/lunar-no-probert-during-partitioning
Lunar no probert during partitioning
This commit is contained in:
commit
3bddb3632b
|
@ -277,6 +277,10 @@ class API:
|
||||||
|
|
||||||
def POST(config: Payload[list]): ...
|
def POST(config: Payload[list]): ...
|
||||||
|
|
||||||
|
class dry_run_wait_probe:
|
||||||
|
"""This endpoint only works in dry-run mode."""
|
||||||
|
def POST() -> None: ...
|
||||||
|
|
||||||
class reset:
|
class reset:
|
||||||
def POST() -> StorageResponse: ...
|
def POST() -> StorageResponse: ...
|
||||||
|
|
||||||
|
@ -302,6 +306,16 @@ class API:
|
||||||
class reset:
|
class reset:
|
||||||
def POST() -> StorageResponseV2: ...
|
def POST() -> StorageResponseV2: ...
|
||||||
|
|
||||||
|
class ensure_transaction:
|
||||||
|
"""This call will ensure that a transaction is initiated.
|
||||||
|
During a transaction, storage probing runs are not permitted to
|
||||||
|
reset the partitioning configuration.
|
||||||
|
A transaction will also be initiated by any v2_storage POST
|
||||||
|
request that modifies the partitioning configuration (e.g.,
|
||||||
|
add_partition, edit_partition, ...) but ensure_transaction can
|
||||||
|
be called early if desired. """
|
||||||
|
def POST() -> None: ...
|
||||||
|
|
||||||
class reformat_disk:
|
class reformat_disk:
|
||||||
def POST(data: Payload[ReformatDisk]) \
|
def POST(data: Payload[ReformatDisk]) \
|
||||||
-> StorageResponseV2: ...
|
-> StorageResponseV2: ...
|
||||||
|
|
|
@ -22,7 +22,7 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import select
|
import select
|
||||||
import time
|
import time
|
||||||
from typing import Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from curtin.storage_config import ptable_uuid_to_flag_entry
|
from curtin.storage_config import ptable_uuid_to_flag_entry
|
||||||
|
|
||||||
|
@ -154,6 +154,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
self._role_to_device: Dict[str: _Device] = {}
|
self._role_to_device: Dict[str: _Device] = {}
|
||||||
self._device_to_structure: Dict[_Device: snapdapi.OnVolume] = {}
|
self._device_to_structure: Dict[_Device: snapdapi.OnVolume] = {}
|
||||||
self.use_tpm: bool = False
|
self.use_tpm: bool = False
|
||||||
|
self.locked_probe_data = False
|
||||||
|
# If probe data come in while we are doing partitioning, store it in
|
||||||
|
# this variable. It will be picked up on next reset will pick it up.
|
||||||
|
self.queued_probe_data: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
def is_core_boot_classic(self):
|
def is_core_boot_classic(self):
|
||||||
return self._system is not None
|
return self._system is not None
|
||||||
|
@ -644,9 +648,22 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
|
|
||||||
async def v2_reset_POST(self) -> StorageResponseV2:
|
async def v2_reset_POST(self) -> StorageResponseV2:
|
||||||
log.info("Resetting Filesystem model")
|
log.info("Resetting Filesystem model")
|
||||||
|
# From the API standpoint, it seems sound to set locked_probe_data back
|
||||||
|
# to False after a reset. But in practise, v2_reset_POST can be called
|
||||||
|
# during manual partitioning ; and we don't want to reenable automatic
|
||||||
|
# loading of probe data. Going forward, this could be controlled by an
|
||||||
|
# optional parameter maybe?
|
||||||
|
if self.queued_probe_data is not None:
|
||||||
|
log.debug("using newly obtained probe data")
|
||||||
|
self.model.load_probe_data(self.queued_probe_data)
|
||||||
|
self.queued_probe_data = None
|
||||||
|
else:
|
||||||
self.model.reset()
|
self.model.reset()
|
||||||
return await self.v2_GET()
|
return await self.v2_GET()
|
||||||
|
|
||||||
|
async def v2_ensure_transaction_POST(self) -> None:
|
||||||
|
self.locked_probe_data = True
|
||||||
|
|
||||||
async def v2_guided_GET(self, wait: bool = False) \
|
async def v2_guided_GET(self, wait: bool = False) \
|
||||||
-> GuidedStorageResponseV2:
|
-> GuidedStorageResponseV2:
|
||||||
"""Acquire a list of possible guided storage configuration scenarios.
|
"""Acquire a list of possible guided storage configuration scenarios.
|
||||||
|
@ -702,16 +719,19 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
async def v2_guided_POST(self, data: GuidedChoiceV2) \
|
async def v2_guided_POST(self, data: GuidedChoiceV2) \
|
||||||
-> GuidedStorageResponseV2:
|
-> GuidedStorageResponseV2:
|
||||||
log.debug(data)
|
log.debug(data)
|
||||||
|
self.locked_probe_data = True
|
||||||
self.guided(data)
|
self.guided(data)
|
||||||
return await self.v2_guided_GET()
|
return await self.v2_guided_GET()
|
||||||
|
|
||||||
async def v2_reformat_disk_POST(self, data: ReformatDisk) \
|
async def v2_reformat_disk_POST(self, data: ReformatDisk) \
|
||||||
-> StorageResponseV2:
|
-> StorageResponseV2:
|
||||||
|
self.locked_probe_data = True
|
||||||
self.reformat(self.model._one(id=data.disk_id), data.ptable)
|
self.reformat(self.model._one(id=data.disk_id), data.ptable)
|
||||||
return await self.v2_GET()
|
return await self.v2_GET()
|
||||||
|
|
||||||
async def v2_add_boot_partition_POST(self, disk_id: str) \
|
async def v2_add_boot_partition_POST(self, disk_id: str) \
|
||||||
-> StorageResponseV2:
|
-> StorageResponseV2:
|
||||||
|
self.locked_probe_data = True
|
||||||
disk = self.model._one(id=disk_id)
|
disk = self.model._one(id=disk_id)
|
||||||
if boot.is_boot_device(disk):
|
if boot.is_boot_device(disk):
|
||||||
raise ValueError('device already has bootloader partition')
|
raise ValueError('device already has bootloader partition')
|
||||||
|
@ -723,6 +743,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
async def v2_add_partition_POST(self, data: AddPartitionV2) \
|
async def v2_add_partition_POST(self, data: AddPartitionV2) \
|
||||||
-> StorageResponseV2:
|
-> StorageResponseV2:
|
||||||
log.debug(data)
|
log.debug(data)
|
||||||
|
self.locked_probe_data = True
|
||||||
if data.partition.boot is not None:
|
if data.partition.boot is not None:
|
||||||
raise ValueError('add_partition does not support changing boot')
|
raise ValueError('add_partition does not support changing boot')
|
||||||
disk = self.model._one(id=data.disk_id)
|
disk = self.model._one(id=data.disk_id)
|
||||||
|
@ -744,6 +765,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
async def v2_delete_partition_POST(self, data: ModifyPartitionV2) \
|
async def v2_delete_partition_POST(self, data: ModifyPartitionV2) \
|
||||||
-> StorageResponseV2:
|
-> StorageResponseV2:
|
||||||
log.debug(data)
|
log.debug(data)
|
||||||
|
self.locked_probe_data = True
|
||||||
disk = self.model._one(id=data.disk_id)
|
disk = self.model._one(id=data.disk_id)
|
||||||
partition = self.get_partition(disk, data.partition.number)
|
partition = self.get_partition(disk, data.partition.number)
|
||||||
self.delete_partition(partition)
|
self.delete_partition(partition)
|
||||||
|
@ -752,6 +774,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
async def v2_edit_partition_POST(self, data: ModifyPartitionV2) \
|
async def v2_edit_partition_POST(self, data: ModifyPartitionV2) \
|
||||||
-> StorageResponseV2:
|
-> StorageResponseV2:
|
||||||
log.debug(data)
|
log.debug(data)
|
||||||
|
self.locked_probe_data = True
|
||||||
disk = self.model._one(id=data.disk_id)
|
disk = self.model._one(id=data.disk_id)
|
||||||
partition = self.get_partition(disk, data.partition.number)
|
partition = self.get_partition(disk, data.partition.number)
|
||||||
if data.partition.size not in (None, partition.size) \
|
if data.partition.size not in (None, partition.size) \
|
||||||
|
@ -773,6 +796,15 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
self.partition_disk_handler(disk, spec, partition=partition)
|
self.partition_disk_handler(disk, spec, partition=partition)
|
||||||
return await self.v2_GET()
|
return await self.v2_GET()
|
||||||
|
|
||||||
|
async def dry_run_wait_probe_POST(self) -> None:
|
||||||
|
if not self.app.opts.dry_run:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# This will start the probe task if not yet started.
|
||||||
|
self.ensure_probing()
|
||||||
|
|
||||||
|
await self._probe_task.task
|
||||||
|
|
||||||
@with_context(name='probe_once', description='restricted={restricted}')
|
@with_context(name='probe_once', description='restricted={restricted}')
|
||||||
async def _probe_once(self, *, context, restricted):
|
async def _probe_once(self, *, context, restricted):
|
||||||
if restricted:
|
if restricted:
|
||||||
|
@ -797,7 +829,11 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
with open(fpath, 'w') as fp:
|
with open(fpath, 'w') as fp:
|
||||||
json.dump(storage, fp, indent=4)
|
json.dump(storage, fp, indent=4)
|
||||||
self.app.note_file_for_apport(key, fpath)
|
self.app.note_file_for_apport(key, fpath)
|
||||||
|
if not self.locked_probe_data:
|
||||||
|
self.queued_probe_data = None
|
||||||
self.model.load_probe_data(storage)
|
self.model.load_probe_data(storage)
|
||||||
|
else:
|
||||||
|
self.queued_probe_data = storage
|
||||||
|
|
||||||
@with_context()
|
@with_context()
|
||||||
async def _probe(self, *, context=None):
|
async def _probe(self, *, context=None):
|
||||||
|
@ -943,6 +979,14 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
loop.remove_reader(self._monitor.fileno())
|
loop.remove_reader(self._monitor.fileno())
|
||||||
|
|
||||||
|
def ensure_probing(self):
|
||||||
|
try:
|
||||||
|
self._probe_task.start_sync()
|
||||||
|
except TaskAlreadyRunningError:
|
||||||
|
log.debug('Skipping run of Probert - probe run already active')
|
||||||
|
else:
|
||||||
|
log.debug('Triggered Probert run on udev event')
|
||||||
|
|
||||||
def _udev_event(self):
|
def _udev_event(self):
|
||||||
cp = run_command(['udevadm', 'settle', '-t', '0'])
|
cp = run_command(['udevadm', 'settle', '-t', '0'])
|
||||||
if cp.returncode != 0:
|
if cp.returncode != 0:
|
||||||
|
@ -959,12 +1003,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
while select.select([self._monitor.fileno()], [], [], 0)[0]:
|
while select.select([self._monitor.fileno()], [], [], 0)[0]:
|
||||||
action, dev = self._monitor.receive_device()
|
action, dev = self._monitor.receive_device()
|
||||||
log.debug("_udev_event %s %s", action, dev)
|
log.debug("_udev_event %s %s", action, dev)
|
||||||
try:
|
self.ensure_probing()
|
||||||
self._probe_task.start_sync()
|
|
||||||
except TaskAlreadyRunningError:
|
|
||||||
log.debug('Skipping run of Probert - probe run already active')
|
|
||||||
else:
|
|
||||||
log.debug('Triggered Probert run on udev event')
|
|
||||||
|
|
||||||
def make_autoinstall(self):
|
def make_autoinstall(self):
|
||||||
rendered = self.model.render()
|
rendered = self.model.render()
|
||||||
|
|
|
@ -25,13 +25,20 @@ from subiquitycore.utils import matching_dicts
|
||||||
from subiquitycore.tests.util import random_string
|
from subiquitycore.tests.util import random_string
|
||||||
|
|
||||||
from subiquity.common.filesystem import gaps
|
from subiquity.common.filesystem import gaps
|
||||||
|
from subiquity.common.filesystem.actions import DeviceAction
|
||||||
from subiquity.common.types import (
|
from subiquity.common.types import (
|
||||||
|
AddPartitionV2,
|
||||||
Bootloader,
|
Bootloader,
|
||||||
|
Gap,
|
||||||
|
GapUsable,
|
||||||
GuidedChoiceV2,
|
GuidedChoiceV2,
|
||||||
GuidedStorageTargetReformat,
|
GuidedStorageTargetReformat,
|
||||||
GuidedStorageTargetResize,
|
GuidedStorageTargetResize,
|
||||||
GuidedStorageTargetUseGap,
|
GuidedStorageTargetUseGap,
|
||||||
|
ModifyPartitionV2,
|
||||||
|
Partition,
|
||||||
ProbeStatus,
|
ProbeStatus,
|
||||||
|
ReformatDisk,
|
||||||
SizingPolicy,
|
SizingPolicy,
|
||||||
)
|
)
|
||||||
from subiquity.models.filesystem import dehumanize_size
|
from subiquity.models.filesystem import dehumanize_size
|
||||||
|
@ -51,12 +58,16 @@ bootloaders_and_ptables = [(bl, pt)
|
||||||
|
|
||||||
|
|
||||||
class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
||||||
|
MOCK_PREFIX = 'subiquity.server.controllers.filesystem.'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = make_app()
|
self.app = make_app()
|
||||||
self.app.opts.bootloader = 'UEFI'
|
self.app.opts.bootloader = 'UEFI'
|
||||||
self.app.report_start_event = mock.Mock()
|
self.app.report_start_event = mock.Mock()
|
||||||
self.app.report_finish_event = mock.Mock()
|
self.app.report_finish_event = mock.Mock()
|
||||||
self.app.prober = mock.Mock()
|
self.app.prober = mock.Mock()
|
||||||
|
self.app.block_log_dir = '/inexistent'
|
||||||
|
self.app.note_file_for_apport = mock.Mock()
|
||||||
self.fsc = FilesystemController(app=self.app)
|
self.fsc = FilesystemController(app=self.app)
|
||||||
self.fsc._configured = True
|
self.fsc._configured = True
|
||||||
|
|
||||||
|
@ -78,6 +89,197 @@ class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
||||||
actual = self.app.prober.get_storage.call_args.args[0]
|
actual = self.app.prober.get_storage.call_args.args[0]
|
||||||
self.assertTrue({'defaults', 'os'} <= actual)
|
self.assertTrue({'defaults', 'os'} <= actual)
|
||||||
|
|
||||||
|
async def test_probe_once_fs_configured(self):
|
||||||
|
self.fsc._configured = True
|
||||||
|
self.fsc.queued_probe_data = None
|
||||||
|
with mock.patch.object(self.fsc.model, 'load_probe_data') as load:
|
||||||
|
await self.fsc._probe_once(restricted=True)
|
||||||
|
self.assertIsNone(self.fsc.queued_probe_data)
|
||||||
|
load.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch('subiquity.server.controllers.filesystem.open',
|
||||||
|
mock.mock_open())
|
||||||
|
async def test_probe_once_locked_probe_data(self):
|
||||||
|
self.fsc._configured = False
|
||||||
|
self.fsc.locked_probe_data = True
|
||||||
|
self.fsc.queued_probe_data = None
|
||||||
|
self.app.prober.get_storage = mock.Mock(return_value={})
|
||||||
|
with mock.patch.object(self.fsc.model, 'load_probe_data') as load:
|
||||||
|
await self.fsc._probe_once(restricted=True)
|
||||||
|
self.assertEqual(self.fsc.queued_probe_data, {})
|
||||||
|
load.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch('subiquity.server.controllers.filesystem.open',
|
||||||
|
mock.mock_open())
|
||||||
|
async def test_probe_once_unlocked_probe_data(self):
|
||||||
|
self.fsc._configured = False
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
self.fsc.queued_probe_data = None
|
||||||
|
self.app.prober.get_storage = mock.Mock(return_value={})
|
||||||
|
with mock.patch.object(self.fsc.model, 'load_probe_data') as load:
|
||||||
|
await self.fsc._probe_once(restricted=True)
|
||||||
|
self.assertIsNone(self.fsc.queued_probe_data, {})
|
||||||
|
load.assert_called_once_with({})
|
||||||
|
|
||||||
|
async def test_v2_reset_POST_no_queued_data(self):
|
||||||
|
self.fsc.queued_probe_data = None
|
||||||
|
with mock.patch.object(self.fsc.model, 'load_probe_data') as load:
|
||||||
|
await self.fsc.v2_reset_POST()
|
||||||
|
load.assert_not_called()
|
||||||
|
|
||||||
|
async def test_v2_reset_POST_queued_data(self):
|
||||||
|
self.fsc.queued_probe_data = {}
|
||||||
|
with mock.patch.object(self.fsc.model, 'load_probe_data') as load:
|
||||||
|
await self.fsc.v2_reset_POST()
|
||||||
|
load.assert_called_once_with({})
|
||||||
|
|
||||||
|
async def test_v2_ensure_transaction_POST(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
await self.fsc.v2_ensure_transaction_POST()
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
|
||||||
|
async def test_v2_reformat_disk_POST(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
with mock.patch.object(self.fsc, 'reformat') as reformat:
|
||||||
|
await self.fsc.v2_reformat_disk_POST(
|
||||||
|
ReformatDisk(disk_id='dev-sda'))
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
reformat.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch(MOCK_PREFIX + 'boot.is_boot_device',
|
||||||
|
mock.Mock(return_value=True))
|
||||||
|
async def test_v2_add_boot_partition_POST_existing_bootloader(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
with mock.patch.object(self.fsc, 'add_boot_disk') as add_boot_disk:
|
||||||
|
with self.assertRaisesRegex(ValueError,
|
||||||
|
r'already\ has\ bootloader'):
|
||||||
|
await self.fsc.v2_add_boot_partition_POST('dev-sda')
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
add_boot_disk.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch(MOCK_PREFIX + 'boot.is_boot_device',
|
||||||
|
mock.Mock(return_value=False))
|
||||||
|
@mock.patch(MOCK_PREFIX + 'DeviceAction.supported',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
async def test_v2_add_boot_partition_POST_not_supported(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
with mock.patch.object(self.fsc, 'add_boot_disk') as add_boot_disk:
|
||||||
|
with self.assertRaisesRegex(ValueError,
|
||||||
|
r'does\ not\ support\ boot'):
|
||||||
|
await self.fsc.v2_add_boot_partition_POST('dev-sda')
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
add_boot_disk.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch(MOCK_PREFIX + 'boot.is_boot_device',
|
||||||
|
mock.Mock(return_value=False))
|
||||||
|
@mock.patch(MOCK_PREFIX + 'DeviceAction.supported',
|
||||||
|
mock.Mock(return_value=[DeviceAction.TOGGLE_BOOT]))
|
||||||
|
async def test_v2_add_boot_partition_POST(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
with mock.patch.object(self.fsc, 'add_boot_disk') as add_boot_disk:
|
||||||
|
await self.fsc.v2_add_boot_partition_POST('dev-sda')
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
add_boot_disk.assert_called_once()
|
||||||
|
|
||||||
|
async def test_v2_add_partition_POST_changing_boot(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = AddPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(
|
||||||
|
format='ext4',
|
||||||
|
mount='/',
|
||||||
|
boot=True,
|
||||||
|
), gap=Gap(
|
||||||
|
offset=1 << 20,
|
||||||
|
size=1000 << 20,
|
||||||
|
usable=GapUsable.YES,
|
||||||
|
))
|
||||||
|
with mock.patch.object(self.fsc, 'create_partition') as create_part:
|
||||||
|
with self.assertRaisesRegex(ValueError,
|
||||||
|
r'does\ not\ support\ changing\ boot'):
|
||||||
|
await self.fsc.v2_add_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
create_part.assert_not_called()
|
||||||
|
|
||||||
|
async def test_v2_add_partition_POST_too_large(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = AddPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(
|
||||||
|
format='ext4',
|
||||||
|
mount='/',
|
||||||
|
size=2000 << 20,
|
||||||
|
), gap=Gap(
|
||||||
|
offset=1 << 20,
|
||||||
|
size=1000 << 20,
|
||||||
|
usable=GapUsable.YES,
|
||||||
|
))
|
||||||
|
with mock.patch.object(self.fsc, 'create_partition') as create_part:
|
||||||
|
with self.assertRaisesRegex(ValueError, r'too\ large'):
|
||||||
|
await self.fsc.v2_add_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
create_part.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch(MOCK_PREFIX + 'gaps.at_offset')
|
||||||
|
async def test_v2_add_partition_POST(self, at_offset):
|
||||||
|
at_offset.split = mock.Mock(return_value=[mock.Mock()])
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = AddPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(
|
||||||
|
format='ext4',
|
||||||
|
mount='/',
|
||||||
|
), gap=Gap(
|
||||||
|
offset=1 << 20,
|
||||||
|
size=1000 << 20,
|
||||||
|
usable=GapUsable.YES,
|
||||||
|
))
|
||||||
|
with mock.patch.object(self.fsc, 'create_partition') as create_part:
|
||||||
|
await self.fsc.v2_add_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
create_part.assert_called_once()
|
||||||
|
|
||||||
|
async def test_v2_delete_partition_POST(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = ModifyPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(number=1),
|
||||||
|
)
|
||||||
|
with mock.patch.object(self.fsc, 'delete_partition') as del_part:
|
||||||
|
with mock.patch.object(self.fsc, 'get_partition'):
|
||||||
|
await self.fsc.v2_delete_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
del_part.assert_called_once()
|
||||||
|
|
||||||
|
async def test_v2_edit_partition_POST_change_boot(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = ModifyPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(number=1, boot=True),
|
||||||
|
)
|
||||||
|
existing = Partition(number=1, size=1000 << 20, boot=False)
|
||||||
|
with mock.patch.object(self.fsc, 'partition_disk_handler') as handler:
|
||||||
|
with mock.patch.object(self.fsc, 'get_partition',
|
||||||
|
return_value=existing):
|
||||||
|
with self.assertRaisesRegex(ValueError, r'changing\ boot'):
|
||||||
|
await self.fsc.v2_edit_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
handler.assert_not_called()
|
||||||
|
|
||||||
|
async def test_v2_edit_partition_POST(self):
|
||||||
|
self.fsc.locked_probe_data = False
|
||||||
|
data = ModifyPartitionV2(
|
||||||
|
disk_id='dev-sda',
|
||||||
|
partition=Partition(number=1),
|
||||||
|
)
|
||||||
|
existing = Partition(number=1, size=1000 << 20, boot=False)
|
||||||
|
with mock.patch.object(self.fsc, 'partition_disk_handler') as handler:
|
||||||
|
with mock.patch.object(self.fsc, 'get_partition',
|
||||||
|
return_value=existing):
|
||||||
|
await self.fsc.v2_edit_partition_POST(data)
|
||||||
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
|
handler.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
class TestGuided(IsolatedAsyncioTestCase):
|
class TestGuided(IsolatedAsyncioTestCase):
|
||||||
boot_expectations = [
|
boot_expectations = [
|
||||||
|
|
|
@ -1502,6 +1502,49 @@ class TestRegression(TestAPI):
|
||||||
self.assertLessEqual(cur['end'] + (1 << 20), nxt['offset'],
|
self.assertLessEqual(cur['end'] + (1 << 20), nxt['offset'],
|
||||||
f'partition overlap {cur} {nxt}')
|
f'partition overlap {cur} {nxt}')
|
||||||
|
|
||||||
|
@timeout()
|
||||||
|
async def test_probert_result_during_partitioning(self):
|
||||||
|
'''If a probert run finished during manual partition, we used to
|
||||||
|
load the probing data, essentially discarding changes made by the user
|
||||||
|
so far. This test creates a new partition, simulates the end of a
|
||||||
|
probert run, and then tries to edit the previously created partition.
|
||||||
|
The edit operation would fail in earlier versions, because the new
|
||||||
|
partition would be discarded.
|
||||||
|
'''
|
||||||
|
cfg = 'examples/simple.json'
|
||||||
|
extra = ['--storage-version', '2']
|
||||||
|
async with start_server(cfg, extra_args=extra) as inst:
|
||||||
|
names = ['locale', 'keyboard', 'source', 'network', 'proxy',
|
||||||
|
'mirror']
|
||||||
|
await inst.post('/meta/mark_configured', endpoint_names=names)
|
||||||
|
resp = await inst.get('/storage/v2')
|
||||||
|
[d] = resp['disks']
|
||||||
|
[g] = d['partitions']
|
||||||
|
data = {
|
||||||
|
'disk_id': 'disk-sda',
|
||||||
|
'gap': g,
|
||||||
|
'partition': {
|
||||||
|
'size': -1,
|
||||||
|
'mount': '/',
|
||||||
|
'format': 'ext4',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_resp = await inst.post('/storage/v2/add_partition', data)
|
||||||
|
[sda] = add_resp['disks']
|
||||||
|
[root] = match(sda['partitions'], mount='/')
|
||||||
|
|
||||||
|
# Now let's make sure we get the results from a probert run to kick
|
||||||
|
# in.
|
||||||
|
await inst.post('/storage/dry_run_wait_probe')
|
||||||
|
data = {
|
||||||
|
'disk_id': 'disk-sda',
|
||||||
|
'partition': {
|
||||||
|
'number': root['number'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# We should be able to modify the created partition.
|
||||||
|
await inst.post('/storage/v2/edit_partition', data)
|
||||||
|
|
||||||
|
|
||||||
class TestCancel(TestAPI):
|
class TestCancel(TestAPI):
|
||||||
@timeout()
|
@timeout()
|
||||||
|
|
Loading…
Reference in New Issue