From 5f8caf48d6ab1ce75101ac57cc771299d2ada07a Mon Sep 17 00:00:00 2001 From: brent s Date: Wed, 6 Nov 2019 12:48:18 -0500 Subject: [PATCH] WHY IS LVM THE WORST THING ON THIS PLANET --- aif.xsd | 26 +++--- aif/__init__.py | 2 +- aif/disk/lvm.py | 174 +++++++++++++++++++++++++++++++++++-- aif/disk/lvm_fallback.py | 46 ++++++++-- aif/disk/mdadm.py | 9 +- aif/disk/mdadm_fallback.py | 1 + aif/envsetup.py | 4 +- examples/aif.xml | 11 +-- setup.py | 6 +- 9 files changed, 241 insertions(+), 38 deletions(-) diff --git a/aif.xsd b/aif.xsd index d949106..83b9df3 100644 --- a/aif.xsd +++ b/aif.xsd @@ -501,22 +501,24 @@ - - - - - - - + + + + + + + + + - - - - + + + + diff --git a/aif/__init__.py b/aif/__init__.py index ab12177..5e8d022 100644 --- a/aif/__init__.py +++ b/aif/__init__.py @@ -2,7 +2,7 @@ try: from . import constants except ImportError: from . import constants_fallback as constants - +from . import constants_fallback from . import utils from . import disk from . import system diff --git a/aif/disk/lvm.py b/aif/disk/lvm.py index 317f6c9..c357dfd 100644 --- a/aif/disk/lvm.py +++ b/aif/disk/lvm.py @@ -1,3 +1,5 @@ +import uuid +## from . import _common import aif.disk.block as block import aif.disk.luks as luks @@ -10,21 +12,181 @@ _BlockDev = _common.BlockDev class PV(object): def __init__(self, pv_xml, partobj): self.xml = pv_xml + self.id = self.xml.attrib('id') + self.source = self.xml.attrib('source') + self.device = partobj + if not isinstance(self.device, (block.Disk, + block.Partition, + luks.LUKS, + mdadm.Array)): + raise ValueError(('partobj must be of type ' + 'aif.disk.block.Disk, ' + 'aif.disk.block.Partition, ' + 'aif.disk.luks.LUKS, or' + 'aif.disk.mdadm.Array')) _common.addBDPlugin('lvm') - self.devpath = None - pass + self.devpath = self.device.devpath + self.is_pooled = False + self.meta = None + self._parseMeta() + + def _parseMeta(self): + # Note, the "UUID" for LVM is *not* a true UUID (RFC4122) so we don't convert it. + # https://unix.stackexchange.com/questions/173722/what-is-the-uuid-format-used-by-lvm + # TODO: parity with lvm_fallback.PV._parseMeta + # key names currently (probably) don't match and need to confirm the information's all present + meta = {} + try: + _meta = _BlockDev.lvm.pvinfo(self.devpath) + except _BlockDev.LVMError: + self.meta = None + self.is_pooled = False + return() + for k in dir(_meta): + if k.startswith('_'): + continue + elif k in ('copy',): + continue + v = getattr(_meta, k) + meta[k] = v + self.meta = meta + self.is_pooled = True + return() + + def prepare(self): + try: + if not self.meta: + self._parseMeta() + if self.meta: + vg = self.meta['vg_name'] + # LVM is SO. DUMB. + # If you're using LVM, seriously - just switch your model to mdadm. It lets you do things like + # remove disks live without restructuring the entire thing. + # That said, because the config references partitions/disks/arrays/etc. created *in the same config*, + # and it's all dependent on block devices defined in the thing, we can be reckless here. + # I'd like to take the time now to remind you to NOT RUN AIF-NG ON A "LIVE" MACHINE. + # At least until I can maybe find a better way to determine which LVs to reduce on multi-LV VGs + # so I can *then* use lvresize in a balanced manner, vgreduce, and pvmove/pvremove and not kill + # everything. + # TODO. + for lv in _BlockDev.lvm.lvs(): + if lv.vg_name == vg: + _BlockDev.lvm.lvremove(vg, lv.lv_name) + _BlockDev.lvm.vgreduce(vg) + _BlockDev.lvm.vgremove(vg) # This *shouldn't* fail. In theory. But LVM is lel. + _BlockDev.lvm.pvremove(self.devpath) + # Or if I can get this working properly. Shame it isn't automagic. + # Seems to kill the LV by dropping a PV under it. Makes sense, but STILL. LVM IS SO DUMB. + # _BlockDev.lvm.vgdeactivate(vg) + # _BlockDev.lvm.pvremove(self.devpath) + # _BlockDev.lvm.vgreduce(vg) + # _BlockDev.lvm.vgactivate(vg) + ## + self.meta = None + self.is_pooled = False + except _BlockDev.LVMError: + self.meta = None + self.is_pooled = False + u = uuid.uuid4() + opts = [_BlockDev.ExtraArg.new('--reportformat', 'json')] + # FUCK. LVM. You can't *specify* a UUID. + # u = uuid.uuid4() + # opts.append(_BlockDev.ExtraArg.new('--uuid', str(u))) + _BlockDev.lvm.pvcreate(self.devpath, + 0, + 0, + opts) + self._parseMeta() + return() class VG(object): - def __init__(self, vg_xml, lv_objs): + def __init__(self, vg_xml): self.xml = vg_xml + self.id = self.xml.attrib('id') + self.name = self.xml.attrib('name') + self.lvs = [] + self.pvs = [] + self.tags = [] + for te in self.xml.findall('tags/tag'): + self.tags.append(te.text) _common.addBDPlugin('lvm') - self.devpath = None - pass + self.devpath = self.name + self.info = None + + def addPV(self, pvobj): + if not isinstance(pvobj, PV): + raise ValueError('pvobj must be of type aif.disk.lvm.PV') + pvobj.prepare() + self.pvs.append(pvobj) + return() + + def create(self): + if not self.pvs: + raise RuntimeError('Cannot create a VG with no PVs') + opts = [_BlockDev.ExtraArg.new('--reportformat', 'json')] + # FUCK. LVM. You can't *specify* a UUID. + # u = uuid.uuid4() + # opts.append(_BlockDev.ExtraArg.new('--uuid', str(u))) + for t in self.tags: + opts.append(_BlockDev.ExtraArg.new('--addtag', t)) + _BlockDev.lvm.vgcreate(self.name, + [p.devpath for p in self.pvs], + 0, + opts) + for p in self.pvs: + p._parseMeta() + self.updateInfo() + return() + + def createLV(self, lv_xml = None): + # If lv_xml is None, we loop through our own XML. + if lv_xml: + lv = LV(lv_xml, self) + lv.create() + self.lvs.append(lv) + else: + for le in self.xml.findall('logicalVolumes/lv'): + pass + self.updateInfo() + return() + + def start(self): + _BlockDev.lvm.vgactivate(self.name) + self.updateInfo() + return() + + def stop(self): + _BlockDev.lvm.vgdeactivate(self.name) + self.updateInfo() + return() + + def updateInfo(self): + _info = _BlockDev.lvm.vginfo(self.name) + # TODO: parity with lvm_fallback.VG.updateInfo + # key names currently (probably) don't match and need to confirm the information's all present + info = {} + for k in dir(_info): + if k.startswith('_'): + continue + elif k in ('copy',): + continue + v = getattr(_info, k) + info[k] = v + self.info = info + return() class LV(object): - def __init__(self, lv_xml, pv_objs): + def __init__(self, lv_xml, vgobj): self.xml = lv_xml + self.id = self.xml.attrib('id') + self.name = self.xml.attrib('name') + self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo().free + self.vg = vg_obj + if not isinstance(self.vg, VG): + raise ValueError('vg_obj must be of type aif.disk.lvm.VG') _common.addBDPlugin('lvm') + + self.devpath = None pass diff --git a/aif/disk/lvm_fallback.py b/aif/disk/lvm_fallback.py index 2ac9f49..71e1447 100644 --- a/aif/disk/lvm_fallback.py +++ b/aif/disk/lvm_fallback.py @@ -6,17 +6,53 @@ import aif.disk.mdadm_fallback as mdadm class PV(object): - def __init__(self, partobj): + def __init__(self, pv_xml, partobj): + self.xml = pv_xml + self.id = self.xml.attrib('id') + self.source = self.xml.attrib('source') + self.device = partobj + if not isinstance(self.device, (block.Disk, + block.Partition, + luks.LUKS, + mdadm.Array)): + raise ValueError(('partobj must be of type ' + 'aif.disk.block.Disk, ' + 'aif.disk.block.Partition, ' + 'aif.disk.luks.LUKS, or' + 'aif.disk.mdadm.Array')) + # TODO + self.devpath = self.device.devpath + pass + + +class LV(object): + def __init__(self, lv_xml, pv_objs, vg_obj): + self.xml = lv_xml + self.id = self.xml.attrib('id') + self.name = self.xml.attrib('name') + self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo().free + self.pvs = pv_objs + self.vg = vg_obj + for p in self.pvs: + if not isinstance(p, PV): + raise ValueError('pv_objs must be a list-like containing aif.disk.lvm.PV items') + if not isinstance(self.vg, VG): + raise ValueError('vg_obj must be of type aif.disk.lvm.VG') + # TODO self.devpath = None pass class VG(object): def __init__(self, vg_xml, lv_objs): + self.xml = vg_xml + self.id = self.xml.attrib('id') + self.name = self.xml.attrib('name') + self.lvs = lv_objs + for l in self.lvs: + if not isinstance(l, LV): + raise ValueError('lv_objs must be a list-like containing aif.disk.lvm.LV items') + # TODO self.devpath = None pass - -class LV(object): - def __init__(self, lv_xml, pv_objs): - pass diff --git a/aif/disk/mdadm.py b/aif/disk/mdadm.py index 2cf8e8c..59f956b 100644 --- a/aif/disk/mdadm.py +++ b/aif/disk/mdadm.py @@ -17,11 +17,11 @@ class Member(object): def __init__(self, member_xml, partobj): self.xml = member_xml self.device = partobj - if not isinstance(self.device, (block.Partition, - block.Disk, + if not isinstance(self.device, (block.Disk, + block.Partition, Array, - lvm.LV, - luks.LUKS)): + luks.LUKS, + lvm.LV)): raise ValueError(('partobj must be of type ' 'aif.disk.block.Disk, ' 'aif.disk.block.Partition, ' @@ -67,6 +67,7 @@ class Member(object): except _BlockDev.MDRaidError: pass _BlockDev.md.destroy(self.devpath) + self._parseDeviceBlock() return() diff --git a/aif/disk/mdadm_fallback.py b/aif/disk/mdadm_fallback.py index 315f83f..88484cd 100644 --- a/aif/disk/mdadm_fallback.py +++ b/aif/disk/mdadm_fallback.py @@ -123,6 +123,7 @@ class Member(object): # TODO: logging subprocess.run(['mdadm', '--misc', '--zero-superblock', self.devpath]) self.is_superblocked = False + self._parseDeviceBlock() return() class Array(object): diff --git a/aif/envsetup.py b/aif/envsetup.py index f7c890b..cfe5a86 100644 --- a/aif/envsetup.py +++ b/aif/envsetup.py @@ -12,7 +12,7 @@ import sys import tempfile import venv ## -import aif.constants +import aif.constants_fallback class EnvBuilder(object): def __init__(self): @@ -35,7 +35,7 @@ class EnvBuilder(object): # This is SO. DUMB. WHY DO I HAVE TO CALL PIP FROM A SHELL. IT'S WRITTEN IN PYTHON. # https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program # TODO: logging - for m in aif.constants.external_deps: + for m in aif.constants_fallback.EXTERNAL_DEPS: pip_cmd = [os.path.join(self.vdir, 'bin', 'python3'), diff --git a/examples/aif.xml b/examples/aif.xml index d15741b..24c07bf 100644 --- a/examples/aif.xml +++ b/examples/aif.xml @@ -57,15 +57,16 @@ - - data - misc - - + + + + + + diff --git a/setup.py b/setup.py index 80587d7..9f63012 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ import setuptools -import aif.constants as PROJ_CONST +import aif.constants_fallback as PROJ_CONST with open('README', 'r') as fh: long_description = fh.read() setuptools.setup( name = 'aif', - version = PROJ_CONST.version, + version = PROJ_CONST.VERSION, author = 'Brent S.', author_email = 'bts@square-r00t.net', description = 'Arch Installation Framework (Next Generation)', @@ -32,5 +32,5 @@ setuptools.setup( project_urls = {'Documentation': 'https://aif-ng.io/', 'Source': 'https://git.square-r00t.net/AIF-NG/', 'Tracker': 'https://bugs.square-r00t.net/index.php?project=9'}, - install_requires = PROJ_CONST.external_deps + install_requires = PROJ_CONST.EXTERNAL_DEPS )