summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbrent s <bts@square-r00t.net>2019-12-23 14:52:00 -0500
committerbrent s <bts@square-r00t.net>2019-12-23 14:52:00 -0500
commitc165e60d34f37b319ab85d9977ed73eb15b2ab93 (patch)
tree01b9794725f1283d22bd6e0e7f23661840680558
parent48ab7f953ffa0368d688ad28aca8cf635fe7e3f3 (diff)
downloadAIF-NG-c165e60d34f37b319ab85d9977ed73eb15b2ab93.tar.xz
lvm logging done
-rw-r--r--aif/disk/lvm.py277
-rw-r--r--aif/disk/lvm_fallback.py482
2 files changed, 462 insertions, 297 deletions
diff --git a/aif/disk/lvm.py b/aif/disk/lvm.py
index deba1d9..a13c617 100644
--- a/aif/disk/lvm.py
+++ b/aif/disk/lvm.py
@@ -1,4 +1,7 @@
-import uuid
+import logging
+# import uuid
+##
+from lxml import etree
##
from . import _common
import aif.utils
@@ -7,12 +10,134 @@ import aif.disk.luks as luks
import aif.disk.mdadm as mdadm
+_logger = logging.getLogger(__name__)
+
+
_BlockDev = _common.BlockDev
+class LV(object):
+ def __init__(self, lv_xml, vgobj):
+ self.xml = lv_xml
+ _logger.debug('lv_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
+ self.id = self.xml.attrib('id')
+ self.name = self.xml.attrib('name')
+ self.vg = vgobj
+ self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
+ _logger.debug('Qualified name: {0}'.format(self.qualified_name))
+ self.pvs = []
+ if not isinstance(self.vg, VG):
+ _logger.debug('vgobj must be of type aif.disk.lvm.VG')
+ raise ValueError('Invalid vgobj type')
+ _common.addBDPlugin('lvm')
+ self.info = None
+ self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
+ self.created = False
+ self.updateInfo()
+ self._initLV()
+
+ def _initLV(self):
+ self.pvs = []
+ _indexed_pvs = {i.id: i for i in self.vg.pvs}
+ for pe in self.xml.findall('pvMember'):
+ _logger.debug('Found PV element: {0}'.format(etree.tostring(pe, with_tail = False).decode('utf-8')))
+ pv_id = pe.attrib('source')
+ if pv_id in _indexed_pvs.keys():
+ self.pvs.append(_indexed_pvs[pv_id])
+ if not self.pvs: # We get all in the VG instead since none were explicitly assigned
+ _logger.debug('No PVs explicitly designated to VG; adding all.')
+ self.pvs = self.vg.pvs
+ # Size processing. We have to do this after indexing PVs.
+ # If not x['type'], assume *extents*, not sectors
+ self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo(<VG>).free TODO
+ x = dict(zip(('from_bgn', 'size', 'type'),
+ aif.utils.convertSizeUnit(self.xml.attrib['size'])))
+ # self.size is bytes
+ self.size = x['size']
+ _extents = {'size': self.vg.info['extent_size'],
+ 'total': 0} # We can't use self.vg.info['extent_count'] because selective PVs.
+ _sizes = {'total': 0,
+ 'free': 0}
+ _vg_pe = self.vg.info['extent_size']
+ for pv in self.pvs:
+ _sizes['total'] += pv.info['pv_size']
+ _sizes['free'] += pv.info['pv_free']
+ _extents['total'] += int(pv.info['pv_size'] / _extents['size'])
+ if x['type'] == '%':
+ self.size = int(_sizes['total'] * (0.01 * self.size))
+ elif x['type'] is None:
+ self.size = int(self.size * _extents['size'])
+ else:
+ self.size = int(aif.utils.size.convertStorage(x['size'],
+ x['type'],
+ target = 'B'))
+ if self.size >= _sizes['total']:
+ self.size = 0
+ return(None)
+
+ def create(self):
+ if not self.pvs:
+ _logger.error('Cannot create LV with no associated PVs')
+ raise RuntimeError('Missing 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.lvcreate(self.vg.name,
+ self.name,
+ self.size,
+ None,
+ [i.devpath for i in self.pvs],
+ opts)
+ self.vg.lvs.append(self)
+ self.created = True
+ self.updateInfo()
+ self.vg.updateInfo()
+ return(None)
+
+ def start(self):
+ _logger.info('Activating LV {0} in VG {1}.'.format(self.name, self.vg.name))
+ _BlockDev.lvm.lvactivate(self.vg.name,
+ self.name,
+ True,
+ None)
+ self.updateInfo()
+ return(None)
+
+ def stop(self):
+ _logger.info('Deactivating LV {0} in VG {1}.'.format(self.name, self.vg.name))
+ _BlockDev.lvm.lvdeactivate(self.vg.name,
+ self.name,
+ None)
+ self.updateInfo()
+ return(None)
+
+ def updateInfo(self):
+ if not self.created:
+ _logger.warning('Attempted to updateInfo on an LV not created yet.')
+ return(None)
+ _info = _BlockDev.lvm.lvinfo(self.vg.name, self.name)
+ # TODO: parity with lvm_fallback.LV.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
+ _logger.debug('Rendered info: {0}'.format(info))
+ return(None)
+
+
class PV(object):
def __init__(self, pv_xml, partobj):
self.xml = pv_xml
+ _logger.debug('pv_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
self.id = self.xml.attrib('id')
self.source = self.xml.attrib('source')
self.device = partobj
@@ -20,11 +145,12 @@ class PV(object):
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'))
+ _logger.error(('partobj must be of type '
+ 'aif.disk.block.Disk, '
+ 'aif.disk.block.Partition, '
+ 'aif.disk.luks.LUKS, or'
+ 'aif.disk.mdadm.Array.'))
+ raise ValueError('Invalid partobj type')
_common.addBDPlugin('lvm')
self.devpath = self.device.devpath
self.is_pooled = False
@@ -40,17 +166,19 @@ class PV(object):
try:
_meta = _BlockDev.lvm.pvinfo(self.devpath)
except _BlockDev.LVMError:
+ _logger.debug('PV device is not a PV yet.')
self.meta = None
self.is_pooled = False
return(None)
for k in dir(_meta):
if k.startswith('_'):
continue
- elif k in ('copy',):
+ elif k in ('copy', ):
continue
v = getattr(_meta, k)
meta[k] = v
self.meta = meta
+ _logger.debug('Rendered meta: {0}'.format(meta))
self.is_pooled = True
return(None)
@@ -65,16 +193,20 @@ class PV(object):
# 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.
+ # I'd like to take the time now to remind you to NOT RUN AIF-NG ON A "PRODUCTION"-STATE 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:
+ _logger.info('Removing LV {0} from VG {1}.'.format(lv.lv_name, vg))
_BlockDev.lvm.lvremove(vg, lv.lv_name)
+ _logger.debug('Reducing VG {0}.'.format(vg))
_BlockDev.lvm.vgreduce(vg)
+ _logger.info('Removing VG {0}.'.format(vg))
_BlockDev.lvm.vgremove(vg) # This *shouldn't* fail. In theory. But LVM is lel.
+ _logger.info('Removing PV {0}.'.format(self.devpath))
_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.
@@ -96,6 +228,7 @@ class PV(object):
0,
0,
opts)
+ _logger.info('Created PV {0} with opts {1}'.format(self.devpath, opts))
self._parseMeta()
return(None)
@@ -103,6 +236,7 @@ class PV(object):
class VG(object):
def __init__(self, vg_xml):
self.xml = vg_xml
+ _logger.debug('vg_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
self.id = self.xml.attrib('id')
self.name = self.xml.attrib('name')
self.pe_size = self.xml.attrib.get('extentSize', 0)
@@ -114,7 +248,8 @@ class VG(object):
x['type'],
target = 'B')
if not aif.utils.isPowerofTwo(self.pe_size):
- raise ValueError('The PE size must be a power of two (in bytes)')
+ _logger.error('The PE size must be a power of two (in bytes).')
+ raise ValueError('Invalid PE value')
self.lvs = []
self.pvs = []
# self.tags = []
@@ -127,14 +262,16 @@ class VG(object):
def addPV(self, pvobj):
if not isinstance(pvobj, PV):
- raise ValueError('pvobj must be of type aif.disk.lvm.PV')
+ _logger.error('pvobj must be of type aif.disk.lvm.PV.')
+ raise ValueError('Invalid pvbobj type')
pvobj.prepare()
self.pvs.append(pvobj)
return(None)
def create(self):
if not self.pvs:
- raise RuntimeError('Cannot create a VG with no PVs')
+ _logger.error('Cannot create a VG with no PVs.')
+ raise RuntimeError('Missing PVs')
opts = [_BlockDev.ExtraArg.new('--reportformat', 'json')]
# FUCK. LVM. You can't *specify* a UUID.
# u = uuid.uuid4()
@@ -153,14 +290,18 @@ class VG(object):
def createLV(self, lv_xml = None):
if not self.created:
- raise RuntimeError('VG must be created before LVs can be added')
+ _logger.info('Attempted to add an LV to a VG before it was created.')
+ raise RuntimeError('LV before VG creation')
# If lv_xml is None, we loop through our own XML.
if lv_xml:
+ _logger.debug('Explicit lv_xml specified: {0}'.format(etree.tostring(lv_xml,
+ with_tail = False).decode('utf-8')))
lv = LV(lv_xml, self)
lv.create()
# self.lvs.append(lv)
else:
for le in self.xml.findall('logicalVolumes/lv'):
+ _logger.debug('Found lv element: {0}'.format(etree.tostring(le, with_tail = False).decode('utf-8')))
lv = LV(le, self)
lv.create()
# self.lvs.append(lv)
@@ -168,17 +309,20 @@ class VG(object):
return(None)
def start(self):
+ _logger.info('Activating VG: {0}.'.format(self.name))
_BlockDev.lvm.vgactivate(self.name)
self.updateInfo()
return(None)
def stop(self):
+ _logger.info('Deactivating VG: {0}.'.format(self.name))
_BlockDev.lvm.vgdeactivate(self.name)
self.updateInfo()
return(None)
def updateInfo(self):
if not self.created:
+ _logger.warning('Attempted to updateInfo on a VG not created yet.')
return(None)
_info = _BlockDev.lvm.vginfo(self.name)
# TODO: parity with lvm_fallback.VG.updateInfo
@@ -192,112 +336,5 @@ class VG(object):
v = getattr(_info, k)
info[k] = v
self.info = info
- return(None)
-
-
-class LV(object):
- def __init__(self, lv_xml, vgobj):
- self.xml = lv_xml
- self.id = self.xml.attrib('id')
- self.name = self.xml.attrib('name')
- self.vg = vgobj
- self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
- self.pvs = []
- if not isinstance(self.vg, VG):
- raise ValueError('vgobj must be of type aif.disk.lvm.VG')
- _common.addBDPlugin('lvm')
- self.info = None
- self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
- self.created = False
- self.updateInfo()
- self._initLV()
-
- def _initLV(self):
- self.pvs = []
- _indexed_pvs = {i.id: i for i in self.vg.pvs}
- for pe in self.xml.findall('pvMember'):
- pv_id = pe.attrib('source')
- if pv_id in _indexed_pvs.keys():
- self.pvs.append(_indexed_pvs[pv_id])
- if not self.pvs: # We get all in the VG instead since none were explicitly assigned
- self.pvs = self.vg.pvs
- # Size processing. We have to do this after indexing PVs.
- # If not x['type'], assume *extents*, not sectors
- self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo(<VG>).free TODO
- x = dict(zip(('from_bgn', 'size', 'type'),
- aif.utils.convertSizeUnit(self.xml.attrib['size'])))
- # self.size is bytes
- self.size = x['size']
- _extents = {'size': self.vg.info['extent_size'],
- 'total': 0} # We can't use self.vg.info['extent_count'] because selective PVs.
- _sizes = {'total': 0,
- 'free': 0}
- _vg_pe = self.vg.info['extent_size']
- for pv in self.pvs:
- _sizes['total'] += pv.info['pv_size']
- _sizes['free'] += pv.info['pv_free']
- _extents['total'] += int(pv.info['pv_size'] / _extents['size'])
- if x['type'] == '%':
- self.size = int(_sizes['total'] * (0.01 * self.size))
- elif x['type'] is None:
- self.size = int(self.size * _extents['size'])
- else:
- self.size = int(aif.utils.size.convertStorage(x['size'],
- x['type'],
- target = 'B'))
- if self.size >= _sizes['total']:
- self.size = 0
- return(None)
-
- def create(self):
- if not self.pvs:
- raise RuntimeError('Cannot create LV with no associated LVs')
- 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.lvcreate(self.vg.name,
- self.name,
- self.size,
- None,
- [i.devpath for i in self.pvs],
- opts)
- self.vg.lvs.append(self)
- self.created = True
- self.updateInfo()
- self.vg.updateInfo()
- return(None)
-
- def start(self):
- _BlockDev.lvm.lvactivate(self.vg.name,
- self.name,
- True,
- None)
- self.updateInfo()
- return(None)
-
- def stop(self):
- _BlockDev.lvm.lvdeactivate(self.vg.name,
- self.name,
- None)
- self.updateInfo()
- return(None)
-
- def updateInfo(self):
- if not self.created:
- return(None)
- _info = _BlockDev.lvm.lvinfo(self.vg.name, self.name)
- # TODO: parity with lvm_fallback.LV.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
+ _logger.debug('Rendered info: {0}'.format(info))
return(None)
diff --git a/aif/disk/lvm_fallback.py b/aif/disk/lvm_fallback.py
index 4e61719..1d21e79 100644
--- a/aif/disk/lvm_fallback.py
+++ b/aif/disk/lvm_fallback.py
@@ -1,16 +1,204 @@
import datetime
import json
+import logging
import subprocess
##
+from lxml import etree
+##
import aif.utils
import aif.disk.block_fallback as block
import aif.disk.luks_fallback as luks
import aif.disk.mdadm_fallback as mdadm
+_logger = logging.getLogger(__name__)
+
+
+class LV(object):
+ def __init__(self, lv_xml, vgobj):
+ self.xml = lv_xml
+ _logger.debug('lv_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
+ self.id = self.xml.attrib('id')
+ self.name = self.xml.attrib('name')
+ self.vg = vgobj
+ self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
+ _logger.debug('Qualified name: {0}'.format(self.qualified_name))
+ self.pvs = []
+ if not isinstance(self.vg, VG):
+ _logger.debug('vgobj must be of type aif.disk.lvm.VG')
+ raise ValueError('Invalid vgobj type')
+ self.info = None
+ self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
+ self.created = False
+ self.updateInfo()
+ self._initLV()
+
+ def _initLV(self):
+ self.pvs = []
+ _indexed_pvs = {i.id: i for i in self.vg.pvs}
+ for pe in self.xml.findall('pvMember'):
+ _logger.debug('Found PV element: {0}'.format(etree.tostring(pe, with_tail = False).decode('utf-8')))
+ pv_id = pe.attrib('source')
+ if pv_id in _indexed_pvs.keys():
+ self.pvs.append(_indexed_pvs[pv_id])
+ if not self.pvs: # We get all in the VG instead since none were explicitly assigned
+ _logger.debug('No PVs explicitly designated to VG; adding all.')
+ self.pvs = self.vg.pvs
+ # Size processing. We have to do this after indexing PVs.
+ # If not x['type'], assume *extents*, not sectors
+ self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo(<VG>).free TODO
+ x = dict(zip(('from_bgn', 'size', 'type'),
+ aif.utils.convertSizeUnit(self.xml.attrib['size'])))
+ # self.size is bytes
+ self.size = x['size']
+ _extents = {'size': self.vg.info['extent_size'],
+ 'total': 0} # We can't use self.vg.info['extent_count'] because selective PVs.
+ _sizes = {'total': 0,
+ 'free': 0}
+ _vg_pe = self.vg.info['extent_size']
+ for pv in self.pvs:
+ _sizes['total'] += pv.info['pv_size']
+ _sizes['free'] += pv.info['pv_free']
+ _extents['total'] += int(pv.info['pv_size'] / _extents['size'])
+ if x['type'] == '%':
+ self.size = int(_sizes['total'] * (0.01 * self.size))
+ elif x['type'] is None:
+ self.size = int(self.size * _extents['size'])
+ else:
+ self.size = int(aif.utils.size.convertStorage(x['size'],
+ x['type'],
+ target = 'B'))
+ if self.size >= _sizes['total']:
+ self.size = 0
+ return(None)
+
+ def create(self):
+ if not self.pvs:
+ _logger.error('Cannot create LV with no associated PVs')
+ raise RuntimeError('Missing PVs')
+ cmd_str = ['lvcreate',
+ '--reportformat', 'json']
+ if self.size > 0:
+ cmd_str.extend(['--size', self.size])
+ elif self.size == 0:
+ cmd_str.extend(['--extents', '100%FREE'])
+ cmd_str.extend([self.name,
+ self.vg.name])
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to create LV successfully')
+ self.vg.lvs.append(self)
+ self.created = True
+ self.updateInfo()
+ self.vg.updateInfo()
+ return(None)
+
+ def start(self):
+ _logger.info('Activating LV {0} in VG {1}.'.format(self.name, self.vg.name))
+ cmd_str = ['lvchange',
+ '--activate', 'y',
+ '--reportformat', 'json',
+ self.qualified_name]
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to activate LV successfully')
+ self.updateInfo()
+ return(None)
+
+ def stop(self):
+ _logger.info('Deactivating LV {0} in VG {1}.'.format(self.name, self.vg.name))
+ cmd_str = ['lvchange',
+ '--activate', 'n',
+ '--reportformat', 'json',
+ self.qualified_name]
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to deactivate successfully')
+ self.updateInfo()
+ return(None)
+
+ def updateInfo(self):
+ if not self.created:
+ _logger.warning('Attempted to updateInfo on an LV not created yet.')
+ return(None)
+ info = {}
+ cmd = ['lvs',
+ '--binary',
+ '--nosuffix',
+ '--units', 'b',
+ '--options', '+lvall',
+ '--reportformat', 'json',
+ self.qualified_name]
+ _info = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(_info.args)))
+ if _info.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(_info.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ self.info = None
+ self.created = False
+ return(None)
+ _info = json.loads(_info.stdout.decode('utf-8'))['report'][0]['vg'][0]
+ for k, v in _info.items():
+ # ints
+ if k in ('lv_fixed_minor', 'lv_kernel_major', 'lv_kernel_minor', 'lv_kernel_read_ahead', 'lv_major',
+ 'lv_metadata_size', 'lv_minor', 'lv_size', 'seg_count'):
+ try:
+ v = int(v)
+ except ValueError:
+ v = 0
+ # booleans - LVs apparently have a third value, "-1", which is "unknown". We translate to None.
+ elif k in ('lv_active_exclusively', 'lv_active_locally', 'lv_active_remotely', 'lv_allocation_locked',
+ 'lv_check_needed', 'lv_converting', 'lv_device_open', 'lv_historical', 'lv_image_synced',
+ 'lv_inactive_table', 'lv_initial_image_sync', 'lv_live_table', 'lv_merge_failed', 'lv_merging',
+ 'lv_skip_activation', 'lv_snapshot_invalid', 'lv_suspended'):
+ if v == '-1':
+ v = None
+ else:
+ v = (True if int(v) == 1 else False)
+ # lists
+ elif k in ('lv_ancestors', 'lv_descendants', 'lv_full_ancestors', 'lv_full_descendants', 'lv_lockargs',
+ 'lv_modules', 'lv_permissions', 'lv_tags'):
+ v = [i.strip() for i in v.split(',') if i.strip() != '']
+ # date time strings
+ elif k in ('lv_time', ):
+ v = datetime.datetime.strptime(v, '%Y-%m-%d %H:%M:%S %z')
+ elif v.strip() == '':
+ v = None
+ info[k] = v
+ self.info = info
+ _logger.debug('Rendered info: {0}'.format(info))
+ return(None)
+
+
class PV(object):
def __init__(self, pv_xml, partobj):
self.xml = pv_xml
+ _logger.debug('pv_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
self.id = self.xml.attrib('id')
self.source = self.xml.attrib('source')
self.device = partobj
@@ -18,11 +206,12 @@ class PV(object):
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'))
+ _logger.error(('partobj must be of type '
+ 'aif.disk.block.Disk, '
+ 'aif.disk.block.Partition, '
+ 'aif.disk.luks.LUKS, or'
+ 'aif.disk.mdadm.Array.'))
+ raise ValueError('Invalid partobj type')
self.devpath = self.device.devpath
self.is_pooled = False
self.meta = None
@@ -40,7 +229,14 @@ class PV(object):
'--reportformat', 'json',
self.devpath]
_meta = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(_meta.args)))
if _meta.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(_meta.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
self.meta = None
self.is_pooled = False
return(None)
@@ -64,21 +260,40 @@ class PV(object):
meta[k] = v
self.meta = meta
self.is_pooled = True
+ _logger.debug('Rendered meta: {0}'.format(meta))
return(None)
def prepare(self):
if not self.meta:
self._parseMeta()
# *Technically*, we should vgreduce before pvremove, but eff it.
- cmd = ['pvremove',
- '--force', '--force',
- '--reportformat', 'json',
- self.devpath]
- subprocess.run(cmd)
- cmd = ['pvcreate',
- '--reportformat', 'json',
- self.devpath]
- subprocess.run(cmd)
+ cmd_str = ['pvremove',
+ '--force', '--force',
+ '--reportformat', 'json',
+ self.devpath]
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to remove PV successfully')
+ cmd_str = ['pvcreate',
+ '--reportformat', 'json',
+ self.devpath]
+ cmd = subprocess.run(cmd_str)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to format successfully')
self._parseMeta()
return(None)
@@ -86,6 +301,7 @@ class PV(object):
class VG(object):
def __init__(self, vg_xml):
self.xml = vg_xml
+ _logger.debug('vg_xml: {0}'.format(etree.tostring(self.xml, with_tail = False).decode('utf-8')))
self.id = self.xml.attrib('id')
self.name = self.xml.attrib('name')
self.pe_size = self.xml.attrib.get('extentSize', 0)
@@ -97,34 +313,45 @@ class VG(object):
x['type'],
target = 'B')
if not aif.utils.isPowerofTwo(self.pe_size):
- raise ValueError('The PE size must be a power of two (in bytes)')
+ _logger.error('The PE size must be a power of two (in bytes).')
+ raise ValueError('Invalid PE value')
self.lvs = []
self.pvs = []
# self.tags = []
# for te in self.xml.findall('tags/tag'):
# self.tags.append(te.text)
- self.devpath = None
self.devpath = self.name
self.info = None
self.created = False
def addPV(self, pvobj):
if not isinstance(pvobj, PV):
- raise ValueError('pvobj must be of type aif.disk.lvm.PV')
+ _logger.error('pvobj must be of type aif.disk.lvm.PV.')
+ raise ValueError('Invalid pvbobj type')
pvobj.prepare()
self.pvs.append(pvobj)
return(None)
def create(self):
if not self.pvs:
- raise RuntimeError('Cannot create a VG with no PVs')
- cmd = ['vgcreate',
- '--reportformat', 'json',
- '--physicalextentsize', '{0}b'.format(self.pe_size),
- self.name]
+ _logger.error('Cannot create a VG with no PVs.')
+ raise RuntimeError('Missing PVs')
+ cmd_str = ['vgcreate',
+ '--reportformat', 'json',
+ '--physicalextentsize', '{0}b'.format(self.pe_size),
+ self.name]
for pv in self.pvs:
- cmd.append(pv.devpath)
- subprocess.run(cmd)
+ cmd_str.append(pv.devpath)
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to create VG successfully')
for pv in self.pvs:
pv._parseMeta()
self.created = True
@@ -133,14 +360,18 @@ class VG(object):
def createLV(self, lv_xml = None):
if not self.created:
- raise RuntimeError('VG must be created before LVs can be added')
+ _logger.info('Attempted to add an LV to a VG before it was created.')
+ raise RuntimeError('LV before VG creation')
# If lv_xml is None, we loop through our own XML.
if lv_xml:
+ _logger.debug('Explicit lv_xml specified: {0}'.format(etree.tostring(lv_xml,
+ with_tail = False).decode('utf-8')))
lv = LV(lv_xml, self)
lv.create()
# self.lvs.append(lv)
else:
for le in self.xml.findall('logicalVolumes/lv'):
+ _logger.debug('Found lv element: {0}'.format(etree.tostring(le, with_tail = False).decode('utf-8')))
lv = LV(le, self)
lv.create()
# self.lvs.append(lv)
@@ -148,34 +379,64 @@ class VG(object):
return(None)
def start(self):
- cmd = ['vgchange',
- '--activate', 'y',
- '--reportformat', 'json',
- self.name]
- subprocess.run(cmd)
+ _logger.info('Activating VG: {0}.'.format(self.name))
+ cmd_str = ['vgchange',
+ '--activate', 'y',
+ '--reportformat', 'json',
+ self.name]
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to activate VG successfully')
self.updateInfo()
return(None)
def stop(self):
- cmd = ['vgchange',
- '--activate', 'n',
- '--reportformat', 'json',
- self.name]
- subprocess.run(cmd)
+ _logger.info('Deactivating VG: {0}.'.format(self.name))
+ cmd_str = ['vgchange',
+ '--activate', 'n',
+ '--reportformat', 'json',
+ self.name]
+ cmd = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(cmd.args)))
+ if cmd.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(cmd.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(cmd, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
+ raise RuntimeError('Failed to deactivate VG successfully')
self.updateInfo()
return(None)
def updateInfo(self):
+ if not self.created:
+ _logger.warning('Attempted to updateInfo on a VG not created yet.')
+ return(None)
info = {}
- cmd = ['vgs',
- '--binary',
- '--nosuffix',
- '--units', 'b',
- '--options', '+vgall',
- '--reportformat', 'json',
- self.name]
- _info = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ cmd_str = ['vgs',
+ '--binary',
+ '--nosuffix',
+ '--units', 'b',
+ '--options', '+vgall',
+ '--reportformat', 'json',
+ self.name]
+ _info = subprocess.run(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ _logger.info('Executed: {0}'.format(' '.join(_info.args)))
if _info.returncode != 0:
+ _logger.warning('Command returned non-zero status')
+ _logger.debug('Exit status: {0}'.format(str(_info.returncode)))
+ for a in ('stdout', 'stderr'):
+ x = getattr(_info, a)
+ if x:
+ _logger.debug('{0}: {1}'.format(a.upper(), x.decode('utf-8').strip()))
self.info = None
self.created = False
return(None)
@@ -196,138 +457,5 @@ class VG(object):
v = None
info[k] = v
self.info = info
- return(None)
-
-
-class LV(object):
- def __init__(self, lv_xml, vgobj):
- self.xml = lv_xml
- self.id = self.xml.attrib('id')
- self.name = self.xml.attrib('name')
- self.vg = vgobj
- self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
- self.pvs = []
- if not isinstance(self.vg, VG):
- raise ValueError('vgobj must be of type aif.disk.lvm.VG')
- self.info = None
- self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
- self.created = False
- self.updateInfo()
- self._initLV()
-
- def _initLV(self):
- self.pvs = []
- _indexed_pvs = {i.id: i for i in self.vg.pvs}
- for pe in self.xml.findall('pvMember'):
- pv_id = pe.attrib('source')
- if pv_id in _indexed_pvs.keys():
- self.pvs.append(_indexed_pvs[pv_id])
- if not self.pvs: # We get all in the VG instead since none were explicitly assigned
- self.pvs = self.vg.pvs
- # Size processing. We have to do this after indexing PVs.
- # If not x['type'], assume *extents*, not sectors
- self.size = self.xml.attrib('size') # Convert to bytes. Can get max from _BlockDev.lvm.vginfo(<VG>).free TODO
- x = dict(zip(('from_bgn', 'size', 'type'),
- aif.utils.convertSizeUnit(self.xml.attrib['size'])))
- # self.size is bytes
- self.size = x['size']
- _extents = {'size': self.vg.info['extent_size'],
- 'total': 0} # We can't use self.vg.info['extent_count'] because selective PVs.
- _sizes = {'total': 0,
- 'free': 0}
- _vg_pe = self.vg.info['extent_size']
- for pv in self.pvs:
- _sizes['total'] += pv.info['pv_size']
- _sizes['free'] += pv.info['pv_free']
- _extents['total'] += int(pv.info['pv_size'] / _extents['size'])
- if x['type'] == '%':
- self.size = int(_sizes['total'] * (0.01 * self.size))
- elif x['type'] is None:
- self.size = int(self.size * _extents['size'])
- else:
- self.size = int(aif.utils.size.convertStorage(x['size'],
- x['type'],
- target = 'B'))
- if self.size >= _sizes['total']:
- self.size = 0
- return(None)
-
- def create(self):
- if not self.pvs:
- raise RuntimeError('Cannot create LV with no associated LVs')
- cmd = ['lvcreate',
- '--reportformat', 'json']
- if self.size > 0:
- cmd.extend(['--size', self.size])
- elif self.size == 0:
- cmd.extend(['--extents', '100%FREE'])
- cmd.extend([self.name,
- self.vg.name])
- self.vg.lvs.append(self)
- self.created = True
- self.updateInfo()
- self.vg.updateInfo()
- return(None)
-
- def start(self):
- cmd = ['lvchange',
- '--activate', 'y',
- '--reportformat', 'json',
- self.qualified_name]
- subprocess.run(cmd)
- self.updateInfo()
- return(None)
-
- def stop(self):
- cmd = ['lvchange',
- '--activate', 'n',
- '--reportformat', 'json',
- self.qualified_name]
- subprocess.run(cmd)
- self.updateInfo()
- return(None)
-
- def updateInfo(self):
- info = {}
- cmd = ['lvs',
- '--binary',
- '--nosuffix',
- '--units', 'b',
- '--options', '+lvall',
- '--reportformat', 'json',
- self.qualified_name]
- _info = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- if _info.returncode != 0:
- self.info = None
- self.created = False
- return(None)
- _info = json.loads(_info.stdout.decode('utf-8'))['report'][0]['vg'][0]
- for k, v in _info.items():
- # ints
- if k in ('lv_fixed_minor', 'lv_kernel_major', 'lv_kernel_minor', 'lv_kernel_read_ahead', 'lv_major',
- 'lv_metadata_size', 'lv_minor', 'lv_size', 'seg_count'):
- try:
- v = int(v)
- except ValueError:
- v = 0
- # booleans - LVs apparently have a third value, "-1", which is "unknown". We translate to None.
- elif k in ('lv_active_exclusively', 'lv_active_locally', 'lv_active_remotely', 'lv_allocation_locked',
- 'lv_check_needed', 'lv_converting', 'lv_device_open', 'lv_historical', 'lv_image_synced',
- 'lv_inactive_table', 'lv_initial_image_sync', 'lv_live_table', 'lv_merge_failed', 'lv_merging',
- 'lv_skip_activation', 'lv_snapshot_invalid', 'lv_suspended'):
- if v == '-1':
- v = None
- else:
- v = (True if int(v) == 1 else False)
- # lists
- elif k in ('lv_ancestors', 'lv_descendants', 'lv_full_ancestors', 'lv_full_descendants', 'lv_lockargs',
- 'lv_modules', 'lv_permissions', 'lv_tags'):
- v = [i.strip() for i in v.split(',') if i.strip() != '']
- # date time strings
- elif k in ('lv_time', ):
- v = datetime.datetime.strptime(v, '%Y-%m-%d %H:%M:%S %z')
- elif v.strip() == '':
- v = None
- info[k] = v
- self.info = info
+ _logger.debug('Rendered info: {0}'.format(info))
return(None)