add Raid model objects

This commit is contained in:
Michael Hudson-Doyle 2018-06-26 11:42:03 +12:00
parent b7f8b7b9a2
commit f207a567c3
1 changed files with 114 additions and 9 deletions

View File

@ -23,7 +23,6 @@ import math
import os import os
import sys import sys
HUMAN_UNITS = ['B', 'K', 'M', 'G', 'T', 'P']
log = logging.getLogger('subiquity.models.filesystem') log = logging.getLogger('subiquity.models.filesystem')
@ -33,6 +32,27 @@ class FS:
is_mounted = attr.ib() is_mounted = attr.ib()
@attr.s(cmp=False)
class RaidLevel:
name = attr.ib()
value = attr.ib()
min_devices = attr.ib()
supports_spares = attr.ib(default=True)
raidlevels = [
RaidLevel(_("0 (striped)"), 0, 2, False),
RaidLevel(_("1 (mirrored)"), 1, 2),
RaidLevel(_("5"), 5, 3),
RaidLevel(_("6"), 6, 4),
RaidLevel(_("10"), 10, 4),
]
raidlevels_by_value = {l.value: l for l in raidlevels}
HUMAN_UNITS = ['B', 'K', 'M', 'G', 'T', 'P']
def humanize_size(size): def humanize_size(size):
if size == 0: if size == 0:
return "0B" return "0B"
@ -86,6 +106,26 @@ def dehumanize_size(size):
return num * mult // div return num * mult // div
def get_raid_size(level, devices):
if len(devices) == 0:
return 0
min_size = min(dev.size for dev in devices)
if min_size <= 0:
return 0
if level == 0:
return min_size * len(devices)
elif level == 1:
return min_size
elif level == 5:
return min_size * (len(devices) - 1)
elif level == 6:
return min_size * (len(devices) - 2)
elif level == 10:
return min_size * (len(devices) // 2)
else:
raise ValueError("unknown raid level %s" % level)
def id_factory(name): def id_factory(name):
i = 0 i = 0
@ -103,11 +143,14 @@ def asdict(inst):
if field.name.startswith('_'): if field.name.startswith('_'):
continue continue
v = getattr(inst, field.name) v = getattr(inst, field.name)
if v: if v is not None:
if hasattr(v, 'id'): if isinstance(v, (list, set)):
v = v.id r[field.name] = [elem.id for elem in v]
if v is not None: else:
r[field.name] = v if hasattr(v, 'id'):
v = v.id
if v is not None:
r[field.name] = v
return r return r
@ -134,7 +177,7 @@ class _Formattable:
# Filesystem # Filesystem
_fs = attr.ib(default=None, repr=False) _fs = attr.ib(default=None, repr=False)
# Nothing yet, but one day RAID, LV, ZPool, BCache... # Raid for now, but one day LV, ZPool, BCache...
_constructed_device = attr.ib(default=None, repr=False) _constructed_device = attr.ib(default=None, repr=False)
def _is_entirely_used(self): def _is_entirely_used(self):
@ -353,6 +396,41 @@ class Partition(_Formattable):
_supports_MAKE_BOOT = False _supports_MAKE_BOOT = False
@attr.s(cmp=False)
class Raid(_Device):
id = attr.ib(default=id_factory("raid"))
type = attr.ib(default="raid")
name = attr.ib(default=None)
raidlevel = attr.ib(default=None) # 0, 1, 5, 6, 10
devices = attr.ib(default=attr.Factory(set)) # set([_Formattable])
spare_devices = attr.ib(default=attr.Factory(set)) # set([_Formattable])
ptable = attr.ib(default=None)
@property
def size(self):
return get_raid_size(self.raidlevel, self.devices)
@property
def label(self):
return self.name
def desc(self):
return _("software RAID {}").format(self.raidlevel)
_supports_INFO = False
_supports_EDIT = True
_supports_PARTITION = Disk._supports_PARTITION
_supports_FORMAT = property(
lambda self: len(self._partitions) == 0 and
self._constructed_device is None)
_supports_DELETE = True
_supports_MAKE_BOOT = False
@property
def path(self):
return "/dev/{}".format(self.name)
@attr.s(cmp=False) @attr.s(cmp=False)
class Filesystem: class Filesystem:
@ -441,6 +519,7 @@ class FilesystemModel(object):
# only gets populated when something uses the disk # only gets populated when something uses the disk
self._disks = collections.OrderedDict() self._disks = collections.OrderedDict()
self._partitions = [] self._partitions = []
self._raids = []
self._filesystems = [] self._filesystems = []
self._mounts = [] self._mounts = []
for k, d in self._available_disks.items(): for k, d in self._available_disks.items():
@ -509,8 +588,11 @@ class FilesystemModel(object):
def all_disks(self): def all_disks(self):
return sorted(self._available_disks.values(), key=lambda x: x.label) return sorted(self._available_disks.values(), key=lambda x: x.label)
def all_raids(self):
return self._raids[:]
def all_devices(self): def all_devices(self):
return self.all_disks() # + self.all_raids() + self.all_lvms() + ... return self.all_disks() + self.all_raids() # + self.all_lvms() + ...
def get_disk(self, path): def get_disk(self, path):
return self._available_disks.get(path) return self._available_disks.get(path)
@ -541,6 +623,26 @@ class FilesystemModel(object):
if len(part.device._partitions) == 0: if len(part.device._partitions) == 0:
part.device.ptable = None part.device.ptable = None
def add_raid(self, name, raidlevel, devices, spare_devices):
r = Raid(
name=name,
raidlevel=raidlevel,
devices=devices,
spare_devices=spare_devices)
for d in devices | spare_devices:
if isinstance(d, Disk):
self._use_disk(d)
d._constructed_device = r
self._raids.append(r)
return r
def remove_raid(self, raid):
if raid._fs or raid._constructed_device or len(raid.partitions()):
raise Exception("can only remove empty RAID")
for d in raid.devices:
d._constructed_device = None
self._raids.remove(raid)
def add_filesystem(self, volume, fstype): def add_filesystem(self, volume, fstype):
log.debug("adding %s to %s", fstype, volume) log.debug("adding %s to %s", fstype, volume)
if not volume.available: if not volume.available:
@ -575,7 +677,10 @@ class FilesystemModel(object):
def get_mountpoint_to_devpath_mapping(self): def get_mountpoint_to_devpath_mapping(self):
r = {} r = {}
for m in self._mounts: for m in self._mounts:
r[m.path] = m.device.volume.path if isinstance(m.device.volume, Raid):
r[m.path] = m.device.volume.name
else:
r[m.path] = m.device.volume.path
return r return r
def any_configuration_done(self): def any_configuration_done(self):