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):
return "{}{}".format(self.parent.action_id, self.partnum)
@property
def devpath(self):
return "/dev/{}".format(self.path)
@property
def size(self):
return self._size

View File

@ -140,10 +140,22 @@ class Blockdev():
self.lvm = []
self.holder = {}
@property
def blocktype(self):
return self.baseaction.type
@property
def devpath(self):
return self.disk.devpath
@property
def path(self):
return self.disk.devpath
@property
def model(self):
return self.disk.model
@property
def mounts(self):
return self._mounts.values()
@ -164,6 +176,11 @@ class Blockdev():
def partitions(self):
return self.disk.partitions
@property
def partnames(self):
return ['{}{}'.format(self.devpath, num) for (num, _) in
self.partitions.items()]
@property
def filesystems(self):
return self._filesystems
@ -182,6 +199,17 @@ class Blockdev():
return True
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
def mounted(self):
return self.is_mounted()
@ -272,6 +300,10 @@ class Blockdev():
log.debug('Partition Added')
return new_size
def get_partition(self, devpath):
[partnum] = re.findall('\d+$', devpath)
return self.partitions[partnum]
def set_holder(self, devpath, holdtype):
self.holder[holdtype] = devpath

View File

@ -15,6 +15,7 @@
import json
import logging
import re
from .blockdev import Blockdev, Raiddev
import math
@ -117,8 +118,7 @@ class FilesystemModel(ModelPolicy):
def reset(self):
log.debug('FilesystemModel: resetting disks')
for disk in self.devices.values():
disk.reset()
self.devices = {}
def get_signal_by_name(self, selection):
for x, y, z in self.get_signals():
@ -152,11 +152,21 @@ class FilesystemModel(ModelPolicy):
self.info[disk] = self.prober.get_storage_info(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))
if disk not in self.devices:
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
self.info[disk].model,
size=self.info[disk].size)
try:
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
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]
def get_available_disks(self):
@ -228,6 +238,7 @@ class FilesystemModel(ModelPolicy):
'hot_spares': '0',
'chunk_size': '4K',
}
could be /dev/sda1, /dev/md0, /dev/bcache1, /dev/vg_foo/foobar2?
'''
raid_devices = []
spare_devices = []
@ -238,11 +249,19 @@ class FilesystemModel(ModelPolicy):
# and then one partition of type raid
for (devpath, _, _) in all_devices:
disk = self.get_disk(devpath)
disk.add_partition(1, disk.freespace, None, None, flag='raid')
if len(raid_devices) + nr_spares < len(all_devices):
raid_devices.append(disk)
# add or update a partition to be raid type
if disk.path != devpath: # we must have got a partition
raiddev = disk.get_partition(devpath)
raiddev.flags = 'raid'
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
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_size = self.calculate_raid_size(raid_level, raid_devices,
spare_devices)
# create a Raiddev (pass in only the names)
raid_dev = Raiddev(raid_dev_name, raid_serial, raid_model,
raid_parttype, raid_size,
[d.devpath for d in raid_devices],
[d.path for d in raid_devices],
raid_level,
[d.devpath for d in spare_devices])
[d.path for d in spare_devices])
# add it to the model's info dict
raid_dev_info = {
@ -316,6 +336,31 @@ class FilesystemModel(ModelPolicy):
log.debug('bootable check: no disks have been marked bootable')
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):
return [dev.disk.devpath for dev in self.get_available_disks()]

View File

@ -47,18 +47,27 @@ class RaidView(ViewPolicy):
items = [
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(
[Color.info_minor(Text("No available disks."))])
for dname in avail_disks:
disk = self.model.get_disk_info(dname)
#device = self.model.get_disk(dname)
disk_sz = _humanize_size(disk.size)
disk_string = "{} {}, {}".format(disk.name,
for dname in avail_devs:
device = self.model.get_disk(dname)
if device.path != dname:
# we've got a partition
raiddev = device.get_partition(dname)
else:
raiddev = device
disk_sz = _humanize_size(raiddev.size)
disk_string = "{} {}, {}".format(dname,
disk_sz,
disk.model)
device.model)
log.debug('raid: disk_string={}'.format(disk_string))
self.selected_disks.append(CheckBox(disk_string))