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:
Ryan Harper 2015-10-05 10:56:59 -05:00
parent 37be0c65ee
commit fd3ddff832
4 changed files with 109 additions and 19 deletions

View File

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

View File

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

View File

@ -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()]

View File

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