Merge pull request #997 from mwhudson/imsm
support for pre-existing vroc containers and volumes
This commit is contained in:
commit
c31e2a060a
|
@ -0,0 +1,35 @@
|
||||||
|
#machine-config: examples/imsm.json
|
||||||
|
Welcome:
|
||||||
|
lang: en_US
|
||||||
|
Refresh:
|
||||||
|
update: no
|
||||||
|
Keyboard:
|
||||||
|
layout: us
|
||||||
|
Network:
|
||||||
|
accept-default: yes
|
||||||
|
Proxy:
|
||||||
|
proxy: ""
|
||||||
|
Mirror:
|
||||||
|
mirror: "http://us.archive.ubuntu.com"
|
||||||
|
Filesystem:
|
||||||
|
guided: yes
|
||||||
|
guided-label: "md126"
|
||||||
|
Identity:
|
||||||
|
realname: Ubuntu
|
||||||
|
username: ubuntu
|
||||||
|
hostname: ubuntu-server
|
||||||
|
# ubuntu
|
||||||
|
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
|
||||||
|
SSH:
|
||||||
|
install_server: true
|
||||||
|
pwauth: false
|
||||||
|
authorized_keys:
|
||||||
|
- |
|
||||||
|
ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAA # ssh-import-id lp:subiquity
|
||||||
|
SnapList:
|
||||||
|
snaps:
|
||||||
|
hello:
|
||||||
|
channel: stable
|
||||||
|
is_classic: false
|
||||||
|
InstallProgress:
|
||||||
|
reboot: yes
|
File diff suppressed because it is too large
Load Diff
|
@ -34,7 +34,7 @@ parts:
|
||||||
plugin: python
|
plugin: python
|
||||||
source-type: git
|
source-type: git
|
||||||
source: https://git.launchpad.net/curtin
|
source: https://git.launchpad.net/curtin
|
||||||
source-commit: d49d35bc6643b063f085d870ea94a53677ae141c
|
source-commit: 82765ab3e59273db0623337787209f0c9836935c
|
||||||
python-packages:
|
python-packages:
|
||||||
- pyyaml==5.3.1
|
- pyyaml==5.3.1
|
||||||
- oauthlib
|
- oauthlib
|
||||||
|
|
|
@ -85,7 +85,12 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator):
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
if self.answers['guided']:
|
if self.answers['guided']:
|
||||||
disk = self.ui.body.form.disks[self.answers['guided-index']]
|
if 'guided-index' in self.answers:
|
||||||
|
disk = self.ui.body.form.disks[self.answers['guided-index']]
|
||||||
|
elif 'guided-label' in self.answers:
|
||||||
|
label = self.answers['guided-label']
|
||||||
|
[disk] = [d for d in self.ui.body.form.disks
|
||||||
|
if d.label == label]
|
||||||
method = self.answers.get('guided-method')
|
method = self.answers.get('guided-method')
|
||||||
self.ui.body.form.guided_choice.value = {
|
self.ui.body.form.guided_choice.value = {
|
||||||
'disk': disk,
|
'disk': disk,
|
||||||
|
|
|
@ -105,14 +105,24 @@ def _part_actions(part):
|
||||||
|
|
||||||
@_supported_actions.register(Raid)
|
@_supported_actions.register(Raid)
|
||||||
def _raid_actions(raid):
|
def _raid_actions(raid):
|
||||||
return [
|
if raid.raidlevel == "container":
|
||||||
DeviceAction.EDIT,
|
return [
|
||||||
DeviceAction.PARTITION,
|
DeviceAction.EDIT,
|
||||||
DeviceAction.FORMAT,
|
DeviceAction.DELETE,
|
||||||
DeviceAction.REMOVE,
|
]
|
||||||
DeviceAction.DELETE,
|
else:
|
||||||
DeviceAction.REFORMAT,
|
actions = [
|
||||||
]
|
DeviceAction.EDIT,
|
||||||
|
DeviceAction.PARTITION,
|
||||||
|
DeviceAction.FORMAT,
|
||||||
|
DeviceAction.REMOVE,
|
||||||
|
DeviceAction.DELETE,
|
||||||
|
DeviceAction.REFORMAT,
|
||||||
|
]
|
||||||
|
if raid._m.bootloader == Bootloader.UEFI:
|
||||||
|
if raid.container and raid.container.metadata == 'imsm':
|
||||||
|
actions.append(DeviceAction.TOGGLE_BOOT)
|
||||||
|
return actions
|
||||||
|
|
||||||
|
|
||||||
@_supported_actions.register(LVM_VolGroup)
|
@_supported_actions.register(LVM_VolGroup)
|
||||||
|
@ -340,6 +350,7 @@ _can_toggle_boot = make_checker(DeviceAction.TOGGLE_BOOT)
|
||||||
|
|
||||||
|
|
||||||
@_can_toggle_boot.register(Disk)
|
@_can_toggle_boot.register(Disk)
|
||||||
|
@_can_toggle_boot.register(Raid)
|
||||||
def _can_toggle_boot_disk(disk):
|
def _can_toggle_boot_disk(disk):
|
||||||
if boot.is_boot_device(disk):
|
if boot.is_boot_device(disk):
|
||||||
for disk2 in boot.all_boot_devices(disk._m):
|
for disk2 in boot.all_boot_devices(disk._m):
|
||||||
|
|
|
@ -17,6 +17,7 @@ import functools
|
||||||
|
|
||||||
from subiquity.models.filesystem import (
|
from subiquity.models.filesystem import (
|
||||||
Disk,
|
Disk,
|
||||||
|
Raid,
|
||||||
Bootloader,
|
Bootloader,
|
||||||
Partition,
|
Partition,
|
||||||
)
|
)
|
||||||
|
@ -39,6 +40,16 @@ def _is_boot_device_disk(disk):
|
||||||
return any(p.grub_device for p in disk._partitions)
|
return any(p.grub_device for p in disk._partitions)
|
||||||
|
|
||||||
|
|
||||||
|
@is_boot_device.register(Raid)
|
||||||
|
def _is_boot_device_raid(raid):
|
||||||
|
bl = raid._m.bootloader
|
||||||
|
if bl != Bootloader.UEFI:
|
||||||
|
return False
|
||||||
|
if not raid.container or raid.container.metadata != 'imsm':
|
||||||
|
return False
|
||||||
|
return any(p.grub_device for p in raid._partitions)
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
def can_be_boot_device(device, *, with_reformatting=False):
|
def can_be_boot_device(device, *, with_reformatting=False):
|
||||||
"""Can `device` be made into a boot device?
|
"""Can `device` be made into a boot device?
|
||||||
|
@ -66,6 +77,19 @@ def _can_be_boot_device_disk(disk, *, with_reformatting=False):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@can_be_boot_device.register(Raid)
|
||||||
|
def _can_be_boot_device_raid(raid, *, with_reformatting=False):
|
||||||
|
bl = raid._m.bootloader
|
||||||
|
if bl != Bootloader.UEFI:
|
||||||
|
return False
|
||||||
|
if not raid.container or raid.container.metadata != 'imsm':
|
||||||
|
return False
|
||||||
|
if raid._has_preexisting_partition() and not with_reformatting:
|
||||||
|
return any(is_esp(p) for p in raid._partitions)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
def is_esp(device):
|
def is_esp(device):
|
||||||
"""Is `device` a UEFI ESP?"""
|
"""Is `device` a UEFI ESP?"""
|
||||||
|
@ -78,7 +102,7 @@ def _is_esp_partition(partition):
|
||||||
return False
|
return False
|
||||||
if partition.device.ptable == "gpt":
|
if partition.device.ptable == "gpt":
|
||||||
return partition.flag == "boot"
|
return partition.flag == "boot"
|
||||||
else:
|
elif isinstance(partition.device, Disk):
|
||||||
blockdev_raw = partition._m._probe_data['blockdev'].get(
|
blockdev_raw = partition._m._probe_data['blockdev'].get(
|
||||||
partition._path())
|
partition._path())
|
||||||
if blockdev_raw is None:
|
if blockdev_raw is None:
|
||||||
|
@ -91,11 +115,14 @@ def _is_esp_partition(partition):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# In case there was garbage in the udev entry...
|
# In case there was garbage in the udev entry...
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def all_boot_devices(model):
|
def all_boot_devices(model):
|
||||||
"""Return all current boot devices for `model`."""
|
"""Return all current boot devices for `model`."""
|
||||||
return [disk for disk in model.all_disks() if is_boot_device(disk)]
|
candidates = model.all_disks() + model.all_raids()
|
||||||
|
return [cand for cand in candidates if is_boot_device(cand)]
|
||||||
|
|
||||||
|
|
||||||
def is_bootloader_partition(partition):
|
def is_bootloader_partition(partition):
|
||||||
|
|
|
@ -122,7 +122,17 @@ def _desc_partition(partition):
|
||||||
|
|
||||||
@desc.register(Raid)
|
@desc.register(Raid)
|
||||||
def _desc_raid(raid):
|
def _desc_raid(raid):
|
||||||
return _("software RAID {level}").format(level=raid.raidlevel[4:])
|
level = raid.raidlevel
|
||||||
|
if level.lower().startswith('raid'):
|
||||||
|
level = level[4:]
|
||||||
|
if raid.container:
|
||||||
|
raid_type = raid.container.metadata
|
||||||
|
elif raid.metadata == "imsm":
|
||||||
|
raid_type = 'imsm' # maybe VROC?
|
||||||
|
else:
|
||||||
|
raid_type = _("software")
|
||||||
|
return _("{type} RAID {level}").format(
|
||||||
|
type=raid_type, level=level)
|
||||||
|
|
||||||
|
|
||||||
@desc.register(LVM_VolGroup)
|
@desc.register(LVM_VolGroup)
|
||||||
|
@ -229,6 +239,16 @@ def _usage_labels_partition(partition):
|
||||||
return _usage_labels_generic(partition)
|
return _usage_labels_generic(partition)
|
||||||
|
|
||||||
|
|
||||||
|
@usage_labels.register(Raid)
|
||||||
|
def _usage_labels_raid(raid):
|
||||||
|
if raid.metadata == 'imsm' and raid._subvolumes:
|
||||||
|
return [
|
||||||
|
_('container for {devices}').format(
|
||||||
|
devices=', '.join([label(v) for v in raid._subvolumes]))
|
||||||
|
]
|
||||||
|
return _usage_labels_generic(raid)
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
def for_client(device, *, min_size=0):
|
def for_client(device, *, min_size=0):
|
||||||
"""Return an API-friendly description of `device`"""
|
"""Return an API-friendly description of `device`"""
|
||||||
|
@ -236,6 +256,7 @@ def for_client(device, *, min_size=0):
|
||||||
|
|
||||||
|
|
||||||
@for_client.register(Disk)
|
@for_client.register(Disk)
|
||||||
|
@for_client.register(Raid)
|
||||||
def _for_client_disk(disk, *, min_size=0):
|
def _for_client_disk(disk, *, min_size=0):
|
||||||
return types.Disk(
|
return types.Disk(
|
||||||
id=disk.id,
|
id=disk.id,
|
||||||
|
|
|
@ -134,6 +134,8 @@ class FilesystemManipulator:
|
||||||
if raid is None:
|
if raid is None:
|
||||||
return
|
return
|
||||||
self.clear(raid)
|
self.clear(raid)
|
||||||
|
for v in raid._subvolumes:
|
||||||
|
self.delete_raid(v)
|
||||||
for p in list(raid.partitions()):
|
for p in list(raid.partitions()):
|
||||||
self.delete_partition(p)
|
self.delete_partition(p)
|
||||||
for d in set(raid.devices) | set(raid.spare_devices):
|
for d in set(raid.devices) | set(raid.spare_devices):
|
||||||
|
@ -354,7 +356,8 @@ class FilesystemManipulator:
|
||||||
p.wipe = 'zero'
|
p.wipe = 'zero'
|
||||||
p.grub_device = True
|
p.grub_device = True
|
||||||
else:
|
else:
|
||||||
new_boot_disk.preserve = False
|
if new_boot_disk.type == "disk":
|
||||||
|
new_boot_disk.preserve = False
|
||||||
if bootloader == Bootloader.UEFI:
|
if bootloader == Bootloader.UEFI:
|
||||||
part_size = UEFI_GRUB_SIZE_BYTES
|
part_size = UEFI_GRUB_SIZE_BYTES
|
||||||
if UEFI_GRUB_SIZE_BYTES*2 >= new_boot_disk.size:
|
if UEFI_GRUB_SIZE_BYTES*2 >= new_boot_disk.size:
|
||||||
|
|
|
@ -173,6 +173,7 @@ raidlevels = [
|
||||||
RaidLevel(_("5"), "raid5", 3),
|
RaidLevel(_("5"), "raid5", 3),
|
||||||
RaidLevel(_("6"), "raid6", 4),
|
RaidLevel(_("6"), "raid6", 4),
|
||||||
RaidLevel(_("10"), "raid10", 4),
|
RaidLevel(_("10"), "raid10", 4),
|
||||||
|
RaidLevel(_("Container"), "container", 2),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -315,7 +316,7 @@ def get_raid_size(level, devices):
|
||||||
min_size = min(sizes)
|
min_size = min(sizes)
|
||||||
if min_size <= 0:
|
if min_size <= 0:
|
||||||
return 0
|
return 0
|
||||||
if level == "raid0":
|
if level == "raid0" or level == "container":
|
||||||
return sum(sizes)
|
return sum(sizes)
|
||||||
elif level == "raid1":
|
elif level == "raid1":
|
||||||
return min_size
|
return min_size
|
||||||
|
@ -685,7 +686,8 @@ class Partition(_Formattable):
|
||||||
class Raid(_Device):
|
class Raid(_Device):
|
||||||
name = attr.ib()
|
name = attr.ib()
|
||||||
raidlevel = attr.ib(converter=lambda x: raidlevels_by_value[x].value)
|
raidlevel = attr.ib(converter=lambda x: raidlevels_by_value[x].value)
|
||||||
devices = attributes.reflist(backlink="_constructed_device")
|
devices = attributes.reflist(
|
||||||
|
backlink="_constructed_device", default=attr.Factory(set))
|
||||||
|
|
||||||
def serialize_devices(self):
|
def serialize_devices(self):
|
||||||
# Surprisingly, the order of devices passed to mdadm --create
|
# Surprisingly, the order of devices passed to mdadm --create
|
||||||
|
@ -700,6 +702,8 @@ class Raid(_Device):
|
||||||
wipe = attr.ib(default=None)
|
wipe = attr.ib(default=None)
|
||||||
ptable = attributes.ptable()
|
ptable = attributes.ptable()
|
||||||
metadata = attr.ib(default=None)
|
metadata = attr.ib(default=None)
|
||||||
|
container = attributes.ref(backlink="_subvolumes", default=None) # Raid
|
||||||
|
_subvolumes = attributes.backlink(default=attr.Factory(list))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
|
@ -718,6 +722,11 @@ class Raid(_Device):
|
||||||
# partitions)
|
# partitions)
|
||||||
return self.size - 2*GPT_OVERHEAD
|
return self.size - 2*GPT_OVERHEAD
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
if self.raidlevel == "container":
|
||||||
|
return False
|
||||||
|
return super().available()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ok_for_raid(self):
|
def ok_for_raid(self):
|
||||||
if self._fs is not None:
|
if self._fs is not None:
|
||||||
|
@ -728,6 +737,8 @@ class Raid(_Device):
|
||||||
return False
|
return False
|
||||||
if len(self._partitions) > 0:
|
if len(self._partitions) > 0:
|
||||||
return False
|
return False
|
||||||
|
if self.raidlevel == "container":
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
ok_for_lvm_vg = ok_for_raid
|
ok_for_lvm_vg = ok_for_raid
|
||||||
|
|
|
@ -51,6 +51,7 @@ from subiquity.common.types import (
|
||||||
)
|
)
|
||||||
from subiquity.models.filesystem import (
|
from subiquity.models.filesystem import (
|
||||||
dehumanize_size,
|
dehumanize_size,
|
||||||
|
Raid,
|
||||||
)
|
)
|
||||||
from subiquity.server.controller import (
|
from subiquity.server.controller import (
|
||||||
SubiquityController,
|
SubiquityController,
|
||||||
|
@ -222,14 +223,27 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
return probe_resp
|
return probe_resp
|
||||||
if not min_size:
|
if not min_size:
|
||||||
min_size = DEFAULT_MIN_SIZE_GUIDED
|
min_size = DEFAULT_MIN_SIZE_GUIDED
|
||||||
|
disks = []
|
||||||
|
for raid in self.model._all(type='raid'):
|
||||||
|
if not boot.can_be_boot_device(raid, with_reformatting=True):
|
||||||
|
continue
|
||||||
|
disks.append(raid)
|
||||||
|
for disk in self.model._all(type='disk'):
|
||||||
|
if not boot.can_be_boot_device(disk, with_reformatting=True):
|
||||||
|
continue
|
||||||
|
cd = disk.constructed_device()
|
||||||
|
if isinstance(cd, Raid):
|
||||||
|
can_be_boot = False
|
||||||
|
for v in cd._subvolumes:
|
||||||
|
if boot.can_be_boot_device(v, with_reformatting=True):
|
||||||
|
can_be_boot = True
|
||||||
|
if can_be_boot:
|
||||||
|
continue
|
||||||
|
disks.append(disk)
|
||||||
return GuidedStorageResponse(
|
return GuidedStorageResponse(
|
||||||
status=ProbeStatus.DONE,
|
status=ProbeStatus.DONE,
|
||||||
error_report=self.full_probe_error(),
|
error_report=self.full_probe_error(),
|
||||||
disks=[
|
disks=[labels.for_client(d, min_size=min_size) for d in disks])
|
||||||
labels.for_client(device, min_size=min_size)
|
|
||||||
for device in self.model._actions
|
|
||||||
if boot.can_be_boot_device(device, with_reformatting=True)
|
|
||||||
])
|
|
||||||
|
|
||||||
async def guided_POST(self, choice: Optional[GuidedChoice]) \
|
async def guided_POST(self, choice: Optional[GuidedChoice]) \
|
||||||
-> StorageResponse:
|
-> StorageResponse:
|
||||||
|
|
|
@ -78,6 +78,14 @@ class ConfirmDeleteStretchy(Stretchy):
|
||||||
if p not in [None, obj]:
|
if p not in [None, obj]:
|
||||||
rows.append(TableRow(cells))
|
rows.append(TableRow(cells))
|
||||||
lines.append(TablePile(rows))
|
lines.append(TablePile(rows))
|
||||||
|
elif obj.type == "raid" and obj._subvolumes:
|
||||||
|
n = len(obj._subvolumes)
|
||||||
|
line = ngettext(
|
||||||
|
"It contains 1 volume.",
|
||||||
|
"It contains {n} volumes.",
|
||||||
|
n)
|
||||||
|
lines.append(Text(line.format(n=n)))
|
||||||
|
lines.append(Text(""))
|
||||||
else:
|
else:
|
||||||
lines.append(Text(_("It is not formatted or mounted.")))
|
lines.append(Text(_("It is not formatted or mounted.")))
|
||||||
|
|
||||||
|
|
|
@ -303,6 +303,7 @@ class DeviceList(WidgetWrap):
|
||||||
_raid_REFORMAT = _disk_REFORMAT
|
_raid_REFORMAT = _disk_REFORMAT
|
||||||
_raid_REMOVE = _disk_REMOVE
|
_raid_REMOVE = _disk_REMOVE
|
||||||
_raid_DELETE = _partition_DELETE
|
_raid_DELETE = _partition_DELETE
|
||||||
|
_raid_TOGGLE_BOOT = _disk_TOGGLE_BOOT
|
||||||
|
|
||||||
_lvm_volgroup_EDIT = _stretchy_shower(VolGroupStretchy)
|
_lvm_volgroup_EDIT = _stretchy_shower(VolGroupStretchy)
|
||||||
_lvm_volgroup_CREATE_LV = _disk_PARTITION
|
_lvm_volgroup_CREATE_LV = _disk_PARTITION
|
||||||
|
|
|
@ -56,7 +56,10 @@ log = logging.getLogger('subiquity.ui.raid')
|
||||||
|
|
||||||
|
|
||||||
raidlevel_choices = [
|
raidlevel_choices = [
|
||||||
Option((_(level.name), True, level)) for level in raidlevels]
|
Option((_(level.name), True, level))
|
||||||
|
for level in raidlevels
|
||||||
|
if level.value != "container"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class RaidnameEditor(StringEditor, WantsToKnowFormField):
|
class RaidnameEditor(StringEditor, WantsToKnowFormField):
|
||||||
|
|
Loading…
Reference in New Issue