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
)