WHY IS LVM THE WORST THING ON THIS PLANET
This commit is contained in:
parent
f424938913
commit
5f8caf48d6
26
aif.xsd
26
aif.xsd
@ -501,22 +501,24 @@
|
|||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="tags" minOccurs="0" maxOccurs="1">
|
<!-- PV creation doesn't really support tags, it seems,
|
||||||
<xs:complexType>
|
in libblockdev. So we'll simplify and just skip them. -->
|
||||||
<xs:sequence>
|
<!-- <xs:element name="tags" minOccurs="0" maxOccurs="1">-->
|
||||||
<xs:element name="tag" type="aif:t_nonempty"
|
<!-- <xs:complexType>-->
|
||||||
minOccurs="1" maxOccurs="unbounded"/>
|
<!-- <xs:sequence>-->
|
||||||
</xs:sequence>
|
<!-- <xs:element name="tag" type="aif:t_nonempty"-->
|
||||||
</xs:complexType>
|
<!-- minOccurs="1" maxOccurs="unbounded"/>-->
|
||||||
</xs:element>
|
<!-- </xs:sequence>-->
|
||||||
|
<!-- </xs:complexType>-->
|
||||||
|
<!-- </xs:element>-->
|
||||||
</xs:all>
|
</xs:all>
|
||||||
<xs:attribute name="id" type="xs:ID" use="required"/>
|
<xs:attribute name="id" type="xs:ID" use="required"/>
|
||||||
<xs:attribute name="name" type="aif:t_nonempty" use="required"/>
|
<xs:attribute name="name" type="aif:t_nonempty" use="required"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:unique name="uniq_vg_tags">
|
<!-- <xs:unique name="uniq_vg_tags">-->
|
||||||
<xs:selector xpath="aif:tags"/>
|
<!-- <xs:selector xpath="aif:tags"/>-->
|
||||||
<xs:field xpath="tag"/>
|
<!-- <xs:field xpath="tag"/>-->
|
||||||
</xs:unique>
|
<!-- </xs:unique>-->
|
||||||
</xs:element>
|
</xs:element>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
@ -2,7 +2,7 @@ try:
|
|||||||
from . import constants
|
from . import constants
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from . import constants_fallback as constants
|
from . import constants_fallback as constants
|
||||||
|
from . import constants_fallback
|
||||||
from . import utils
|
from . import utils
|
||||||
from . import disk
|
from . import disk
|
||||||
from . import system
|
from . import system
|
||||||
|
172
aif/disk/lvm.py
172
aif/disk/lvm.py
@ -1,3 +1,5 @@
|
|||||||
|
import uuid
|
||||||
|
##
|
||||||
from . import _common
|
from . import _common
|
||||||
import aif.disk.block as block
|
import aif.disk.block as block
|
||||||
import aif.disk.luks as luks
|
import aif.disk.luks as luks
|
||||||
@ -10,21 +12,181 @@ _BlockDev = _common.BlockDev
|
|||||||
class PV(object):
|
class PV(object):
|
||||||
def __init__(self, pv_xml, partobj):
|
def __init__(self, pv_xml, partobj):
|
||||||
self.xml = pv_xml
|
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')
|
_common.addBDPlugin('lvm')
|
||||||
self.devpath = None
|
self.devpath = self.device.devpath
|
||||||
pass
|
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):
|
class VG(object):
|
||||||
def __init__(self, vg_xml, lv_objs):
|
def __init__(self, vg_xml):
|
||||||
self.xml = 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')
|
_common.addBDPlugin('lvm')
|
||||||
self.devpath = None
|
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
|
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):
|
class LV(object):
|
||||||
def __init__(self, lv_xml, pv_objs):
|
def __init__(self, lv_xml, vgobj):
|
||||||
self.xml = lv_xml
|
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(<VG>).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')
|
_common.addBDPlugin('lvm')
|
||||||
|
|
||||||
|
self.devpath = None
|
||||||
pass
|
pass
|
||||||
|
@ -6,17 +6,53 @@ import aif.disk.mdadm_fallback as mdadm
|
|||||||
|
|
||||||
|
|
||||||
class PV(object):
|
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(<VG>).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
|
self.devpath = None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VG(object):
|
class VG(object):
|
||||||
def __init__(self, vg_xml, lv_objs):
|
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
|
self.devpath = None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LV(object):
|
|
||||||
def __init__(self, lv_xml, pv_objs):
|
|
||||||
pass
|
|
||||||
|
@ -17,11 +17,11 @@ class Member(object):
|
|||||||
def __init__(self, member_xml, partobj):
|
def __init__(self, member_xml, partobj):
|
||||||
self.xml = member_xml
|
self.xml = member_xml
|
||||||
self.device = partobj
|
self.device = partobj
|
||||||
if not isinstance(self.device, (block.Partition,
|
if not isinstance(self.device, (block.Disk,
|
||||||
block.Disk,
|
block.Partition,
|
||||||
Array,
|
Array,
|
||||||
lvm.LV,
|
luks.LUKS,
|
||||||
luks.LUKS)):
|
lvm.LV)):
|
||||||
raise ValueError(('partobj must be of type '
|
raise ValueError(('partobj must be of type '
|
||||||
'aif.disk.block.Disk, '
|
'aif.disk.block.Disk, '
|
||||||
'aif.disk.block.Partition, '
|
'aif.disk.block.Partition, '
|
||||||
@ -67,6 +67,7 @@ class Member(object):
|
|||||||
except _BlockDev.MDRaidError:
|
except _BlockDev.MDRaidError:
|
||||||
pass
|
pass
|
||||||
_BlockDev.md.destroy(self.devpath)
|
_BlockDev.md.destroy(self.devpath)
|
||||||
|
self._parseDeviceBlock()
|
||||||
return()
|
return()
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +123,7 @@ class Member(object):
|
|||||||
# TODO: logging
|
# TODO: logging
|
||||||
subprocess.run(['mdadm', '--misc', '--zero-superblock', self.devpath])
|
subprocess.run(['mdadm', '--misc', '--zero-superblock', self.devpath])
|
||||||
self.is_superblocked = False
|
self.is_superblocked = False
|
||||||
|
self._parseDeviceBlock()
|
||||||
return()
|
return()
|
||||||
|
|
||||||
class Array(object):
|
class Array(object):
|
||||||
|
@ -12,7 +12,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import venv
|
import venv
|
||||||
##
|
##
|
||||||
import aif.constants
|
import aif.constants_fallback
|
||||||
|
|
||||||
class EnvBuilder(object):
|
class EnvBuilder(object):
|
||||||
def __init__(self):
|
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.
|
# 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
|
# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program
|
||||||
# TODO: logging
|
# TODO: logging
|
||||||
for m in aif.constants.external_deps:
|
for m in aif.constants_fallback.EXTERNAL_DEPS:
|
||||||
pip_cmd = [os.path.join(self.vdir,
|
pip_cmd = [os.path.join(self.vdir,
|
||||||
'bin',
|
'bin',
|
||||||
'python3'),
|
'python3'),
|
||||||
|
@ -57,15 +57,16 @@
|
|||||||
</luks>
|
</luks>
|
||||||
<lvm>
|
<lvm>
|
||||||
<volumeGroup id="vg1" name="group1">
|
<volumeGroup id="vg1" name="group1">
|
||||||
<tags>
|
|
||||||
<tag>data</tag>
|
|
||||||
<tag>misc</tag>
|
|
||||||
</tags>
|
|
||||||
<physicalVolumes>
|
<physicalVolumes>
|
||||||
<pv id="pv1" source="lvm_member1"/>
|
<pv id="pv1" source="lvm_member1"/>
|
||||||
</physicalVolumes>
|
</physicalVolumes>
|
||||||
<logicalVolumes>
|
<logicalVolumes>
|
||||||
<lv id="lv1" name="logical1" size="100%"/>
|
<!-- Default is to add all available PVs in PhysicalVolumes... -->
|
||||||
|
<lv id="lv1" name="logical1" size="80%"/>
|
||||||
|
<!-- But you can also explicitly designate them. -->
|
||||||
|
<lv id="lv2" name="logical1" size="20%">
|
||||||
|
<pvMember source="pv1"/>
|
||||||
|
</lv>
|
||||||
</logicalVolumes>
|
</logicalVolumes>
|
||||||
</volumeGroup>
|
</volumeGroup>
|
||||||
</lvm>
|
</lvm>
|
||||||
|
6
setup.py
6
setup.py
@ -1,12 +1,12 @@
|
|||||||
import setuptools
|
import setuptools
|
||||||
import aif.constants as PROJ_CONST
|
import aif.constants_fallback as PROJ_CONST
|
||||||
|
|
||||||
with open('README', 'r') as fh:
|
with open('README', 'r') as fh:
|
||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name = 'aif',
|
name = 'aif',
|
||||||
version = PROJ_CONST.version,
|
version = PROJ_CONST.VERSION,
|
||||||
author = 'Brent S.',
|
author = 'Brent S.',
|
||||||
author_email = 'bts@square-r00t.net',
|
author_email = 'bts@square-r00t.net',
|
||||||
description = 'Arch Installation Framework (Next Generation)',
|
description = 'Arch Installation Framework (Next Generation)',
|
||||||
@ -32,5 +32,5 @@ setuptools.setup(
|
|||||||
project_urls = {'Documentation': 'https://aif-ng.io/',
|
project_urls = {'Documentation': 'https://aif-ng.io/',
|
||||||
'Source': 'https://git.square-r00t.net/AIF-NG/',
|
'Source': 'https://git.square-r00t.net/AIF-NG/',
|
||||||
'Tracker': 'https://bugs.square-r00t.net/index.php?project=9'},
|
'Tracker': 'https://bugs.square-r00t.net/index.php?project=9'},
|
||||||
install_requires = PROJ_CONST.external_deps
|
install_requires = PROJ_CONST.EXTERNAL_DEPS
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user