Use attribute metadata to drive maintenance of backlinks
By backlinks I mean for example the link a filesystem has to a mount that uses it, or the link from a partition to the RAID it is a member of. This might be a little magical, but something like this is really nice when interpreting config objects from curtin.
This commit is contained in:
parent
67884836db
commit
18374cef06
|
@ -27,7 +27,48 @@ import platform
|
||||||
log = logging.getLogger('subiquity.models.filesystem')
|
log = logging.getLogger('subiquity.models.filesystem')
|
||||||
|
|
||||||
|
|
||||||
|
def _set_backlinks(obj):
|
||||||
|
for field in attr.fields(type(obj)):
|
||||||
|
backlink = field.metadata.get('backlink')
|
||||||
|
if backlink is None:
|
||||||
|
continue
|
||||||
|
v = getattr(obj, field.name)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
if not isinstance(v, (list, set)):
|
||||||
|
v = [v]
|
||||||
|
for vv in v:
|
||||||
|
b = getattr(vv, backlink, None)
|
||||||
|
if isinstance(b, list):
|
||||||
|
b.append(obj)
|
||||||
|
elif isinstance(b, set):
|
||||||
|
b.add(obj)
|
||||||
|
else:
|
||||||
|
setattr(vv, backlink, obj)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_backlinks(obj):
|
||||||
|
for field in attr.fields(type(obj)):
|
||||||
|
backlink = field.metadata.get('backlink')
|
||||||
|
if backlink is None:
|
||||||
|
continue
|
||||||
|
v = getattr(obj, field.name)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
if not isinstance(v, (list, set)):
|
||||||
|
v = [v]
|
||||||
|
for vv in v:
|
||||||
|
b = getattr(vv, backlink, None)
|
||||||
|
if isinstance(b, list):
|
||||||
|
b.remove(obj)
|
||||||
|
elif isinstance(b, set):
|
||||||
|
b.remove(obj)
|
||||||
|
else:
|
||||||
|
setattr(vv, backlink, None)
|
||||||
|
|
||||||
|
|
||||||
def fsobj(c):
|
def fsobj(c):
|
||||||
|
c.__attrs_post_init__ = _set_backlinks
|
||||||
return attr.s(cmp=False)(c)
|
return attr.s(cmp=False)(c)
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,12 +202,18 @@ def idfield(base):
|
||||||
return attr.ib(default=attr.Factory(factory))
|
return attr.ib(default=attr.Factory(factory))
|
||||||
|
|
||||||
|
|
||||||
def ref():
|
def ref(*, backlink=None):
|
||||||
return attr.ib(default=None, metadata={'ref': True})
|
metadata = {'ref': True}
|
||||||
|
if backlink:
|
||||||
|
metadata['backlink'] = backlink
|
||||||
|
return attr.ib(default=None, metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
def reflist():
|
def reflist(*, backlink=None):
|
||||||
return attr.ib(default=attr.Factory(set), metadata={'reflist': True})
|
metadata = {'reflist': True}
|
||||||
|
if backlink:
|
||||||
|
metadata['backlink'] = backlink
|
||||||
|
return attr.ib(default=attr.Factory(set), metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
def const(value):
|
def const(value):
|
||||||
|
@ -478,7 +525,7 @@ class Partition(_Formattable):
|
||||||
|
|
||||||
id = idfield("part")
|
id = idfield("part")
|
||||||
type = const("partition")
|
type = const("partition")
|
||||||
device = ref() # Disk
|
device = ref(backlink="_partitions") # Disk
|
||||||
size = attr.ib(default=None)
|
size = attr.ib(default=None)
|
||||||
wipe = attr.ib(default=None)
|
wipe = attr.ib(default=None)
|
||||||
flag = attr.ib(default=None)
|
flag = attr.ib(default=None)
|
||||||
|
@ -543,8 +590,8 @@ class Raid(_Device):
|
||||||
preserve = attr.ib(default=False)
|
preserve = attr.ib(default=False)
|
||||||
name = attr.ib(default=None)
|
name = attr.ib(default=None)
|
||||||
raidlevel = attr.ib(default=None) # 0, 1, 5, 6, 10
|
raidlevel = attr.ib(default=None) # 0, 1, 5, 6, 10
|
||||||
devices = reflist() # set([_Formattable])
|
devices = reflist(backlink="_constructed_device") # set([_Formattable])
|
||||||
spare_devices = reflist() # set([_Formattable])
|
spare_devices = reflist(backlink="_constructed_device") # ditto
|
||||||
ptable = attr.ib(default=None)
|
ptable = attr.ib(default=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -623,7 +670,7 @@ class LVM_VolGroup(_Device):
|
||||||
type = const("lvm_volgroup")
|
type = const("lvm_volgroup")
|
||||||
preserve = attr.ib(default=False)
|
preserve = attr.ib(default=False)
|
||||||
name = attr.ib(default=None)
|
name = attr.ib(default=None)
|
||||||
devices = reflist() # set([_Formattable])
|
devices = reflist(backlink="_constructed_device") # set([_Formattable])
|
||||||
_passphrase = attr.ib(default=None, repr=False)
|
_passphrase = attr.ib(default=None, repr=False)
|
||||||
|
|
||||||
def serialize_devices(self):
|
def serialize_devices(self):
|
||||||
|
@ -691,7 +738,7 @@ class LVM_LogicalVolume(_Formattable):
|
||||||
id = idfield("lv")
|
id = idfield("lv")
|
||||||
type = const("lvm_partition")
|
type = const("lvm_partition")
|
||||||
name = attr.ib(default=None)
|
name = attr.ib(default=None)
|
||||||
volgroup = ref() # LVM_VolGroup
|
volgroup = ref(backlink="_partitions") # LVM_VolGroup
|
||||||
size = attr.ib(default=None)
|
size = attr.ib(default=None)
|
||||||
preserve = attr.ib(default=False)
|
preserve = attr.ib(default=False)
|
||||||
|
|
||||||
|
@ -744,7 +791,7 @@ class Filesystem:
|
||||||
id = idfield("fs")
|
id = idfield("fs")
|
||||||
type = const("format")
|
type = const("format")
|
||||||
fstype = attr.ib(default=None)
|
fstype = attr.ib(default=None)
|
||||||
volume = ref() # _Formattable
|
volume = ref(backlink="_fs") # _Formattable
|
||||||
label = attr.ib(default=None)
|
label = attr.ib(default=None)
|
||||||
uuid = attr.ib(default=None)
|
uuid = attr.ib(default=None)
|
||||||
preserve = attr.ib(default=False)
|
preserve = attr.ib(default=False)
|
||||||
|
@ -767,7 +814,7 @@ class Filesystem:
|
||||||
class Mount:
|
class Mount:
|
||||||
id = idfield("mount")
|
id = idfield("mount")
|
||||||
type = const("mount")
|
type = const("mount")
|
||||||
device = ref() # Filesystem
|
device = ref(backlink="_mount") # Filesystem
|
||||||
path = attr.ib(default=None)
|
path = attr.ib(default=None)
|
||||||
|
|
||||||
def can_delete(self):
|
def can_delete(self):
|
||||||
|
@ -983,9 +1030,7 @@ class FilesystemModel(object):
|
||||||
raise Exception("%s is already formatted" % (disk.label,))
|
raise Exception("%s is already formatted" % (disk.label,))
|
||||||
p = Partition(device=disk, size=real_size, flag=flag, wipe=wipe)
|
p = Partition(device=disk, size=real_size, flag=flag, wipe=wipe)
|
||||||
if flag in ("boot", "bios_grub", "prep"):
|
if flag in ("boot", "bios_grub", "prep"):
|
||||||
disk._partitions.insert(0, p)
|
disk._partitions.insert(0, disk._partitions.pop())
|
||||||
else:
|
|
||||||
disk._partitions.append(p)
|
|
||||||
disk.ptable = 'gpt'
|
disk.ptable = 'gpt'
|
||||||
self._partitions.append(p)
|
self._partitions.append(p)
|
||||||
return p
|
return p
|
||||||
|
@ -993,7 +1038,7 @@ class FilesystemModel(object):
|
||||||
def remove_partition(self, part):
|
def remove_partition(self, part):
|
||||||
if part._fs or part._constructed_device:
|
if part._fs or part._constructed_device:
|
||||||
raise Exception("can only remove empty partition")
|
raise Exception("can only remove empty partition")
|
||||||
part.device._partitions.remove(part)
|
_remove_backlinks(part)
|
||||||
self._partitions.remove(part)
|
self._partitions.remove(part)
|
||||||
if len(part.device._partitions) == 0:
|
if len(part.device._partitions) == 0:
|
||||||
part.device.ptable = None
|
part.device.ptable = None
|
||||||
|
@ -1007,43 +1052,35 @@ class FilesystemModel(object):
|
||||||
for d in devices | spare_devices:
|
for d in devices | spare_devices:
|
||||||
if isinstance(d, Disk):
|
if isinstance(d, Disk):
|
||||||
self._use_disk(d)
|
self._use_disk(d)
|
||||||
d._constructed_device = r
|
|
||||||
self._raids.append(r)
|
self._raids.append(r)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def remove_raid(self, raid):
|
def remove_raid(self, raid):
|
||||||
if raid._fs or raid._constructed_device or len(raid.partitions()):
|
if raid._fs or raid._constructed_device or len(raid.partitions()):
|
||||||
raise Exception("can only remove empty RAID")
|
raise Exception("can only remove empty RAID")
|
||||||
for d in raid.devices:
|
_remove_backlinks(raid)
|
||||||
d._constructed_device = None
|
|
||||||
self._raids.remove(raid)
|
self._raids.remove(raid)
|
||||||
|
|
||||||
def add_volgroup(self, name, devices, passphrase):
|
def add_volgroup(self, name, devices, passphrase):
|
||||||
vg = LVM_VolGroup(name=name, devices=devices, passphrase=passphrase)
|
vg = LVM_VolGroup(name=name, devices=devices, passphrase=passphrase)
|
||||||
for d in devices:
|
|
||||||
if isinstance(d, Disk):
|
|
||||||
self._use_disk(d)
|
|
||||||
d._constructed_device = vg
|
|
||||||
self._vgs.append(vg)
|
self._vgs.append(vg)
|
||||||
return vg
|
return vg
|
||||||
|
|
||||||
def remove_volgroup(self, vg):
|
def remove_volgroup(self, vg):
|
||||||
if len(vg._partitions):
|
if len(vg._partitions):
|
||||||
raise Exception("can only remove empty VG")
|
raise Exception("can only remove empty VG")
|
||||||
for d in vg.devices:
|
_remove_backlinks(vg)
|
||||||
d._constructed_device = None
|
|
||||||
self._vgs.remove(vg)
|
self._vgs.remove(vg)
|
||||||
|
|
||||||
def add_logical_volume(self, vg, name, size):
|
def add_logical_volume(self, vg, name, size):
|
||||||
lv = LVM_LogicalVolume(volgroup=vg, name=name, size=size)
|
lv = LVM_LogicalVolume(volgroup=vg, name=name, size=size)
|
||||||
vg._partitions.append(lv)
|
|
||||||
self._lvs.append(lv)
|
self._lvs.append(lv)
|
||||||
return lv
|
return lv
|
||||||
|
|
||||||
def remove_logical_volume(self, lv):
|
def remove_logical_volume(self, lv):
|
||||||
if lv._fs:
|
if lv._fs:
|
||||||
raise Exception("can only remove empty LV")
|
raise Exception("can only remove empty LV")
|
||||||
lv.volgroup._partitions.remove(lv)
|
_remove_backlinks(lv)
|
||||||
self._lvs.remove(lv)
|
self._lvs.remove(lv)
|
||||||
|
|
||||||
def add_filesystem(self, volume, fstype):
|
def add_filesystem(self, volume, fstype):
|
||||||
|
@ -1057,25 +1094,25 @@ class FilesystemModel(object):
|
||||||
self._use_disk(volume)
|
self._use_disk(volume)
|
||||||
if volume._fs is not None:
|
if volume._fs is not None:
|
||||||
raise Exception("%s is already formatted")
|
raise Exception("%s is already formatted")
|
||||||
volume._fs = fs = Filesystem(volume=volume, fstype=fstype)
|
fs = Filesystem(volume=volume, fstype=fstype)
|
||||||
self._filesystems.append(fs)
|
self._filesystems.append(fs)
|
||||||
return fs
|
return fs
|
||||||
|
|
||||||
def remove_filesystem(self, fs):
|
def remove_filesystem(self, fs):
|
||||||
if fs._mount:
|
if fs._mount:
|
||||||
raise Exception("can only remove unmounted filesystem")
|
raise Exception("can only remove unmounted filesystem")
|
||||||
fs.volume._fs = None
|
_remove_backlinks(fs)
|
||||||
self._filesystems.remove(fs)
|
self._filesystems.remove(fs)
|
||||||
|
|
||||||
def add_mount(self, fs, path):
|
def add_mount(self, fs, path):
|
||||||
if fs._mount is not None:
|
if fs._mount is not None:
|
||||||
raise Exception("%s is already mounted")
|
raise Exception("%s is already mounted")
|
||||||
fs._mount = m = Mount(device=fs, path=path)
|
m = Mount(device=fs, path=path)
|
||||||
self._mounts.append(m)
|
self._mounts.append(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def remove_mount(self, mount):
|
def remove_mount(self, mount):
|
||||||
mount.device._mount = None
|
_remove_backlinks(mount)
|
||||||
self._mounts.remove(mount)
|
self._mounts.remove(mount)
|
||||||
|
|
||||||
def any_configuration_done(self):
|
def any_configuration_done(self):
|
||||||
|
|
Loading…
Reference in New Issue