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
|
||||
source-type: git
|
||||
source: https://git.launchpad.net/curtin
|
||||
source-commit: d49d35bc6643b063f085d870ea94a53677ae141c
|
||||
source-commit: 82765ab3e59273db0623337787209f0c9836935c
|
||||
python-packages:
|
||||
- pyyaml==5.3.1
|
||||
- oauthlib
|
||||
|
|
|
@ -85,7 +85,12 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator):
|
|||
await asyncio.sleep(0.1)
|
||||
|
||||
if self.answers['guided']:
|
||||
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,
|
||||
|
|
|
@ -105,7 +105,13 @@ def _part_actions(part):
|
|||
|
||||
@_supported_actions.register(Raid)
|
||||
def _raid_actions(raid):
|
||||
if raid.raidlevel == "container":
|
||||
return [
|
||||
DeviceAction.EDIT,
|
||||
DeviceAction.DELETE,
|
||||
]
|
||||
else:
|
||||
actions = [
|
||||
DeviceAction.EDIT,
|
||||
DeviceAction.PARTITION,
|
||||
DeviceAction.FORMAT,
|
||||
|
@ -113,6 +119,10 @@ def _raid_actions(raid):
|
|||
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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,6 +356,7 @@ class FilesystemManipulator:
|
|||
p.wipe = 'zero'
|
||||
p.grub_device = True
|
||||
else:
|
||||
if new_boot_disk.type == "disk":
|
||||
new_boot_disk.preserve = False
|
||||
if bootloader == Bootloader.UEFI:
|
||||
part_size = UEFI_GRUB_SIZE_BYTES
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.")))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue