lvm logging done
This commit is contained in:
parent
48ab7f953f
commit
c165e60d34
413
aif/disk/lvm.py
413
aif/disk/lvm.py
@ -1,4 +1,7 @@
|
|||||||
import uuid
|
import logging
|
||||||
|
# import uuid
|
||||||
|
##
|
||||||
|
from lxml import etree
|
||||||
##
|
##
|
||||||
from . import _common
|
from . import _common
|
||||||
import aif.utils
|
import aif.utils
|
||||||
@ -7,204 +10,25 @@ import aif.disk.luks as luks
|
|||||||
import aif.disk.mdadm as mdadm
|
import aif.disk.mdadm as mdadm
|
||||||
|
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_BlockDev = _common.BlockDev
|
_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 = 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(None)
|
|
||||||
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(None)
|
|
||||||
|
|
||||||
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
|
|
||||||
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(None)
|
|
||||||
|
|
||||||
|
|
||||||
class VG(object):
|
|
||||||
def __init__(self, vg_xml):
|
|
||||||
self.xml = vg_xml
|
|
||||||
self.id = self.xml.attrib('id')
|
|
||||||
self.name = self.xml.attrib('name')
|
|
||||||
self.pe_size = self.xml.attrib.get('extentSize', 0)
|
|
||||||
if self.pe_size:
|
|
||||||
x = dict(zip(('from_bgn', 'size', 'type'),
|
|
||||||
aif.utils.convertSizeUnit(self.pe_size)))
|
|
||||||
if x['type']:
|
|
||||||
self.pe_size = aif.utils.size.convertStorage(self.pe_size,
|
|
||||||
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)')
|
|
||||||
self.lvs = []
|
|
||||||
self.pvs = []
|
|
||||||
# self.tags = []
|
|
||||||
# for te in self.xml.findall('tags/tag'):
|
|
||||||
# self.tags.append(te.text)
|
|
||||||
_common.addBDPlugin('lvm')
|
|
||||||
self.devpath = '/dev/{0}'.format(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')
|
|
||||||
pvobj.prepare()
|
|
||||||
self.pvs.append(pvobj)
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
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],
|
|
||||||
self.pe_size,
|
|
||||||
opts)
|
|
||||||
for pv in self.pvs:
|
|
||||||
pv._parseMeta()
|
|
||||||
self.created = True
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def createLV(self, lv_xml = None):
|
|
||||||
if not self.created:
|
|
||||||
raise RuntimeError('VG must be created before LVs can be added')
|
|
||||||
# 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'):
|
|
||||||
lv = LV(le, self)
|
|
||||||
lv.create()
|
|
||||||
# self.lvs.append(lv)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
_BlockDev.lvm.vgactivate(self.name)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
_BlockDev.lvm.vgdeactivate(self.name)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def updateInfo(self):
|
|
||||||
if not self.created:
|
|
||||||
return(None)
|
|
||||||
_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(None)
|
|
||||||
|
|
||||||
|
|
||||||
class LV(object):
|
class LV(object):
|
||||||
def __init__(self, lv_xml, vgobj):
|
def __init__(self, lv_xml, vgobj):
|
||||||
self.xml = lv_xml
|
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.id = self.xml.attrib('id')
|
||||||
self.name = self.xml.attrib('name')
|
self.name = self.xml.attrib('name')
|
||||||
self.vg = vgobj
|
self.vg = vgobj
|
||||||
self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
|
self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
|
||||||
|
_logger.debug('Qualified name: {0}'.format(self.qualified_name))
|
||||||
self.pvs = []
|
self.pvs = []
|
||||||
if not isinstance(self.vg, VG):
|
if not isinstance(self.vg, VG):
|
||||||
raise ValueError('vgobj must be of type aif.disk.lvm.VG')
|
_logger.debug('vgobj must be of type aif.disk.lvm.VG')
|
||||||
|
raise ValueError('Invalid vgobj type')
|
||||||
_common.addBDPlugin('lvm')
|
_common.addBDPlugin('lvm')
|
||||||
self.info = None
|
self.info = None
|
||||||
self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
|
self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
|
||||||
@ -216,10 +40,12 @@ class LV(object):
|
|||||||
self.pvs = []
|
self.pvs = []
|
||||||
_indexed_pvs = {i.id: i for i in self.vg.pvs}
|
_indexed_pvs = {i.id: i for i in self.vg.pvs}
|
||||||
for pe in self.xml.findall('pvMember'):
|
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')
|
pv_id = pe.attrib('source')
|
||||||
if pv_id in _indexed_pvs.keys():
|
if pv_id in _indexed_pvs.keys():
|
||||||
self.pvs.append(_indexed_pvs[pv_id])
|
self.pvs.append(_indexed_pvs[pv_id])
|
||||||
if not self.pvs: # We get all in the VG instead since none were explicitly assigned
|
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
|
self.pvs = self.vg.pvs
|
||||||
# Size processing. We have to do this after indexing PVs.
|
# Size processing. We have to do this after indexing PVs.
|
||||||
# If not x['type'], assume *extents*, not sectors
|
# If not x['type'], assume *extents*, not sectors
|
||||||
@ -251,7 +77,8 @@ class LV(object):
|
|||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
if not self.pvs:
|
if not self.pvs:
|
||||||
raise RuntimeError('Cannot create LV with no associated LVs')
|
_logger.error('Cannot create LV with no associated PVs')
|
||||||
|
raise RuntimeError('Missing PVs')
|
||||||
opts = [_BlockDev.ExtraArg.new('--reportformat', 'json')]
|
opts = [_BlockDev.ExtraArg.new('--reportformat', 'json')]
|
||||||
# FUCK. LVM. You can't *specify* a UUID.
|
# FUCK. LVM. You can't *specify* a UUID.
|
||||||
# u = uuid.uuid4()
|
# u = uuid.uuid4()
|
||||||
@ -271,6 +98,7 @@ class LV(object):
|
|||||||
return(None)
|
return(None)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
_logger.info('Activating LV {0} in VG {1}.'.format(self.name, self.vg.name))
|
||||||
_BlockDev.lvm.lvactivate(self.vg.name,
|
_BlockDev.lvm.lvactivate(self.vg.name,
|
||||||
self.name,
|
self.name,
|
||||||
True,
|
True,
|
||||||
@ -279,6 +107,7 @@ class LV(object):
|
|||||||
return(None)
|
return(None)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
_logger.info('Deactivating LV {0} in VG {1}.'.format(self.name, self.vg.name))
|
||||||
_BlockDev.lvm.lvdeactivate(self.vg.name,
|
_BlockDev.lvm.lvdeactivate(self.vg.name,
|
||||||
self.name,
|
self.name,
|
||||||
None)
|
None)
|
||||||
@ -287,6 +116,7 @@ class LV(object):
|
|||||||
|
|
||||||
def updateInfo(self):
|
def updateInfo(self):
|
||||||
if not self.created:
|
if not self.created:
|
||||||
|
_logger.warning('Attempted to updateInfo on an LV not created yet.')
|
||||||
return(None)
|
return(None)
|
||||||
_info = _BlockDev.lvm.lvinfo(self.vg.name, self.name)
|
_info = _BlockDev.lvm.lvinfo(self.vg.name, self.name)
|
||||||
# TODO: parity with lvm_fallback.LV.updateInfo
|
# TODO: parity with lvm_fallback.LV.updateInfo
|
||||||
@ -300,4 +130,211 @@ class LV(object):
|
|||||||
v = getattr(_info, k)
|
v = getattr(_info, k)
|
||||||
info[k] = v
|
info[k] = v
|
||||||
self.info = info
|
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
|
||||||
|
if not isinstance(self.device, (block.Disk,
|
||||||
|
block.Partition,
|
||||||
|
luks.LUKS,
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
_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', ):
|
||||||
|
continue
|
||||||
|
v = getattr(_meta, k)
|
||||||
|
meta[k] = v
|
||||||
|
self.meta = meta
|
||||||
|
_logger.debug('Rendered meta: {0}'.format(meta))
|
||||||
|
self.is_pooled = True
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
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 "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.
|
||||||
|
# _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
|
||||||
|
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)
|
||||||
|
_logger.info('Created PV {0} with opts {1}'.format(self.devpath, opts))
|
||||||
|
self._parseMeta()
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
if self.pe_size:
|
||||||
|
x = dict(zip(('from_bgn', 'size', 'type'),
|
||||||
|
aif.utils.convertSizeUnit(self.pe_size)))
|
||||||
|
if x['type']:
|
||||||
|
self.pe_size = aif.utils.size.convertStorage(self.pe_size,
|
||||||
|
x['type'],
|
||||||
|
target = 'B')
|
||||||
|
if not aif.utils.isPowerofTwo(self.pe_size):
|
||||||
|
_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)
|
||||||
|
_common.addBDPlugin('lvm')
|
||||||
|
self.devpath = '/dev/{0}'.format(self.name)
|
||||||
|
self.info = None
|
||||||
|
self.created = False
|
||||||
|
|
||||||
|
def addPV(self, pvobj):
|
||||||
|
if not isinstance(pvobj, 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:
|
||||||
|
_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()
|
||||||
|
# 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],
|
||||||
|
self.pe_size,
|
||||||
|
opts)
|
||||||
|
for pv in self.pvs:
|
||||||
|
pv._parseMeta()
|
||||||
|
self.created = True
|
||||||
|
self.updateInfo()
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
def createLV(self, lv_xml = None):
|
||||||
|
if not self.created:
|
||||||
|
_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)
|
||||||
|
self.updateInfo()
|
||||||
|
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
|
||||||
|
# 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)
|
return(None)
|
||||||
|
@ -1,214 +1,32 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
##
|
##
|
||||||
|
from lxml import etree
|
||||||
|
##
|
||||||
import aif.utils
|
import aif.utils
|
||||||
import aif.disk.block_fallback as block
|
import aif.disk.block_fallback as block
|
||||||
import aif.disk.luks_fallback as luks
|
import aif.disk.luks_fallback as luks
|
||||||
import aif.disk.mdadm_fallback as mdadm
|
import aif.disk.mdadm_fallback as mdadm
|
||||||
|
|
||||||
|
|
||||||
class PV(object):
|
_logger = logging.getLogger(__name__)
|
||||||
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'))
|
|
||||||
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
|
|
||||||
meta = {}
|
|
||||||
cmd = ['pvs',
|
|
||||||
'--binary',
|
|
||||||
'--nosuffix',
|
|
||||||
'--units', 'b',
|
|
||||||
'--options', '+pvall',
|
|
||||||
'--reportformat', 'json',
|
|
||||||
self.devpath]
|
|
||||||
_meta = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
|
||||||
if _meta.returncode != 0:
|
|
||||||
self.meta = None
|
|
||||||
self.is_pooled = False
|
|
||||||
return(None)
|
|
||||||
_meta = json.loads(_meta.stdout.decode('utf-8'))['report'][0]['pv'][0]
|
|
||||||
for k, v in _meta.items():
|
|
||||||
# We *could* regex this but the pattern would be a little more complex than idea,
|
|
||||||
# especially for such predictable strings.
|
|
||||||
# These are ints.
|
|
||||||
if k in ('dev_size', 'pe_start', 'pv_ba_size', 'pv_ba_start', 'pv_ext_vsn', 'pv_free', 'pv_major',
|
|
||||||
'pv_mda_count', 'pv_mda_free', 'pv_mda_size', 'pv_mda_used_count', 'pv_minor', 'pv_pe_alloc_count',
|
|
||||||
'pv_pe_alloc_count', 'pv_size', 'pv_used'):
|
|
||||||
v = int(v)
|
|
||||||
# These are boolean.
|
|
||||||
elif k in ('pv_allocatable', 'pv_duplicate', 'pv_exported', 'pv_in_use', 'pv_missing'):
|
|
||||||
v = (True if int(v) == 1 else False)
|
|
||||||
# This is a list.
|
|
||||||
elif k == 'pv_tags':
|
|
||||||
v = [i.strip() for i in v.split(',') if i.strip() != '']
|
|
||||||
elif v.strip() == '':
|
|
||||||
v = None
|
|
||||||
meta[k] = v
|
|
||||||
self.meta = meta
|
|
||||||
self.is_pooled = True
|
|
||||||
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)
|
|
||||||
self._parseMeta()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
|
|
||||||
class VG(object):
|
|
||||||
def __init__(self, vg_xml):
|
|
||||||
self.xml = vg_xml
|
|
||||||
self.id = self.xml.attrib('id')
|
|
||||||
self.name = self.xml.attrib('name')
|
|
||||||
self.pe_size = self.xml.attrib.get('extentSize', 0)
|
|
||||||
if self.pe_size:
|
|
||||||
x = dict(zip(('from_bgn', 'size', 'type'),
|
|
||||||
aif.utils.convertSizeUnit(self.pe_size)))
|
|
||||||
if x['type']:
|
|
||||||
self.pe_size = aif.utils.size.convertStorage(self.pe_size,
|
|
||||||
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)')
|
|
||||||
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')
|
|
||||||
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]
|
|
||||||
for pv in self.pvs:
|
|
||||||
cmd.append(pv.devpath)
|
|
||||||
subprocess.run(cmd)
|
|
||||||
for pv in self.pvs:
|
|
||||||
pv._parseMeta()
|
|
||||||
self.created = True
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def createLV(self, lv_xml = None):
|
|
||||||
if not self.created:
|
|
||||||
raise RuntimeError('VG must be created before LVs can be added')
|
|
||||||
# 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'):
|
|
||||||
lv = LV(le, self)
|
|
||||||
lv.create()
|
|
||||||
# self.lvs.append(lv)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
cmd = ['vgchange',
|
|
||||||
'--activate', 'y',
|
|
||||||
'--reportformat', 'json',
|
|
||||||
self.name]
|
|
||||||
subprocess.run(cmd)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
cmd = ['vgchange',
|
|
||||||
'--activate', 'n',
|
|
||||||
'--reportformat', 'json',
|
|
||||||
self.name]
|
|
||||||
subprocess.run(cmd)
|
|
||||||
self.updateInfo()
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
def updateInfo(self):
|
|
||||||
info = {}
|
|
||||||
cmd = ['vgs',
|
|
||||||
'--binary',
|
|
||||||
'--nosuffix',
|
|
||||||
'--units', 'b',
|
|
||||||
'--options', '+vgall',
|
|
||||||
'--reportformat', 'json',
|
|
||||||
self.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_count', 'max_lv', 'max_pv', 'pv_count', 'snap_count', 'vg_extent_count', 'vg_extent_size',
|
|
||||||
'vg_free', 'vg_free_count', 'vg_mda_count', 'vg_mda_free', 'vg_mda_size', 'vg_mda_used_count',
|
|
||||||
'vg_missing_pv_count', 'vg_seqno', 'vg_size'):
|
|
||||||
v = int(v)
|
|
||||||
# booleans
|
|
||||||
elif k in ('vg_clustered', 'vg_exported', 'vg_extendable', 'vg_partial', 'vg_shared'):
|
|
||||||
v = (True if int(v) == 1 else False)
|
|
||||||
# lists
|
|
||||||
elif k in ('vg_lock_args', 'vg_permissions', 'vg_tags'): # not 100% sure about vg_permissions...
|
|
||||||
v = [i.strip() for i in v.split(',') if i.strip() != '']
|
|
||||||
elif v.strip() == '':
|
|
||||||
v = None
|
|
||||||
info[k] = v
|
|
||||||
self.info = info
|
|
||||||
return(None)
|
|
||||||
|
|
||||||
|
|
||||||
class LV(object):
|
class LV(object):
|
||||||
def __init__(self, lv_xml, vgobj):
|
def __init__(self, lv_xml, vgobj):
|
||||||
self.xml = lv_xml
|
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.id = self.xml.attrib('id')
|
||||||
self.name = self.xml.attrib('name')
|
self.name = self.xml.attrib('name')
|
||||||
self.vg = vgobj
|
self.vg = vgobj
|
||||||
self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
|
self.qualified_name = '{0}/{1}'.format(self.vg.name, self.name)
|
||||||
|
_logger.debug('Qualified name: {0}'.format(self.qualified_name))
|
||||||
self.pvs = []
|
self.pvs = []
|
||||||
if not isinstance(self.vg, VG):
|
if not isinstance(self.vg, VG):
|
||||||
raise ValueError('vgobj must be of type aif.disk.lvm.VG')
|
_logger.debug('vgobj must be of type aif.disk.lvm.VG')
|
||||||
|
raise ValueError('Invalid vgobj type')
|
||||||
self.info = None
|
self.info = None
|
||||||
self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
|
self.devpath = '/dev/{0}/{1}'.format(self.vg.name, self.name)
|
||||||
self.created = False
|
self.created = False
|
||||||
@ -219,10 +37,12 @@ class LV(object):
|
|||||||
self.pvs = []
|
self.pvs = []
|
||||||
_indexed_pvs = {i.id: i for i in self.vg.pvs}
|
_indexed_pvs = {i.id: i for i in self.vg.pvs}
|
||||||
for pe in self.xml.findall('pvMember'):
|
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')
|
pv_id = pe.attrib('source')
|
||||||
if pv_id in _indexed_pvs.keys():
|
if pv_id in _indexed_pvs.keys():
|
||||||
self.pvs.append(_indexed_pvs[pv_id])
|
self.pvs.append(_indexed_pvs[pv_id])
|
||||||
if not self.pvs: # We get all in the VG instead since none were explicitly assigned
|
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
|
self.pvs = self.vg.pvs
|
||||||
# Size processing. We have to do this after indexing PVs.
|
# Size processing. We have to do this after indexing PVs.
|
||||||
# If not x['type'], assume *extents*, not sectors
|
# If not x['type'], assume *extents*, not sectors
|
||||||
@ -254,15 +74,26 @@ class LV(object):
|
|||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
if not self.pvs:
|
if not self.pvs:
|
||||||
raise RuntimeError('Cannot create LV with no associated LVs')
|
_logger.error('Cannot create LV with no associated PVs')
|
||||||
cmd = ['lvcreate',
|
raise RuntimeError('Missing PVs')
|
||||||
|
cmd_str = ['lvcreate',
|
||||||
'--reportformat', 'json']
|
'--reportformat', 'json']
|
||||||
if self.size > 0:
|
if self.size > 0:
|
||||||
cmd.extend(['--size', self.size])
|
cmd_str.extend(['--size', self.size])
|
||||||
elif self.size == 0:
|
elif self.size == 0:
|
||||||
cmd.extend(['--extents', '100%FREE'])
|
cmd_str.extend(['--extents', '100%FREE'])
|
||||||
cmd.extend([self.name,
|
cmd_str.extend([self.name,
|
||||||
self.vg.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.vg.lvs.append(self)
|
||||||
self.created = True
|
self.created = True
|
||||||
self.updateInfo()
|
self.updateInfo()
|
||||||
@ -270,24 +101,47 @@ class LV(object):
|
|||||||
return(None)
|
return(None)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
cmd = ['lvchange',
|
_logger.info('Activating LV {0} in VG {1}.'.format(self.name, self.vg.name))
|
||||||
|
cmd_str = ['lvchange',
|
||||||
'--activate', 'y',
|
'--activate', 'y',
|
||||||
'--reportformat', 'json',
|
'--reportformat', 'json',
|
||||||
self.qualified_name]
|
self.qualified_name]
|
||||||
subprocess.run(cmd)
|
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()
|
self.updateInfo()
|
||||||
return(None)
|
return(None)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
cmd = ['lvchange',
|
_logger.info('Deactivating LV {0} in VG {1}.'.format(self.name, self.vg.name))
|
||||||
|
cmd_str = ['lvchange',
|
||||||
'--activate', 'n',
|
'--activate', 'n',
|
||||||
'--reportformat', 'json',
|
'--reportformat', 'json',
|
||||||
self.qualified_name]
|
self.qualified_name]
|
||||||
subprocess.run(cmd)
|
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()
|
self.updateInfo()
|
||||||
return(None)
|
return(None)
|
||||||
|
|
||||||
def updateInfo(self):
|
def updateInfo(self):
|
||||||
|
if not self.created:
|
||||||
|
_logger.warning('Attempted to updateInfo on an LV not created yet.')
|
||||||
|
return(None)
|
||||||
info = {}
|
info = {}
|
||||||
cmd = ['lvs',
|
cmd = ['lvs',
|
||||||
'--binary',
|
'--binary',
|
||||||
@ -297,7 +151,14 @@ class LV(object):
|
|||||||
'--reportformat', 'json',
|
'--reportformat', 'json',
|
||||||
self.qualified_name]
|
self.qualified_name]
|
||||||
_info = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
_info = subprocess.run(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
||||||
|
_logger.info('Executed: {0}'.format(' '.join(_info.args)))
|
||||||
if _info.returncode != 0:
|
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.info = None
|
||||||
self.created = False
|
self.created = False
|
||||||
return(None)
|
return(None)
|
||||||
@ -330,4 +191,271 @@ class LV(object):
|
|||||||
v = None
|
v = None
|
||||||
info[k] = v
|
info[k] = v
|
||||||
self.info = info
|
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
|
||||||
|
if not isinstance(self.device, (block.Disk,
|
||||||
|
block.Partition,
|
||||||
|
luks.LUKS,
|
||||||
|
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
|
||||||
|
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
|
||||||
|
meta = {}
|
||||||
|
cmd = ['pvs',
|
||||||
|
'--binary',
|
||||||
|
'--nosuffix',
|
||||||
|
'--units', 'b',
|
||||||
|
'--options', '+pvall',
|
||||||
|
'--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)
|
||||||
|
_meta = json.loads(_meta.stdout.decode('utf-8'))['report'][0]['pv'][0]
|
||||||
|
for k, v in _meta.items():
|
||||||
|
# We *could* regex this but the pattern would be a little more complex than idea,
|
||||||
|
# especially for such predictable strings.
|
||||||
|
# These are ints.
|
||||||
|
if k in ('dev_size', 'pe_start', 'pv_ba_size', 'pv_ba_start', 'pv_ext_vsn', 'pv_free', 'pv_major',
|
||||||
|
'pv_mda_count', 'pv_mda_free', 'pv_mda_size', 'pv_mda_used_count', 'pv_minor', 'pv_pe_alloc_count',
|
||||||
|
'pv_pe_alloc_count', 'pv_size', 'pv_used'):
|
||||||
|
v = int(v)
|
||||||
|
# These are boolean.
|
||||||
|
elif k in ('pv_allocatable', 'pv_duplicate', 'pv_exported', 'pv_in_use', 'pv_missing'):
|
||||||
|
v = (True if int(v) == 1 else False)
|
||||||
|
# This is a list.
|
||||||
|
elif k == 'pv_tags':
|
||||||
|
v = [i.strip() for i in v.split(',') if i.strip() != '']
|
||||||
|
elif v.strip() == '':
|
||||||
|
v = None
|
||||||
|
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_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)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
if self.pe_size:
|
||||||
|
x = dict(zip(('from_bgn', 'size', 'type'),
|
||||||
|
aif.utils.convertSizeUnit(self.pe_size)))
|
||||||
|
if x['type']:
|
||||||
|
self.pe_size = aif.utils.size.convertStorage(self.pe_size,
|
||||||
|
x['type'],
|
||||||
|
target = 'B')
|
||||||
|
if not aif.utils.isPowerofTwo(self.pe_size):
|
||||||
|
_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 = self.name
|
||||||
|
self.info = None
|
||||||
|
self.created = False
|
||||||
|
|
||||||
|
def addPV(self, pvobj):
|
||||||
|
if not isinstance(pvobj, 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:
|
||||||
|
_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_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
|
||||||
|
self.updateInfo()
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
def createLV(self, lv_xml = None):
|
||||||
|
if not self.created:
|
||||||
|
_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)
|
||||||
|
self.updateInfo()
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
_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):
|
||||||
|
_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_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)
|
||||||
|
_info = json.loads(_info.stdout.decode('utf-8'))['report'][0]['vg'][0]
|
||||||
|
for k, v in _info.items():
|
||||||
|
# ints
|
||||||
|
if k in ('lv_count', 'max_lv', 'max_pv', 'pv_count', 'snap_count', 'vg_extent_count', 'vg_extent_size',
|
||||||
|
'vg_free', 'vg_free_count', 'vg_mda_count', 'vg_mda_free', 'vg_mda_size', 'vg_mda_used_count',
|
||||||
|
'vg_missing_pv_count', 'vg_seqno', 'vg_size'):
|
||||||
|
v = int(v)
|
||||||
|
# booleans
|
||||||
|
elif k in ('vg_clustered', 'vg_exported', 'vg_extendable', 'vg_partial', 'vg_shared'):
|
||||||
|
v = (True if int(v) == 1 else False)
|
||||||
|
# lists
|
||||||
|
elif k in ('vg_lock_args', 'vg_permissions', 'vg_tags'): # not 100% sure about vg_permissions...
|
||||||
|
v = [i.strip() for i in v.split(',') if i.strip() != '']
|
||||||
|
elif v.strip() == '':
|
||||||
|
v = None
|
||||||
|
info[k] = v
|
||||||
|
self.info = info
|
||||||
|
_logger.debug('Rendered info: {0}'.format(info))
|
||||||
return(None)
|
return(None)
|
||||||
|
Loading…
Reference in New Issue
Block a user