Fix handling of empty disks and partitions for raid menu
mdadm supports using whole disks and empty partitions. Update model to define empty disks and empty partitions. This allows one to construct a raid, and then use that raid to construct further raid (raid0 multiple raid1s). Signed-off-by: Ryan Harper <ryan.harper@canonical.com>
This commit is contained in:
parent
37be0c65ee
commit
fd3ddff832
|
@ -159,6 +159,10 @@ class PartitionAction(DiskAction):
|
||||||
def path(self):
|
def path(self):
|
||||||
return "{}{}".format(self.parent.action_id, self.partnum)
|
return "{}{}".format(self.parent.action_id, self.partnum)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def devpath(self):
|
||||||
|
return "/dev/{}".format(self.path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
return self._size
|
return self._size
|
||||||
|
|
|
@ -140,10 +140,22 @@ class Blockdev():
|
||||||
self.lvm = []
|
self.lvm = []
|
||||||
self.holder = {}
|
self.holder = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def blocktype(self):
|
||||||
|
return self.baseaction.type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devpath(self):
|
def devpath(self):
|
||||||
return self.disk.devpath
|
return self.disk.devpath
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self.disk.devpath
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model(self):
|
||||||
|
return self.disk.model
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mounts(self):
|
def mounts(self):
|
||||||
return self._mounts.values()
|
return self._mounts.values()
|
||||||
|
@ -164,6 +176,11 @@ class Blockdev():
|
||||||
def partitions(self):
|
def partitions(self):
|
||||||
return self.disk.partitions
|
return self.disk.partitions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def partnames(self):
|
||||||
|
return ['{}{}'.format(self.devpath, num) for (num, _) in
|
||||||
|
self.partitions.items()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filesystems(self):
|
def filesystems(self):
|
||||||
return self._filesystems
|
return self._filesystems
|
||||||
|
@ -182,6 +199,17 @@ class Blockdev():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_partitions(self):
|
||||||
|
''' return list of non-zero sized partitions
|
||||||
|
defined but not mounted or formatted or used in
|
||||||
|
raid, lvm, bcache'''
|
||||||
|
return [part.devpath for (num, part) in self.partitions.items()
|
||||||
|
if part.size > 0 and
|
||||||
|
part.flags not in ['raid', 'lvm', 'bcache'] and
|
||||||
|
(part.devpath not in self._mounts.keys() or
|
||||||
|
part.devpath not in self._filesystems.keys())]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mounted(self):
|
def mounted(self):
|
||||||
return self.is_mounted()
|
return self.is_mounted()
|
||||||
|
@ -272,6 +300,10 @@ class Blockdev():
|
||||||
log.debug('Partition Added')
|
log.debug('Partition Added')
|
||||||
return new_size
|
return new_size
|
||||||
|
|
||||||
|
def get_partition(self, devpath):
|
||||||
|
[partnum] = re.findall('\d+$', devpath)
|
||||||
|
return self.partitions[partnum]
|
||||||
|
|
||||||
def set_holder(self, devpath, holdtype):
|
def set_holder(self, devpath, holdtype):
|
||||||
self.holder[holdtype] = devpath
|
self.holder[holdtype] = devpath
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
from .blockdev import Blockdev, Raiddev
|
from .blockdev import Blockdev, Raiddev
|
||||||
import math
|
import math
|
||||||
|
@ -117,8 +118,7 @@ class FilesystemModel(ModelPolicy):
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
log.debug('FilesystemModel: resetting disks')
|
log.debug('FilesystemModel: resetting disks')
|
||||||
for disk in self.devices.values():
|
self.devices = {}
|
||||||
disk.reset()
|
|
||||||
|
|
||||||
def get_signal_by_name(self, selection):
|
def get_signal_by_name(self, selection):
|
||||||
for x, y, z in self.get_signals():
|
for x, y, z in self.get_signals():
|
||||||
|
@ -152,11 +152,21 @@ class FilesystemModel(ModelPolicy):
|
||||||
self.info[disk] = self.prober.get_storage_info(disk)
|
self.info[disk] = self.prober.get_storage_info(disk)
|
||||||
|
|
||||||
def get_disk(self, disk):
|
def get_disk(self, disk):
|
||||||
|
'''get disk object given path. If provided a partition, then
|
||||||
|
return the parent disk. /dev/sda2 --> /dev/sda obj'''
|
||||||
log.debug('probe_storage: get_disk({})'.format(disk))
|
log.debug('probe_storage: get_disk({})'.format(disk))
|
||||||
|
|
||||||
if disk not in self.devices:
|
if disk not in self.devices:
|
||||||
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
|
try:
|
||||||
self.info[disk].model,
|
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
|
||||||
size=self.info[disk].size)
|
self.info[disk].model,
|
||||||
|
size=self.info[disk].size)
|
||||||
|
except KeyError:
|
||||||
|
''' if it looks like a partition, try again with
|
||||||
|
parent device '''
|
||||||
|
if disk[-1].isdigit():
|
||||||
|
return self.get_disk(re.split('[\d+]', disk)[0])
|
||||||
|
|
||||||
return self.devices[disk]
|
return self.devices[disk]
|
||||||
|
|
||||||
def get_available_disks(self):
|
def get_available_disks(self):
|
||||||
|
@ -228,6 +238,7 @@ class FilesystemModel(ModelPolicy):
|
||||||
'hot_spares': '0',
|
'hot_spares': '0',
|
||||||
'chunk_size': '4K',
|
'chunk_size': '4K',
|
||||||
}
|
}
|
||||||
|
could be /dev/sda1, /dev/md0, /dev/bcache1, /dev/vg_foo/foobar2?
|
||||||
'''
|
'''
|
||||||
raid_devices = []
|
raid_devices = []
|
||||||
spare_devices = []
|
spare_devices = []
|
||||||
|
@ -238,11 +249,19 @@ class FilesystemModel(ModelPolicy):
|
||||||
# and then one partition of type raid
|
# and then one partition of type raid
|
||||||
for (devpath, _, _) in all_devices:
|
for (devpath, _, _) in all_devices:
|
||||||
disk = self.get_disk(devpath)
|
disk = self.get_disk(devpath)
|
||||||
disk.add_partition(1, disk.freespace, None, None, flag='raid')
|
|
||||||
if len(raid_devices) + nr_spares < len(all_devices):
|
# add or update a partition to be raid type
|
||||||
raid_devices.append(disk)
|
if disk.path != devpath: # we must have got a partition
|
||||||
|
raiddev = disk.get_partition(devpath)
|
||||||
|
raiddev.flags = 'raid'
|
||||||
else:
|
else:
|
||||||
spare_devices.append(disk)
|
disk.add_partition(1, disk.freespace, None, None, flag='raid')
|
||||||
|
raiddev = disk
|
||||||
|
|
||||||
|
if len(raid_devices) + nr_spares < len(all_devices):
|
||||||
|
raid_devices.append(raiddev)
|
||||||
|
else:
|
||||||
|
spare_devices.append(raiddev)
|
||||||
|
|
||||||
# auto increment md number based in registered devices
|
# auto increment md number based in registered devices
|
||||||
raid_dev_name = '/dev/md{}'.format(len(self.raid_devices))
|
raid_dev_name = '/dev/md{}'.format(len(self.raid_devices))
|
||||||
|
@ -252,12 +271,13 @@ class FilesystemModel(ModelPolicy):
|
||||||
raid_level = int(raidspec.get('raid_level'))
|
raid_level = int(raidspec.get('raid_level'))
|
||||||
raid_size = self.calculate_raid_size(raid_level, raid_devices,
|
raid_size = self.calculate_raid_size(raid_level, raid_devices,
|
||||||
spare_devices)
|
spare_devices)
|
||||||
|
|
||||||
# create a Raiddev (pass in only the names)
|
# create a Raiddev (pass in only the names)
|
||||||
raid_dev = Raiddev(raid_dev_name, raid_serial, raid_model,
|
raid_dev = Raiddev(raid_dev_name, raid_serial, raid_model,
|
||||||
raid_parttype, raid_size,
|
raid_parttype, raid_size,
|
||||||
[d.devpath for d in raid_devices],
|
[d.path for d in raid_devices],
|
||||||
raid_level,
|
raid_level,
|
||||||
[d.devpath for d in spare_devices])
|
[d.path for d in spare_devices])
|
||||||
|
|
||||||
# add it to the model's info dict
|
# add it to the model's info dict
|
||||||
raid_dev_info = {
|
raid_dev_info = {
|
||||||
|
@ -316,6 +336,31 @@ class FilesystemModel(ModelPolicy):
|
||||||
log.debug('bootable check: no disks have been marked bootable')
|
log.debug('bootable check: no disks have been marked bootable')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_empty_disks(self):
|
||||||
|
''' empty disk is one that does not have any
|
||||||
|
partitions, filesystems or mounts, and is non-zero size '''
|
||||||
|
empty = []
|
||||||
|
for dev in self.get_available_disks():
|
||||||
|
if len(dev.partitions) == 0 and \
|
||||||
|
len(dev.mounts) == 0 and \
|
||||||
|
len(dev.filesystems) == 0:
|
||||||
|
empty.append(dev)
|
||||||
|
log.debug('empty_disks: {}'.format(", ".join([dev.path for dev in empty])))
|
||||||
|
return empty
|
||||||
|
|
||||||
|
def get_empty_disk_names(self):
|
||||||
|
return [dev.disk.devpath for dev in self.get_empty_disks()]
|
||||||
|
|
||||||
|
def get_empty_partition_names(self):
|
||||||
|
''' empty partitions have non-zero size, but are not part
|
||||||
|
of a filesystem or mount point or other raid '''
|
||||||
|
empty = []
|
||||||
|
for dev in self.get_available_disks():
|
||||||
|
empty += dev.available_partitions
|
||||||
|
|
||||||
|
log.debug('empty_partitions: {}'.format(", ".join(empty)))
|
||||||
|
return empty
|
||||||
|
|
||||||
def get_available_disk_names(self):
|
def get_available_disk_names(self):
|
||||||
return [dev.disk.devpath for dev in self.get_available_disks()]
|
return [dev.disk.devpath for dev in self.get_available_disks()]
|
||||||
|
|
||||||
|
|
|
@ -47,18 +47,27 @@ class RaidView(ViewPolicy):
|
||||||
items = [
|
items = [
|
||||||
Text("DISK SELECTION")
|
Text("DISK SELECTION")
|
||||||
]
|
]
|
||||||
avail_disks = self.model.get_available_disk_names()
|
|
||||||
if len(avail_disks) == 0:
|
# raid can use empty whole disks, or empty partitions
|
||||||
|
avail_disks = self.model.get_empty_disk_names()
|
||||||
|
avail_parts = self.model.get_empty_partition_names()
|
||||||
|
avail_devs = sorted(avail_disks + avail_parts)
|
||||||
|
if len(avail_devs) == 0:
|
||||||
return items.append(
|
return items.append(
|
||||||
[Color.info_minor(Text("No available disks."))])
|
[Color.info_minor(Text("No available disks."))])
|
||||||
|
|
||||||
for dname in avail_disks:
|
for dname in avail_devs:
|
||||||
disk = self.model.get_disk_info(dname)
|
device = self.model.get_disk(dname)
|
||||||
#device = self.model.get_disk(dname)
|
if device.path != dname:
|
||||||
disk_sz = _humanize_size(disk.size)
|
# we've got a partition
|
||||||
disk_string = "{} {}, {}".format(disk.name,
|
raiddev = device.get_partition(dname)
|
||||||
|
else:
|
||||||
|
raiddev = device
|
||||||
|
|
||||||
|
disk_sz = _humanize_size(raiddev.size)
|
||||||
|
disk_string = "{} {}, {}".format(dname,
|
||||||
disk_sz,
|
disk_sz,
|
||||||
disk.model)
|
device.model)
|
||||||
log.debug('raid: disk_string={}'.format(disk_string))
|
log.debug('raid: disk_string={}'.format(disk_string))
|
||||||
self.selected_disks.append(CheckBox(disk_string))
|
self.selected_disks.append(CheckBox(disk_string))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue