mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-11 19:26:28 +00:00
Prevent multiple data disks with name hassos-data-external (#4222)
* Prevent multiple data disks with name hassos-data-external * Fix pylint issues * Do rename before format
This commit is contained in:
parent
842e550dda
commit
fce0d2aaed
@ -30,6 +30,7 @@ DBUS_IFACE_HOSTNAME = "org.freedesktop.hostname1"
|
|||||||
DBUS_IFACE_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
|
DBUS_IFACE_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
|
||||||
DBUS_IFACE_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config"
|
DBUS_IFACE_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config"
|
||||||
DBUS_IFACE_NM = "org.freedesktop.NetworkManager"
|
DBUS_IFACE_NM = "org.freedesktop.NetworkManager"
|
||||||
|
DBUS_IFACE_PARTITION = "org.freedesktop.UDisks2.Partition"
|
||||||
DBUS_IFACE_PARTITION_TABLE = "org.freedesktop.UDisks2.PartitionTable"
|
DBUS_IFACE_PARTITION_TABLE = "org.freedesktop.UDisks2.PartitionTable"
|
||||||
DBUS_IFACE_RAUC_INSTALLER = "de.pengutronix.rauc.Installer"
|
DBUS_IFACE_RAUC_INSTALLER = "de.pengutronix.rauc.Installer"
|
||||||
DBUS_IFACE_RESOLVED_MANAGER = "org.freedesktop.resolve1.Manager"
|
DBUS_IFACE_RESOLVED_MANAGER = "org.freedesktop.resolve1.Manager"
|
||||||
@ -130,10 +131,13 @@ DBUS_ATTR_MODE = "Mode"
|
|||||||
DBUS_ATTR_MODEL = "Model"
|
DBUS_ATTR_MODEL = "Model"
|
||||||
DBUS_ATTR_MOUNT_POINTS = "MountPoints"
|
DBUS_ATTR_MOUNT_POINTS = "MountPoints"
|
||||||
DBUS_ATTR_MULTICAST_DNS = "MulticastDNS"
|
DBUS_ATTR_MULTICAST_DNS = "MulticastDNS"
|
||||||
|
DBUS_ATTR_NAME = "Name"
|
||||||
DBUS_ATTR_NAMESERVER_DATA = "NameserverData"
|
DBUS_ATTR_NAMESERVER_DATA = "NameserverData"
|
||||||
DBUS_ATTR_NAMESERVERS = "Nameservers"
|
DBUS_ATTR_NAMESERVERS = "Nameservers"
|
||||||
DBUS_ATTR_NTP = "NTP"
|
DBUS_ATTR_NTP = "NTP"
|
||||||
DBUS_ATTR_NTPSYNCHRONIZED = "NTPSynchronized"
|
DBUS_ATTR_NTPSYNCHRONIZED = "NTPSynchronized"
|
||||||
|
DBUS_ATTR_NUMBER = "Number"
|
||||||
|
DBUS_ATTR_OFFSET = "Offset"
|
||||||
DBUS_ATTR_OPERATING_SYSTEM_PRETTY_NAME = "OperatingSystemPrettyName"
|
DBUS_ATTR_OPERATING_SYSTEM_PRETTY_NAME = "OperatingSystemPrettyName"
|
||||||
DBUS_ATTR_OPERATION = "Operation"
|
DBUS_ATTR_OPERATION = "Operation"
|
||||||
DBUS_ATTR_PARSER_VERSION = "ParserVersion"
|
DBUS_ATTR_PARSER_VERSION = "ParserVersion"
|
||||||
@ -156,12 +160,14 @@ DBUS_ATTR_STATIC_OPERATING_SYSTEM_CPE_NAME = "OperatingSystemCPEName"
|
|||||||
DBUS_ATTR_STRENGTH = "Strength"
|
DBUS_ATTR_STRENGTH = "Strength"
|
||||||
DBUS_ATTR_SUPPORTED_FILESYSTEMS = "SupportedFilesystems"
|
DBUS_ATTR_SUPPORTED_FILESYSTEMS = "SupportedFilesystems"
|
||||||
DBUS_ATTR_SYMLINKS = "Symlinks"
|
DBUS_ATTR_SYMLINKS = "Symlinks"
|
||||||
|
DBUS_ATTR_TABLE = "Table"
|
||||||
DBUS_ATTR_TIME_DETECTED = "TimeDetected"
|
DBUS_ATTR_TIME_DETECTED = "TimeDetected"
|
||||||
DBUS_ATTR_TIMEUSEC = "TimeUSec"
|
DBUS_ATTR_TIMEUSEC = "TimeUSec"
|
||||||
DBUS_ATTR_TIMEZONE = "Timezone"
|
DBUS_ATTR_TIMEZONE = "Timezone"
|
||||||
DBUS_ATTR_TRANSACTION_STATISTICS = "TransactionStatistics"
|
DBUS_ATTR_TRANSACTION_STATISTICS = "TransactionStatistics"
|
||||||
DBUS_ATTR_TYPE = "Type"
|
DBUS_ATTR_TYPE = "Type"
|
||||||
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC = "UserspaceTimestampMonotonic"
|
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC = "UserspaceTimestampMonotonic"
|
||||||
|
DBUS_ATTR_UUID_UPPERCASE = "UUID"
|
||||||
DBUS_ATTR_UUID = "Uuid"
|
DBUS_ATTR_UUID = "Uuid"
|
||||||
DBUS_ATTR_VARIANT = "Variant"
|
DBUS_ATTR_VARIANT = "Variant"
|
||||||
DBUS_ATTR_VENDOR = "Vendor"
|
DBUS_ATTR_VENDOR = "Vendor"
|
||||||
|
@ -26,6 +26,7 @@ from ..const import (
|
|||||||
DBUS_ATTR_SYMLINKS,
|
DBUS_ATTR_SYMLINKS,
|
||||||
DBUS_IFACE_BLOCK,
|
DBUS_IFACE_BLOCK,
|
||||||
DBUS_IFACE_FILESYSTEM,
|
DBUS_IFACE_FILESYSTEM,
|
||||||
|
DBUS_IFACE_PARTITION,
|
||||||
DBUS_IFACE_PARTITION_TABLE,
|
DBUS_IFACE_PARTITION_TABLE,
|
||||||
DBUS_NAME_UDISKS2,
|
DBUS_NAME_UDISKS2,
|
||||||
)
|
)
|
||||||
@ -34,6 +35,7 @@ from ..utils import dbus_connected
|
|||||||
from .const import UDISKS2_DEFAULT_OPTIONS, FormatType
|
from .const import UDISKS2_DEFAULT_OPTIONS, FormatType
|
||||||
from .data import FormatOptions, udisks2_bytes_to_path
|
from .data import FormatOptions, udisks2_bytes_to_path
|
||||||
from .filesystem import UDisks2Filesystem
|
from .filesystem import UDisks2Filesystem
|
||||||
|
from .partition import UDisks2Partition
|
||||||
from .partition_table import UDisks2PartitionTable
|
from .partition_table import UDisks2PartitionTable
|
||||||
|
|
||||||
ADDITIONAL_INTERFACES: dict[str, Callable[[str], DBusInterfaceProxy]] = {
|
ADDITIONAL_INTERFACES: dict[str, Callable[[str], DBusInterfaceProxy]] = {
|
||||||
@ -52,6 +54,7 @@ class UDisks2Block(DBusInterfaceProxy):
|
|||||||
properties_interface: str = DBUS_IFACE_BLOCK
|
properties_interface: str = DBUS_IFACE_BLOCK
|
||||||
|
|
||||||
_filesystem: UDisks2Filesystem | None = None
|
_filesystem: UDisks2Filesystem | None = None
|
||||||
|
_partition: UDisks2Partition | None = None
|
||||||
_partition_table: UDisks2PartitionTable | None = None
|
_partition_table: UDisks2PartitionTable | None = None
|
||||||
|
|
||||||
def __init__(self, object_path: str, *, sync_properties: bool = True) -> None:
|
def __init__(self, object_path: str, *, sync_properties: bool = True) -> None:
|
||||||
@ -79,6 +82,11 @@ class UDisks2Block(DBusInterfaceProxy):
|
|||||||
"""Filesystem interface if block device is one."""
|
"""Filesystem interface if block device is one."""
|
||||||
return self._filesystem
|
return self._filesystem
|
||||||
|
|
||||||
|
@property
|
||||||
|
def partition(self) -> UDisks2Partition | None:
|
||||||
|
"""Partition interface if block device is one."""
|
||||||
|
return self._partition
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def partition_table(self) -> UDisks2PartitionTable | None:
|
def partition_table(self) -> UDisks2PartitionTable | None:
|
||||||
"""Partition table interface if block device is one."""
|
"""Partition table interface if block device is one."""
|
||||||
@ -195,7 +203,7 @@ class UDisks2Block(DBusInterfaceProxy):
|
|||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
intr.update()
|
intr.update()
|
||||||
for intr in (self.filesystem, self.partition_table)
|
for intr in (self.filesystem, self.partition, self.partition_table)
|
||||||
if intr
|
if intr
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -225,6 +233,17 @@ class UDisks2Block(DBusInterfaceProxy):
|
|||||||
self.filesystem.stop_sync_property_changes()
|
self.filesystem.stop_sync_property_changes()
|
||||||
self._filesystem = None
|
self._filesystem = None
|
||||||
|
|
||||||
|
# Check if block device is a partition
|
||||||
|
if not self.partition and DBUS_IFACE_PARTITION in self.dbus.proxies:
|
||||||
|
self._partition = UDisks2Partition(
|
||||||
|
self.object_path, sync_properties=self.sync_properties
|
||||||
|
)
|
||||||
|
await self._partition.initialize(self.dbus)
|
||||||
|
|
||||||
|
elif self.partition and DBUS_IFACE_PARTITION not in self.dbus.proxies:
|
||||||
|
self.partition.stop_sync_property_changes()
|
||||||
|
self._partition = None
|
||||||
|
|
||||||
# Check if block device is a partition table
|
# Check if block device is a partition table
|
||||||
if not self.partition_table and DBUS_IFACE_PARTITION_TABLE in self.dbus.proxies:
|
if not self.partition_table and DBUS_IFACE_PARTITION_TABLE in self.dbus.proxies:
|
||||||
self._partition_table = UDisks2PartitionTable(
|
self._partition_table = UDisks2PartitionTable(
|
||||||
|
@ -194,7 +194,7 @@ MountOptionsDataType = TypedDict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(slots=True)
|
||||||
class MountOptions(UDisks2StandardOptions):
|
class MountOptions(UDisks2StandardOptions):
|
||||||
"""Filesystem mount options.
|
"""Filesystem mount options.
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ UnmountOptionsDataType = TypedDict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(slots=True)
|
||||||
class UnmountOptions(UDisks2StandardOptions):
|
class UnmountOptions(UDisks2StandardOptions):
|
||||||
"""Filesystem unmount options.
|
"""Filesystem unmount options.
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ CreatePartitionOptionsDataType = TypedDict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(slots=True)
|
||||||
class CreatePartitionOptions(UDisks2StandardOptions):
|
class CreatePartitionOptions(UDisks2StandardOptions):
|
||||||
"""Create partition options.
|
"""Create partition options.
|
||||||
|
|
||||||
@ -300,3 +300,39 @@ class CreatePartitionOptions(UDisks2StandardOptions):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
return {k: v for k, v in data.items() if v}
|
return {k: v for k, v in data.items() if v}
|
||||||
|
|
||||||
|
|
||||||
|
DeletePartitionOptionsDataType = TypedDict(
|
||||||
|
"DeletePartitionOptionsDataType",
|
||||||
|
{"tear-down": NotRequired[bool]} | _udisks2_standard_options_annotations,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class DeletePartitionOptions(UDisks2StandardOptions):
|
||||||
|
"""Delete partition options.
|
||||||
|
|
||||||
|
http://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.Partition.html#gdbus-method-org-freedesktop-UDisks2-Partition.Delete
|
||||||
|
"""
|
||||||
|
|
||||||
|
tear_down: bool | None = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: DeletePartitionOptionsDataType) -> "DeletePartitionOptions":
|
||||||
|
"""Create DeletePartitionOptions from dict."""
|
||||||
|
return DeletePartitionOptions(
|
||||||
|
tear_down=data.get("tear-down"),
|
||||||
|
# UDisks2 standard options
|
||||||
|
auth_no_user_interaction=data.get("auth.no_user_interaction"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Variant]:
|
||||||
|
"""Return dict representation."""
|
||||||
|
data = {
|
||||||
|
"tear-down": _optional_variant("b", self.tear_down),
|
||||||
|
# UDisks2 standard options
|
||||||
|
"auth.no_user_interaction": _optional_variant(
|
||||||
|
"b", self.auth_no_user_interaction
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return {k: v for k, v in data.items() if v}
|
||||||
|
109
supervisor/dbus/udisks2/partition.py
Normal file
109
supervisor/dbus/udisks2/partition.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""Interface to UDisks2 Partition over D-Bus."""
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
DBUS_ATTR_NAME,
|
||||||
|
DBUS_ATTR_NUMBER,
|
||||||
|
DBUS_ATTR_OFFSET,
|
||||||
|
DBUS_ATTR_SIZE,
|
||||||
|
DBUS_ATTR_TABLE,
|
||||||
|
DBUS_ATTR_TYPE,
|
||||||
|
DBUS_ATTR_UUID_UPPERCASE,
|
||||||
|
DBUS_IFACE_PARTITION,
|
||||||
|
DBUS_NAME_UDISKS2,
|
||||||
|
)
|
||||||
|
from ..interface import DBusInterfaceProxy, dbus_property
|
||||||
|
from ..utils import dbus_connected
|
||||||
|
from .const import UDISKS2_DEFAULT_OPTIONS
|
||||||
|
from .data import DeletePartitionOptions
|
||||||
|
|
||||||
|
|
||||||
|
class UDisks2Partition(DBusInterfaceProxy):
|
||||||
|
"""Handle D-Bus interface for UDisks2 Partition device object.
|
||||||
|
|
||||||
|
http://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.Partition.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = DBUS_IFACE_PARTITION
|
||||||
|
bus_name = DBUS_NAME_UDISKS2
|
||||||
|
properties_interface = DBUS_IFACE_PARTITION
|
||||||
|
|
||||||
|
def __init__(self, object_path: str, *, sync_properties: bool = True) -> None:
|
||||||
|
"""Initialize object."""
|
||||||
|
self.object_path = object_path
|
||||||
|
self.sync_properties = sync_properties
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def number(self) -> int:
|
||||||
|
"""Parition number in partition table."""
|
||||||
|
return self.properties[DBUS_ATTR_NUMBER]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def type_(self) -> str:
|
||||||
|
"""Partition type."""
|
||||||
|
return self.properties[DBUS_ATTR_TYPE]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def offset(self) -> int:
|
||||||
|
"""Offset of partition in bytes."""
|
||||||
|
return self.properties[DBUS_ATTR_OFFSET]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def size(self) -> int:
|
||||||
|
"""Size of partition in bytes."""
|
||||||
|
return self.properties[DBUS_ATTR_SIZE]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def name_(self) -> str:
|
||||||
|
"""Name/label of partition if known."""
|
||||||
|
return self.properties[DBUS_ATTR_NAME]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def uuid(self) -> str:
|
||||||
|
"""UUID of partition if known."""
|
||||||
|
return self.properties[DBUS_ATTR_UUID_UPPERCASE]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def table(self) -> str:
|
||||||
|
"""Object path of the partition table this belongs to.
|
||||||
|
|
||||||
|
Provide to UDisks2.get_block_device for UDisks2Block object.
|
||||||
|
"""
|
||||||
|
return self.properties[DBUS_ATTR_TABLE]
|
||||||
|
|
||||||
|
@dbus_connected
|
||||||
|
async def set_type(self, type_: str) -> None:
|
||||||
|
"""Set the type of the partition.
|
||||||
|
|
||||||
|
Type should be a GUID from https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
|
||||||
|
for GPT type tables or a hexadecimal number for dos type tables. Can also use empty string
|
||||||
|
and let UDisks2 choose a default based on partition table and OS.
|
||||||
|
"""
|
||||||
|
await self.dbus.Partition.call_set_type(type_, UDISKS2_DEFAULT_OPTIONS)
|
||||||
|
|
||||||
|
@dbus_connected
|
||||||
|
async def set_name(self, name: str) -> None:
|
||||||
|
"""Set the name/label of the partition."""
|
||||||
|
await self.dbus.Partition.call_set_name(name, UDISKS2_DEFAULT_OPTIONS)
|
||||||
|
|
||||||
|
@dbus_connected
|
||||||
|
async def resize(self, size: int = 0) -> None:
|
||||||
|
"""Attempt to increase size of partition by specified bytes. 0 means determine and use maximal size.
|
||||||
|
|
||||||
|
Position/offset cannot be changed, only size. May be slightly bigger then requested.
|
||||||
|
Raises error if allocation fails.
|
||||||
|
"""
|
||||||
|
await self.dbus.Partition.call_resize(size, UDISKS2_DEFAULT_OPTIONS)
|
||||||
|
|
||||||
|
@dbus_connected
|
||||||
|
async def delete(self, options: DeletePartitionOptions | None = None) -> None:
|
||||||
|
"""Delete the partition."""
|
||||||
|
options = options.to_dict() if options else {}
|
||||||
|
return await self.dbus.Partition.call_delete(options | UDISKS2_DEFAULT_OPTIONS)
|
@ -55,7 +55,8 @@ class UDisks2PartitionTable(DBusInterfaceProxy):
|
|||||||
"""Create a new partition and return object path of new block device.
|
"""Create a new partition and return object path of new block device.
|
||||||
|
|
||||||
Type should be a GUID from https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
|
Type should be a GUID from https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
|
||||||
or empty string and let UDisks2 choose a default based on partition table and OS.
|
for GPT type tables or a hexadecimal number for dos type tables. Can also use empty string
|
||||||
|
and let UDisks2 choose a default based on partition table and OS.
|
||||||
Provide return value with UDisks2Block.new. Or UDisks2.get_block_device after UDisks2.update.
|
Provide return value with UDisks2Block.new. Or UDisks2.get_block_device after UDisks2.update.
|
||||||
"""
|
"""
|
||||||
options = options.to_dict() if options else {}
|
options = options.to_dict() if options else {}
|
||||||
|
@ -3,3 +3,4 @@
|
|||||||
FILESYSTEM_LABEL_DATA_DISK = "hassos-data"
|
FILESYSTEM_LABEL_DATA_DISK = "hassos-data"
|
||||||
FILESYSTEM_LABEL_OLD_DATA_DISK = "hassos-data-old"
|
FILESYSTEM_LABEL_OLD_DATA_DISK = "hassos-data-old"
|
||||||
PARTITION_NAME_EXTERNAL_DATA_DISK = "hassos-data-external"
|
PARTITION_NAME_EXTERNAL_DATA_DISK = "hassos-data-external"
|
||||||
|
PARTITION_NAME_OLD_EXTERNAL_DATA_DISK = "hassos-data-external-old"
|
||||||
|
@ -23,7 +23,10 @@ from ..exceptions import (
|
|||||||
from ..jobs.const import JobCondition, JobExecutionLimit
|
from ..jobs.const import JobCondition, JobExecutionLimit
|
||||||
from ..jobs.decorator import Job
|
from ..jobs.decorator import Job
|
||||||
from ..utils.sentry import capture_exception
|
from ..utils.sentry import capture_exception
|
||||||
from .const import PARTITION_NAME_EXTERNAL_DATA_DISK
|
from .const import (
|
||||||
|
PARTITION_NAME_EXTERNAL_DATA_DISK,
|
||||||
|
PARTITION_NAME_OLD_EXTERNAL_DATA_DISK,
|
||||||
|
)
|
||||||
|
|
||||||
LINUX_DATA_PARTITION_GUID: Final = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
LINUX_DATA_PARTITION_GUID: Final = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
||||||
OS_AGENT_MARK_DATA_MOVE_VERSION: Final = AwesomeVersion("1.5.0")
|
OS_AGENT_MARK_DATA_MOVE_VERSION: Final = AwesomeVersion("1.5.0")
|
||||||
@ -186,9 +189,26 @@ class DataDisk(CoreSysAttributes):
|
|||||||
]
|
]
|
||||||
if len(target_disk) != 1:
|
if len(target_disk) != 1:
|
||||||
raise HassOSDataDiskError(
|
raise HassOSDataDiskError(
|
||||||
f"'{new_disk!s}' not a valid data disk target!", _LOGGER.error
|
f"'{new_disk}' not a valid data disk target!", _LOGGER.error
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
|
# If any other partition is named "hassos-data-external" error and ask for its removal
|
||||||
|
# otherwise it will create a race condition at startup
|
||||||
|
if self.disk_used and (
|
||||||
|
conflicts := [
|
||||||
|
block
|
||||||
|
for block in self.sys_dbus.udisks2.block_devices
|
||||||
|
if block.partition
|
||||||
|
and block.partition.name_ == PARTITION_NAME_EXTERNAL_DATA_DISK
|
||||||
|
and block.device != self.disk_used.device_path
|
||||||
|
and block.drive != target_disk[0].object_path
|
||||||
|
]
|
||||||
|
):
|
||||||
|
raise HassOSDataDiskError(
|
||||||
|
f"Partition(s) {', '.join([conflict.device.as_posix() for conflict in conflicts])} have name 'hassos-data-external' which prevents migration. Remove or rename them first.",
|
||||||
|
_LOGGER.error,
|
||||||
|
)
|
||||||
|
|
||||||
# Older OS did not have mark data move API. Must let OS do disk format & migration
|
# Older OS did not have mark data move API. Must let OS do disk format & migration
|
||||||
if self.sys_dbus.agent.version < OS_AGENT_MARK_DATA_MOVE_VERSION:
|
if self.sys_dbus.agent.version < OS_AGENT_MARK_DATA_MOVE_VERSION:
|
||||||
try:
|
try:
|
||||||
@ -201,11 +221,37 @@ class DataDisk(CoreSysAttributes):
|
|||||||
) from err
|
) from err
|
||||||
else:
|
else:
|
||||||
# Format disk then tell OS to migrate next reboot
|
# Format disk then tell OS to migrate next reboot
|
||||||
|
current_block = (
|
||||||
|
self.sys_dbus.udisks2.get_block_device(
|
||||||
|
self.disk_used.device_object_path
|
||||||
|
)
|
||||||
|
if self.disk_used
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# If migrating from one external data disk to another, rename the old one to prevent conflicts
|
||||||
|
# Do this first because otherwise a subsequent failure could create a race condition on reboot
|
||||||
|
if (
|
||||||
|
current_block
|
||||||
|
and current_block.partition
|
||||||
|
and current_block.partition.name_ == PARTITION_NAME_EXTERNAL_DATA_DISK
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await current_block.partition.set_name(
|
||||||
|
PARTITION_NAME_OLD_EXTERNAL_DATA_DISK
|
||||||
|
)
|
||||||
|
except DBusError as err:
|
||||||
|
raise HassOSDataDiskError(
|
||||||
|
f"Could not rename existing external data disk to prevent name conflict: {err!s}",
|
||||||
|
_LOGGER.error,
|
||||||
|
) from err
|
||||||
|
|
||||||
partition = await self._format_device_with_single_partition(target_disk[0])
|
partition = await self._format_device_with_single_partition(target_disk[0])
|
||||||
|
|
||||||
if self.disk_used and partition.size < self.disk_used.size:
|
if current_block and current_block.size > partition.size:
|
||||||
raise HassOSDataDiskError(
|
raise HassOSDataDiskError(
|
||||||
f"Cannot use {new_disk} as data disk as it is smaller then the current one (new: {partition.size}, current: {self.disk_used.size})"
|
f"Cannot use {new_disk} as data disk as it is smaller then the current one (new: {partition.size}, current: {current_block.size})",
|
||||||
|
_LOGGER.error,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -255,7 +301,7 @@ class DataDisk(CoreSysAttributes):
|
|||||||
except DBusError as err:
|
except DBusError as err:
|
||||||
capture_exception(err)
|
capture_exception(err)
|
||||||
raise HassOSDataDiskError(
|
raise HassOSDataDiskError(
|
||||||
f"Could not create new data partition: {err!s}"
|
f"Could not create new data partition: {err!s}", _LOGGER.error
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -264,7 +310,8 @@ class DataDisk(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
except DBusError as err:
|
except DBusError as err:
|
||||||
raise HassOSDataDiskError(
|
raise HassOSDataDiskError(
|
||||||
f"New data partition at {partition} is missing or unusable"
|
f"New data partition at {partition} is missing or unusable",
|
||||||
|
_LOGGER.error,
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@ -225,7 +225,7 @@ async def fixture_udisks2_services(
|
|||||||
"/org/freedesktop/UDisks2/block_devices/sda",
|
"/org/freedesktop/UDisks2/block_devices/sda",
|
||||||
"/org/freedesktop/UDisks2/block_devices/sdb",
|
"/org/freedesktop/UDisks2/block_devices/sdb",
|
||||||
],
|
],
|
||||||
"udisks2_parition": [
|
"udisks2_partition": [
|
||||||
"/org/freedesktop/UDisks2/block_devices/mmcblk1p1",
|
"/org/freedesktop/UDisks2/block_devices/mmcblk1p1",
|
||||||
"/org/freedesktop/UDisks2/block_devices/mmcblk1p2",
|
"/org/freedesktop/UDisks2/block_devices/mmcblk1p2",
|
||||||
"/org/freedesktop/UDisks2/block_devices/mmcblk1p3",
|
"/org/freedesktop/UDisks2/block_devices/mmcblk1p3",
|
||||||
|
@ -11,6 +11,7 @@ from supervisor.dbus.udisks2.block import UDisks2Block
|
|||||||
from supervisor.dbus.udisks2.const import FormatType, PartitionTableType
|
from supervisor.dbus.udisks2.const import FormatType, PartitionTableType
|
||||||
from supervisor.dbus.udisks2.data import FormatOptions
|
from supervisor.dbus.udisks2.data import FormatOptions
|
||||||
from supervisor.dbus.udisks2.filesystem import UDisks2Filesystem
|
from supervisor.dbus.udisks2.filesystem import UDisks2Filesystem
|
||||||
|
from supervisor.dbus.udisks2.partition import UDisks2Partition
|
||||||
from supervisor.dbus.udisks2.partition_table import UDisks2PartitionTable
|
from supervisor.dbus.udisks2.partition_table import UDisks2PartitionTable
|
||||||
from supervisor.utils.dbus import DBus
|
from supervisor.utils.dbus import DBus
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ async def test_block_device_info(
|
|||||||
assert sda1.id_label is None
|
assert sda1.id_label is None
|
||||||
assert sda1.symlinks is None
|
assert sda1.symlinks is None
|
||||||
assert sda1.filesystem is None
|
assert sda1.filesystem is None
|
||||||
|
assert sda1.partition is None
|
||||||
|
|
||||||
await sda.connect(dbus_session_bus)
|
await sda.connect(dbus_session_bus)
|
||||||
await sda1.connect(dbus_session_bus)
|
await sda1.connect(dbus_session_bus)
|
||||||
@ -66,6 +68,7 @@ async def test_block_device_info(
|
|||||||
assert sda.id_label == ""
|
assert sda.id_label == ""
|
||||||
assert sda.partition_table.type == PartitionTableType.GPT
|
assert sda.partition_table.type == PartitionTableType.GPT
|
||||||
assert sda.filesystem is None
|
assert sda.filesystem is None
|
||||||
|
assert sda.partition is None
|
||||||
|
|
||||||
assert sda1.id_label == "hassos-data-old"
|
assert sda1.id_label == "hassos-data-old"
|
||||||
assert sda1.symlinks == [
|
assert sda1.symlinks == [
|
||||||
@ -80,6 +83,7 @@ async def test_block_device_info(
|
|||||||
]
|
]
|
||||||
assert sda1.partition_table is None
|
assert sda1.partition_table is None
|
||||||
assert sda1.filesystem.mount_points == []
|
assert sda1.filesystem.mount_points == []
|
||||||
|
assert sda1.partition.number == 1
|
||||||
|
|
||||||
block_sda_service.emit_properties_changed({"IdLabel": "test"})
|
block_sda_service.emit_properties_changed({"IdLabel": "test"})
|
||||||
await block_sda_service.ping()
|
await block_sda_service.ping()
|
||||||
@ -135,9 +139,10 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
await sda.connect(dbus_session_bus)
|
await sda.connect(dbus_session_bus)
|
||||||
await sda1.connect(dbus_session_bus)
|
await sda1.connect(dbus_session_bus)
|
||||||
|
|
||||||
# Connected but neither are filesystems are partition tables
|
# Connected but neither are filesystems, partitions or partition tables
|
||||||
assert sda.partition_table is None
|
assert sda.partition_table is None
|
||||||
assert sda1.filesystem is None
|
assert sda1.filesystem is None
|
||||||
|
assert sda1.partition is None
|
||||||
assert sda.id_label == ""
|
assert sda.id_label == ""
|
||||||
assert sda1.id_label == "hassos-data-old"
|
assert sda1.id_label == "hassos-data-old"
|
||||||
|
|
||||||
@ -147,11 +152,13 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
{
|
{
|
||||||
"udisks2_partition_table": "/org/freedesktop/UDisks2/block_devices/sda",
|
"udisks2_partition_table": "/org/freedesktop/UDisks2/block_devices/sda",
|
||||||
"udisks2_filesystem": "/org/freedesktop/UDisks2/block_devices/sda1",
|
"udisks2_filesystem": "/org/freedesktop/UDisks2/block_devices/sda1",
|
||||||
|
"udisks2_partition": "/org/freedesktop/UDisks2/block_devices/sda1",
|
||||||
},
|
},
|
||||||
dbus_session_bus,
|
dbus_session_bus,
|
||||||
)
|
)
|
||||||
sda_pt_service = services["udisks2_partition_table"]
|
sda_pt_service = services["udisks2_partition_table"]
|
||||||
sda1_fs_service = services["udisks2_filesystem"]
|
sda1_fs_service = services["udisks2_filesystem"]
|
||||||
|
sda1_part_service = services["udisks2_partition"]
|
||||||
|
|
||||||
await sda.check_type()
|
await sda.check_type()
|
||||||
await sda1.check_type()
|
await sda1.check_type()
|
||||||
@ -159,11 +166,14 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
# Check that the type is now correct and property changes are syncing
|
# Check that the type is now correct and property changes are syncing
|
||||||
assert sda.partition_table
|
assert sda.partition_table
|
||||||
assert sda1.filesystem
|
assert sda1.filesystem
|
||||||
|
assert sda1.partition
|
||||||
|
|
||||||
partition_table: UDisks2PartitionTable = sda.partition_table
|
partition_table: UDisks2PartitionTable = sda.partition_table
|
||||||
filesystem: UDisks2Filesystem = sda1.filesystem
|
filesystem: UDisks2Filesystem = sda1.filesystem
|
||||||
|
partition: UDisks2Partition = sda1.partition
|
||||||
assert partition_table.type == PartitionTableType.GPT
|
assert partition_table.type == PartitionTableType.GPT
|
||||||
assert filesystem.size == 250058113024
|
assert filesystem.size == 250058113024
|
||||||
|
assert partition.name_ == "hassos-data-external"
|
||||||
|
|
||||||
sda_pt_service.emit_properties_changed({"Type": "dos"})
|
sda_pt_service.emit_properties_changed({"Type": "dos"})
|
||||||
await sda_pt_service.ping()
|
await sda_pt_service.ping()
|
||||||
@ -173,6 +183,10 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
await sda1_fs_service.ping()
|
await sda1_fs_service.ping()
|
||||||
assert filesystem.size == 100
|
assert filesystem.size == 100
|
||||||
|
|
||||||
|
sda1_part_service.emit_properties_changed({"Name": "test"})
|
||||||
|
await sda1_part_service.ping()
|
||||||
|
assert partition.name_ == "test"
|
||||||
|
|
||||||
# Force introspection to return the original block device only introspection and re-check type
|
# Force introspection to return the original block device only introspection and re-check type
|
||||||
with patch.object(DBus, "introspect", return_value=orig_introspection):
|
with patch.object(DBus, "introspect", return_value=orig_introspection):
|
||||||
await sda.check_type()
|
await sda.check_type()
|
||||||
@ -183,6 +197,7 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
assert sda1.is_connected is True
|
assert sda1.is_connected is True
|
||||||
assert sda.partition_table is None
|
assert sda.partition_table is None
|
||||||
assert sda1.filesystem is None
|
assert sda1.filesystem is None
|
||||||
|
assert sda1.partition is None
|
||||||
|
|
||||||
# Property changes should still sync for the block devices
|
# Property changes should still sync for the block devices
|
||||||
sda_block_service.emit_properties_changed({"IdLabel": "test"})
|
sda_block_service.emit_properties_changed({"IdLabel": "test"})
|
||||||
@ -201,3 +216,7 @@ async def test_check_type(dbus_session_bus: MessageBus):
|
|||||||
sda1_fs_service.emit_properties_changed({"Size": 250058113024})
|
sda1_fs_service.emit_properties_changed({"Size": 250058113024})
|
||||||
await sda1_fs_service.ping()
|
await sda1_fs_service.ping()
|
||||||
assert filesystem.size == 100
|
assert filesystem.size == 100
|
||||||
|
|
||||||
|
sda1_part_service.emit_properties_changed({"Name": "hassos-data-external"})
|
||||||
|
await sda1_part_service.ping()
|
||||||
|
assert partition.name_ == "test"
|
||||||
|
161
tests/dbus/udisks2/test_partition.py
Normal file
161
tests/dbus/udisks2/test_partition.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
"""Test UDisks2 Partition."""
|
||||||
|
|
||||||
|
from dbus_fast import Variant
|
||||||
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from supervisor.dbus.udisks2.data import DeletePartitionOptions
|
||||||
|
from supervisor.dbus.udisks2.partition import UDisks2Partition
|
||||||
|
from supervisor.exceptions import DBusNotConnectedError
|
||||||
|
|
||||||
|
from tests.common import mock_dbus_services
|
||||||
|
from tests.dbus_service_mocks.udisks2_partition import Partition as PartitionService
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="partition_sda1_service")
|
||||||
|
async def fixture_partition_sda1_service(
|
||||||
|
dbus_session_bus: MessageBus,
|
||||||
|
) -> PartitionService:
|
||||||
|
"""Mock sda1 Partition service."""
|
||||||
|
yield (
|
||||||
|
await mock_dbus_services(
|
||||||
|
{"udisks2_partition": "/org/freedesktop/UDisks2/block_devices/sda1"},
|
||||||
|
dbus_session_bus,
|
||||||
|
)
|
||||||
|
)["udisks2_partition"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="partition_sdb1_service")
|
||||||
|
async def fixture_partition_sdb_service(
|
||||||
|
dbus_session_bus: MessageBus,
|
||||||
|
) -> PartitionService:
|
||||||
|
"""Mock sdb1 Partition service."""
|
||||||
|
yield (
|
||||||
|
await mock_dbus_services(
|
||||||
|
{"udisks2_partition": "/org/freedesktop/UDisks2/block_devices/sdb1"},
|
||||||
|
dbus_session_bus,
|
||||||
|
)
|
||||||
|
)["udisks2_partition"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_partition_table_info(
|
||||||
|
partition_sda1_service: PartitionService,
|
||||||
|
partition_sdb1_service: PartitionService,
|
||||||
|
dbus_session_bus: MessageBus,
|
||||||
|
):
|
||||||
|
"""Test partition table info."""
|
||||||
|
sda1 = UDisks2Partition("/org/freedesktop/UDisks2/block_devices/sda1")
|
||||||
|
sdb1 = UDisks2Partition(
|
||||||
|
"/org/freedesktop/UDisks2/block_devices/sdb1", sync_properties=False
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sda1.name_ is None
|
||||||
|
assert sda1.size is None
|
||||||
|
assert sdb1.name_ is None
|
||||||
|
assert sdb1.size is None
|
||||||
|
|
||||||
|
await sda1.connect(dbus_session_bus)
|
||||||
|
await sdb1.connect(dbus_session_bus)
|
||||||
|
|
||||||
|
assert sda1.name_ == "hassos-data-external"
|
||||||
|
assert sda1.size == 250058113024
|
||||||
|
assert sdb1.name_ == ""
|
||||||
|
assert sdb1.size == 67108864
|
||||||
|
|
||||||
|
partition_sda1_service.emit_properties_changed({"Name": "test"})
|
||||||
|
await partition_sda1_service.ping()
|
||||||
|
assert sda1.name_ == "test"
|
||||||
|
|
||||||
|
partition_sda1_service.emit_properties_changed({}, ["Name"])
|
||||||
|
await partition_sda1_service.ping()
|
||||||
|
await partition_sda1_service.ping()
|
||||||
|
assert sda1.name_ == "hassos-data-external"
|
||||||
|
|
||||||
|
# Prop changes should not sync for this one
|
||||||
|
partition_sdb1_service.emit_properties_changed({"Name": "test"})
|
||||||
|
await partition_sdb1_service.ping()
|
||||||
|
assert sdb1.name_ == ""
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_type(
|
||||||
|
partition_sda1_service: PartitionService, dbus_session_bus: MessageBus
|
||||||
|
):
|
||||||
|
"""Test setting partition type."""
|
||||||
|
partition_sda1_service.SetType.calls.clear()
|
||||||
|
sda1 = UDisks2Partition("/org/freedesktop/UDisks2/block_devices/sda1")
|
||||||
|
|
||||||
|
with pytest.raises(DBusNotConnectedError):
|
||||||
|
await sda1.set_type("0FC63DAF-8483-4772-8E79-3D69D8477DE4")
|
||||||
|
|
||||||
|
await sda1.connect(dbus_session_bus)
|
||||||
|
|
||||||
|
await sda1.set_type("0FC63DAF-8483-4772-8E79-3D69D8477DE4")
|
||||||
|
|
||||||
|
assert partition_sda1_service.SetType.calls == [
|
||||||
|
(
|
||||||
|
"0FC63DAF-8483-4772-8E79-3D69D8477DE4",
|
||||||
|
{"auth.no_user_interaction": Variant("b", True)},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_name(
|
||||||
|
partition_sda1_service: PartitionService, dbus_session_bus: MessageBus
|
||||||
|
):
|
||||||
|
"""Test setting partition name."""
|
||||||
|
partition_sda1_service.SetName.calls.clear()
|
||||||
|
sda1 = UDisks2Partition("/org/freedesktop/UDisks2/block_devices/sda1")
|
||||||
|
|
||||||
|
with pytest.raises(DBusNotConnectedError):
|
||||||
|
await sda1.set_name("test")
|
||||||
|
|
||||||
|
await sda1.connect(dbus_session_bus)
|
||||||
|
|
||||||
|
await sda1.set_name("test")
|
||||||
|
|
||||||
|
assert partition_sda1_service.SetName.calls == [
|
||||||
|
("test", {"auth.no_user_interaction": Variant("b", True)})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_resize(
|
||||||
|
partition_sda1_service: PartitionService, dbus_session_bus: MessageBus
|
||||||
|
):
|
||||||
|
"""Test resizing partition."""
|
||||||
|
partition_sda1_service.Resize.calls.clear()
|
||||||
|
sda1 = UDisks2Partition("/org/freedesktop/UDisks2/block_devices/sda1")
|
||||||
|
|
||||||
|
with pytest.raises(DBusNotConnectedError):
|
||||||
|
await sda1.resize()
|
||||||
|
|
||||||
|
await sda1.connect(dbus_session_bus)
|
||||||
|
|
||||||
|
await sda1.resize()
|
||||||
|
|
||||||
|
assert partition_sda1_service.Resize.calls == [
|
||||||
|
(0, {"auth.no_user_interaction": Variant("b", True)})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete(
|
||||||
|
partition_sda1_service: PartitionService, dbus_session_bus: MessageBus
|
||||||
|
):
|
||||||
|
"""Test deleting partition."""
|
||||||
|
partition_sda1_service.Delete.calls.clear()
|
||||||
|
sda1 = UDisks2Partition("/org/freedesktop/UDisks2/block_devices/sda1")
|
||||||
|
|
||||||
|
with pytest.raises(DBusNotConnectedError):
|
||||||
|
await sda1.delete(DeletePartitionOptions(tear_down=True))
|
||||||
|
|
||||||
|
await sda1.connect(dbus_session_bus)
|
||||||
|
|
||||||
|
await sda1.delete(DeletePartitionOptions(tear_down=True))
|
||||||
|
|
||||||
|
assert partition_sda1_service.Delete.calls == [
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"tear-down": Variant("b", True),
|
||||||
|
"auth.no_user_interaction": Variant("b", True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
@ -1,4 +1,5 @@
|
|||||||
"""Test OS API."""
|
"""Test OS API."""
|
||||||
|
from dataclasses import replace
|
||||||
from pathlib import PosixPath
|
from pathlib import PosixPath
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ from tests.dbus_service_mocks.agent_datadisk import DataDisk as DataDiskService
|
|||||||
from tests.dbus_service_mocks.base import DBusServiceMock
|
from tests.dbus_service_mocks.base import DBusServiceMock
|
||||||
from tests.dbus_service_mocks.logind import Logind as LogindService
|
from tests.dbus_service_mocks.logind import Logind as LogindService
|
||||||
from tests.dbus_service_mocks.udisks2_block import Block as BlockService
|
from tests.dbus_service_mocks.udisks2_block import Block as BlockService
|
||||||
|
from tests.dbus_service_mocks.udisks2_filesystem import Filesystem as FilesystemService
|
||||||
|
from tests.dbus_service_mocks.udisks2_partition import Partition as PartitionService
|
||||||
from tests.dbus_service_mocks.udisks2_partition_table import (
|
from tests.dbus_service_mocks.udisks2_partition_table import (
|
||||||
PartitionTable as PartitionTableService,
|
PartitionTable as PartitionTableService,
|
||||||
)
|
)
|
||||||
@ -71,7 +74,9 @@ async def test_datadisk_move_fail(coresys: CoreSys, new_disk: str):
|
|||||||
"""Test datadisk move to non-existent or invalid devices."""
|
"""Test datadisk move to non-existent or invalid devices."""
|
||||||
coresys.os._available = True
|
coresys.os._available = True
|
||||||
|
|
||||||
with pytest.raises(HassOSDataDiskError):
|
with pytest.raises(
|
||||||
|
HassOSDataDiskError, match=f"'{new_disk}' not a valid data disk target!"
|
||||||
|
):
|
||||||
await coresys.os.datadisk.migrate_disk(new_disk)
|
await coresys.os.datadisk.migrate_disk(new_disk)
|
||||||
|
|
||||||
|
|
||||||
@ -195,9 +200,77 @@ async def test_datadisk_migrate_too_small(
|
|||||||
await all_dbus_services["os_agent"].ping()
|
await all_dbus_services["os_agent"].ping()
|
||||||
coresys.os._available = True
|
coresys.os._available = True
|
||||||
|
|
||||||
with pytest.raises(HassOSDataDiskError):
|
with pytest.raises(
|
||||||
|
HassOSDataDiskError,
|
||||||
|
match=r"Cannot use SSK-SSK-Storage-DF56419883D56 as data disk as it is smaller then the current one",
|
||||||
|
):
|
||||||
await coresys.os.datadisk.migrate_disk("SSK-SSK-Storage-DF56419883D56")
|
await coresys.os.datadisk.migrate_disk("SSK-SSK-Storage-DF56419883D56")
|
||||||
|
|
||||||
assert partition_table_service.CreatePartition.calls
|
assert partition_table_service.CreatePartition.calls
|
||||||
assert datadisk_service.MarkDataMove.calls == []
|
assert datadisk_service.MarkDataMove.calls == []
|
||||||
assert logind_service.Reboot.calls == []
|
assert logind_service.Reboot.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_datadisk_migrate_multiple_external_data_disks(
|
||||||
|
coresys: CoreSys,
|
||||||
|
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
|
):
|
||||||
|
"""Test migration stops when another hassos-data-external partition detected."""
|
||||||
|
datadisk_service: DataDiskService = all_dbus_services["agent_datadisk"]
|
||||||
|
datadisk_service.ChangeDevice.calls.clear()
|
||||||
|
datadisk_service.MarkDataMove.calls.clear()
|
||||||
|
|
||||||
|
sdb1_filesystem_service: FilesystemService = all_dbus_services[
|
||||||
|
"udisks2_filesystem"
|
||||||
|
]["/org/freedesktop/UDisks2/block_devices/sdb1"]
|
||||||
|
sdb1_filesystem_service.fixture = replace(
|
||||||
|
sdb1_filesystem_service.fixture, MountPoints=[]
|
||||||
|
)
|
||||||
|
coresys.os._available = True
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
HassOSDataDiskError,
|
||||||
|
match=r"Partition\(s\) /dev/sda1 have name 'hassos-data-external' which prevents migration",
|
||||||
|
):
|
||||||
|
await coresys.os.datadisk.migrate_disk("Generic-Flash-Disk-61BCDDB6")
|
||||||
|
|
||||||
|
assert datadisk_service.ChangeDevice.calls == []
|
||||||
|
assert datadisk_service.MarkDataMove.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_datadisk_migrate_between_external_renames(
|
||||||
|
coresys: CoreSys,
|
||||||
|
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
|
):
|
||||||
|
"""Test migration from one external data disk to another renames the original."""
|
||||||
|
sdb1_partition_service: PartitionService = all_dbus_services["udisks2_partition"][
|
||||||
|
"/org/freedesktop/UDisks2/block_devices/sdb1"
|
||||||
|
]
|
||||||
|
sdb1_partition_service.SetName.calls.clear()
|
||||||
|
|
||||||
|
sdb1_filesystem_service: FilesystemService = all_dbus_services[
|
||||||
|
"udisks2_filesystem"
|
||||||
|
]["/org/freedesktop/UDisks2/block_devices/sdb1"]
|
||||||
|
sdb1_filesystem_service.fixture = replace(
|
||||||
|
sdb1_filesystem_service.fixture, MountPoints=[]
|
||||||
|
)
|
||||||
|
sdb1_block_service: BlockService = all_dbus_services["udisks2_block"][
|
||||||
|
"/org/freedesktop/UDisks2/block_devices/sdb1"
|
||||||
|
]
|
||||||
|
sdb1_block_service.fixture = replace(sdb1_block_service.fixture, Size=250058113024)
|
||||||
|
|
||||||
|
datadisk_service: DataDiskService = all_dbus_services["agent_datadisk"]
|
||||||
|
datadisk_service.MarkDataMove.calls.clear()
|
||||||
|
datadisk_service.emit_properties_changed({"CurrentDevice": "/dev/sda1"})
|
||||||
|
await datadisk_service.ping()
|
||||||
|
|
||||||
|
all_dbus_services["os_agent"].emit_properties_changed({"Version": "1.5.0"})
|
||||||
|
await all_dbus_services["os_agent"].ping()
|
||||||
|
coresys.os._available = True
|
||||||
|
|
||||||
|
await coresys.os.datadisk.migrate_disk("Generic-Flash-Disk-61BCDDB6")
|
||||||
|
|
||||||
|
assert datadisk_service.MarkDataMove.calls == [tuple()]
|
||||||
|
assert sdb1_partition_service.SetName.calls == [
|
||||||
|
("hassos-data-external-old", {"auth.no_user_interaction": Variant("b", True)})
|
||||||
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user