storage: when enabled, raise NRE when from POST v2/add_boot_partition
When the request to v2/add_boot_partition can not be processed by the server because it is not valid, raise a FilesystemValueError exception. Depending on configuration, it will be treated as a normal ValueError or as a NonReportableError. Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
1cca91f9fe
commit
d04c01a068
|
@ -23,6 +23,7 @@ import sys
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
from subiquity.server.controllers.filesystem import set_user_error_reportable
|
||||||
from subiquitycore.log import setup_logger
|
from subiquitycore.log import setup_logger
|
||||||
|
|
||||||
from .common import LOGDIR, setup_environment
|
from .common import LOGDIR, setup_environment
|
||||||
|
@ -137,6 +138,16 @@ def make_server_args_parser():
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument("--storage-version", action="store", type=int)
|
parser.add_argument("--storage-version", action="store", type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-report-storage-user-errors",
|
||||||
|
action="store_true",
|
||||||
|
help="""\
|
||||||
|
When False (the default), exceptions raised by /storage/v2/ POST handlers when
|
||||||
|
the client sends bad information will cause the creation of a crash report and
|
||||||
|
a HTTP 500 error to be returned.
|
||||||
|
When True, no crash report will be created and a HTTP 422 error will be
|
||||||
|
returned. """,
|
||||||
|
)
|
||||||
parser.add_argument("--use-os-prober", action="store_true", default=False)
|
parser.add_argument("--use-os-prober", action="store_true", default=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--postinst-hooks-dir", default="/etc/subiquity/postinst.d", type=pathlib.Path
|
"--postinst-hooks-dir", default="/etc/subiquity/postinst.d", type=pathlib.Path
|
||||||
|
@ -158,6 +169,7 @@ def main():
|
||||||
opts.storage_version = int(
|
opts.storage_version = int(
|
||||||
opts.kernel_cmdline.get("subiquity-storage-version", 1)
|
opts.kernel_cmdline.get("subiquity-storage-version", 1)
|
||||||
)
|
)
|
||||||
|
set_user_error_reportable(not opts.no_report_storage_user_errors)
|
||||||
logdir = LOGDIR
|
logdir = LOGDIR
|
||||||
if opts.dry_run:
|
if opts.dry_run:
|
||||||
if opts.dry_run_config:
|
if opts.dry_run_config:
|
||||||
|
|
|
@ -22,7 +22,7 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union
|
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import pyudev
|
import pyudev
|
||||||
|
@ -79,6 +79,7 @@ from subiquity.server.autoinstall import AutoinstallError
|
||||||
from subiquity.server.controller import SubiquityController
|
from subiquity.server.controller import SubiquityController
|
||||||
from subiquity.server.controllers.source import SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
|
from subiquity.server.controllers.source import SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
|
||||||
from subiquity.server.mounter import Mounter
|
from subiquity.server.mounter import Mounter
|
||||||
|
from subiquity.server.nonreportable import NonReportableException
|
||||||
from subiquity.server.snapdapi import (
|
from subiquity.server.snapdapi import (
|
||||||
StorageEncryptionSupport,
|
StorageEncryptionSupport,
|
||||||
StorageSafety,
|
StorageSafety,
|
||||||
|
@ -122,6 +123,23 @@ class NoSnapdSystemsOnSource(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NonReportableSVE(NonReportableException):
|
||||||
|
"""Non reportable storage value error"""
|
||||||
|
|
||||||
|
|
||||||
|
class ReportableSVE(ValueError):
|
||||||
|
"""Reportable storage value error"""
|
||||||
|
|
||||||
|
|
||||||
|
# Depending on config, we will let the SVE fail on the server side
|
||||||
|
StorageValueError: Type[NonReportableSVE | ReportableSVE] = ReportableSVE
|
||||||
|
|
||||||
|
|
||||||
|
def set_user_error_reportable(reportable: bool) -> None:
|
||||||
|
global StorageValueError
|
||||||
|
StorageValueError = ReportableSVE if reportable else NonReportableSVE
|
||||||
|
|
||||||
|
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class CapabilityInfo:
|
class CapabilityInfo:
|
||||||
allowed: List[GuidedCapability] = attr.Factory(list)
|
allowed: List[GuidedCapability] = attr.Factory(list)
|
||||||
|
@ -1193,9 +1211,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
self.locked_probe_data = True
|
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 StorageValueError("device already has bootloader partition")
|
||||||
if DeviceAction.TOGGLE_BOOT not in DeviceAction.supported(disk):
|
if DeviceAction.TOGGLE_BOOT not in DeviceAction.supported(disk):
|
||||||
raise ValueError("disk does not support boot partiton")
|
raise StorageValueError("disk does not support boot partiton")
|
||||||
self.add_boot_disk(disk)
|
self.add_boot_disk(disk)
|
||||||
return await self.v2_GET()
|
return await self.v2_GET()
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ from subiquity.server.autoinstall import AutoinstallError
|
||||||
from subiquity.server.controllers.filesystem import (
|
from subiquity.server.controllers.filesystem import (
|
||||||
DRY_RUN_RESET_SIZE,
|
DRY_RUN_RESET_SIZE,
|
||||||
FilesystemController,
|
FilesystemController,
|
||||||
|
StorageValueError,
|
||||||
VariationInfo,
|
VariationInfo,
|
||||||
)
|
)
|
||||||
from subiquity.server.dryrun import DRConfig
|
from subiquity.server.dryrun import DRConfig
|
||||||
|
@ -220,7 +221,9 @@ class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
||||||
async def test_v2_add_boot_partition_POST_existing_bootloader(self):
|
async def test_v2_add_boot_partition_POST_existing_bootloader(self):
|
||||||
self.fsc.locked_probe_data = False
|
self.fsc.locked_probe_data = False
|
||||||
with mock.patch.object(self.fsc, "add_boot_disk") as add_boot_disk:
|
with mock.patch.object(self.fsc, "add_boot_disk") as add_boot_disk:
|
||||||
with self.assertRaisesRegex(ValueError, r"already\ has\ bootloader"):
|
with self.assertRaises(
|
||||||
|
StorageValueError, msg="device already has bootloader"
|
||||||
|
):
|
||||||
await self.fsc.v2_add_boot_partition_POST("dev-sda")
|
await self.fsc.v2_add_boot_partition_POST("dev-sda")
|
||||||
self.assertTrue(self.fsc.locked_probe_data)
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
add_boot_disk.assert_not_called()
|
add_boot_disk.assert_not_called()
|
||||||
|
@ -230,7 +233,7 @@ class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
||||||
async def test_v2_add_boot_partition_POST_not_supported(self):
|
async def test_v2_add_boot_partition_POST_not_supported(self):
|
||||||
self.fsc.locked_probe_data = False
|
self.fsc.locked_probe_data = False
|
||||||
with mock.patch.object(self.fsc, "add_boot_disk") as add_boot_disk:
|
with mock.patch.object(self.fsc, "add_boot_disk") as add_boot_disk:
|
||||||
with self.assertRaisesRegex(ValueError, r"does\ not\ support\ boot"):
|
with self.assertRaises(StorageValueError, msg="disk does not support boot"):
|
||||||
await self.fsc.v2_add_boot_partition_POST("dev-sda")
|
await self.fsc.v2_add_boot_partition_POST("dev-sda")
|
||||||
self.assertTrue(self.fsc.locked_probe_data)
|
self.assertTrue(self.fsc.locked_probe_data)
|
||||||
add_boot_disk.assert_not_called()
|
add_boot_disk.assert_not_called()
|
||||||
|
|
|
@ -2272,3 +2272,37 @@ class TestMountDetection(TestAPI):
|
||||||
self.assertTrue(disk1["has_in_use_partition"])
|
self.assertTrue(disk1["has_in_use_partition"])
|
||||||
disk1p2 = disk1["partitions"][1]
|
disk1p2 = disk1["partitions"][1]
|
||||||
self.assertTrue(disk1p2["is_in_use"])
|
self.assertTrue(disk1p2["is_in_use"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestFilesystemUserErrors(TestAPI):
|
||||||
|
@timeout()
|
||||||
|
async def test_add_boot_partition__with_error_report(self):
|
||||||
|
cfg = "examples/machines/simple.json"
|
||||||
|
extra = ["--storage-version", "2"]
|
||||||
|
async with start_server(cfg, extra_args=extra) as inst:
|
||||||
|
await inst.post("/storage/v2/add_boot_partition", disk_id="disk-sda")
|
||||||
|
try:
|
||||||
|
await inst.post("/storage/v2/add_boot_partition", disk_id="disk-sda")
|
||||||
|
except ClientResponseError as cre:
|
||||||
|
self.assertEqual(500, cre.status)
|
||||||
|
self.assertIn("x-error-report", cre.headers)
|
||||||
|
self.assertEqual(
|
||||||
|
"device already has bootloader partition",
|
||||||
|
json.loads(cre.headers["x-error-msg"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
@timeout()
|
||||||
|
async def test_add_boot_partition__no_error_report(self):
|
||||||
|
cfg = "examples/machines/simple.json"
|
||||||
|
extra = ["--storage-version", "2", "--no-report-storage-user-error"]
|
||||||
|
async with start_server(cfg, extra_args=extra) as inst:
|
||||||
|
await inst.post("/storage/v2/add_boot_partition", disk_id="disk-sda")
|
||||||
|
try:
|
||||||
|
await inst.post("/storage/v2/add_boot_partition", disk_id="disk-sda")
|
||||||
|
except ClientResponseError as cre:
|
||||||
|
self.assertEqual(422, cre.status)
|
||||||
|
self.assertNotIn("x-error-report", cre.headers)
|
||||||
|
self.assertEqual(
|
||||||
|
"device already has bootloader partition",
|
||||||
|
json.loads(cre.headers["x-error-msg"]),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue