Merge pull request #997 from mwhudson/imsm

support for pre-existing vroc containers and volumes
This commit is contained in:
Michael Hudson-Doyle 2021-07-07 08:48:18 +12:00 committed by GitHub
commit c31e2a060a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1822 additions and 22 deletions

View File

@ -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

1661
examples/imsm.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ parts:
plugin: python
source-type: git
source: https://git.launchpad.net/curtin
source-commit: d49d35bc6643b063f085d870ea94a53677ae141c
source-commit: 82765ab3e59273db0623337787209f0c9836935c
python-packages:
- pyyaml==5.3.1
- oauthlib

View File

@ -85,7 +85,12 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator):
await asyncio.sleep(0.1)
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')
self.ui.body.form.guided_choice.value = {
'disk': disk,

View File

@ -105,14 +105,24 @@ def _part_actions(part):
@_supported_actions.register(Raid)
def _raid_actions(raid):
return [
DeviceAction.EDIT,
DeviceAction.PARTITION,
DeviceAction.FORMAT,
DeviceAction.REMOVE,
DeviceAction.DELETE,
DeviceAction.REFORMAT,
]
if raid.raidlevel == "container":
return [
DeviceAction.EDIT,
DeviceAction.DELETE,
]
else:
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)
@ -340,6 +350,7 @@ _can_toggle_boot = make_checker(DeviceAction.TOGGLE_BOOT)
@_can_toggle_boot.register(Disk)
@_can_toggle_boot.register(Raid)
def _can_toggle_boot_disk(disk):
if boot.is_boot_device(disk):
for disk2 in boot.all_boot_devices(disk._m):

View File

@ -17,6 +17,7 @@ import functools
from subiquity.models.filesystem import (
Disk,
Raid,
Bootloader,
Partition,
)
@ -39,6 +40,16 @@ def _is_boot_device_disk(disk):
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
def can_be_boot_device(device, *, with_reformatting=False):
"""Can `device` be made into a boot device?
@ -66,6 +77,19 @@ def _can_be_boot_device_disk(disk, *, with_reformatting=False):
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
def is_esp(device):
"""Is `device` a UEFI ESP?"""
@ -78,7 +102,7 @@ def _is_esp_partition(partition):
return False
if partition.device.ptable == "gpt":
return partition.flag == "boot"
else:
elif isinstance(partition.device, Disk):
blockdev_raw = partition._m._probe_data['blockdev'].get(
partition._path())
if blockdev_raw is None:
@ -91,11 +115,14 @@ def _is_esp_partition(partition):
except ValueError:
# In case there was garbage in the udev entry...
return False
else:
return False
def all_boot_devices(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):

View File

@ -122,7 +122,17 @@ def _desc_partition(partition):
@desc.register(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)
@ -229,6 +239,16 @@ def _usage_labels_partition(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
def for_client(device, *, min_size=0):
"""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(Raid)
def _for_client_disk(disk, *, min_size=0):
return types.Disk(
id=disk.id,

View File

@ -134,6 +134,8 @@ class FilesystemManipulator:
if raid is None:
return
self.clear(raid)
for v in raid._subvolumes:
self.delete_raid(v)
for p in list(raid.partitions()):
self.delete_partition(p)
for d in set(raid.devices) | set(raid.spare_devices):
@ -354,7 +356,8 @@ class FilesystemManipulator:
p.wipe = 'zero'
p.grub_device = True
else:
new_boot_disk.preserve = False
if new_boot_disk.type == "disk":
new_boot_disk.preserve = False
if bootloader == Bootloader.UEFI:
part_size = UEFI_GRUB_SIZE_BYTES
if UEFI_GRUB_SIZE_BYTES*2 >= new_boot_disk.size:

View File

@ -173,6 +173,7 @@ raidlevels = [
RaidLevel(_("5"), "raid5", 3),
RaidLevel(_("6"), "raid6", 4),
RaidLevel(_("10"), "raid10", 4),
RaidLevel(_("Container"), "container", 2),
]
@ -315,7 +316,7 @@ def get_raid_size(level, devices):
min_size = min(sizes)
if min_size <= 0:
return 0
if level == "raid0":
if level == "raid0" or level == "container":
return sum(sizes)
elif level == "raid1":
return min_size
@ -685,7 +686,8 @@ class Partition(_Formattable):
class Raid(_Device):
name = attr.ib()
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):
# Surprisingly, the order of devices passed to mdadm --create
@ -700,6 +702,8 @@ class Raid(_Device):
wipe = attr.ib(default=None)
ptable = attributes.ptable()
metadata = attr.ib(default=None)
container = attributes.ref(backlink="_subvolumes", default=None) # Raid
_subvolumes = attributes.backlink(default=attr.Factory(list))
@property
def size(self):
@ -718,6 +722,11 @@ class Raid(_Device):
# partitions)
return self.size - 2*GPT_OVERHEAD
def available(self):
if self.raidlevel == "container":
return False
return super().available()
@property
def ok_for_raid(self):
if self._fs is not None:
@ -728,6 +737,8 @@ class Raid(_Device):
return False
if len(self._partitions) > 0:
return False
if self.raidlevel == "container":
return False
return True
ok_for_lvm_vg = ok_for_raid

View File

@ -51,6 +51,7 @@ from subiquity.common.types import (
)
from subiquity.models.filesystem import (
dehumanize_size,
Raid,
)
from subiquity.server.controller import (
SubiquityController,
@ -222,14 +223,27 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
return probe_resp
if not min_size:
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(
status=ProbeStatus.DONE,
error_report=self.full_probe_error(),
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)
])
disks=[labels.for_client(d, min_size=min_size) for d in disks])
async def guided_POST(self, choice: Optional[GuidedChoice]) \
-> StorageResponse:

View File

@ -78,6 +78,14 @@ class ConfirmDeleteStretchy(Stretchy):
if p not in [None, obj]:
rows.append(TableRow(cells))
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:
lines.append(Text(_("It is not formatted or mounted.")))

View File

@ -303,6 +303,7 @@ class DeviceList(WidgetWrap):
_raid_REFORMAT = _disk_REFORMAT
_raid_REMOVE = _disk_REMOVE
_raid_DELETE = _partition_DELETE
_raid_TOGGLE_BOOT = _disk_TOGGLE_BOOT
_lvm_volgroup_EDIT = _stretchy_shower(VolGroupStretchy)
_lvm_volgroup_CREATE_LV = _disk_PARTITION

View File

@ -56,7 +56,10 @@ log = logging.getLogger('subiquity.ui.raid')
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):