Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for creating shared LVM setups #1123

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions blivet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,20 @@ def log_bd_message(level, msg):
from gi.repository import GLib
from gi.repository import BlockDev as blockdev
if arch.is_s390():
_REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm"))
_REQUESTED_PLUGIN_NAMES = set(("btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm"))
else:
_REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm"))
_REQUESTED_PLUGIN_NAMES = set(("btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm"))

# nvme plugin is not generally available
if hasattr(blockdev.Plugin, "NVME"):
_REQUESTED_PLUGIN_NAMES.add("nvme")

_requested_plugins = blockdev.plugin_specs_from_names(_REQUESTED_PLUGIN_NAMES)
# XXX force non-dbus LVM plugin
lvm_plugin = blockdev.PluginSpec()
lvm_plugin.name = blockdev.Plugin.LVM
lvm_plugin.so_name = "libbd_lvm.so.3"
_requested_plugins.append(lvm_plugin)
try:
succ_, avail_plugs = blockdev.try_reinit(require_plugins=_requested_plugins, reload=False, log_func=log_bd_message)
except GLib.GError as err:
Expand Down
48 changes: 42 additions & 6 deletions blivet/devices/lvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def get_supported_pe_sizes():

def __init__(self, name, parents=None, size=None, free=None,
pe_size=None, pe_count=None, pe_free=None, pv_count=None,
uuid=None, exists=False, sysfs_path='', exported=False):
uuid=None, exists=False, sysfs_path='', exported=False,
shared=False):
"""
:param name: the device name (generally a device node's basename)
:type name: str
Expand All @@ -124,6 +125,11 @@ def __init__(self, name, parents=None, size=None, free=None,
:type pv_count: int
:keyword uuid: the VG UUID
:type uuid: str

For non-existing VGs only:

:keyword shared: whether to create this VG as shared
:type shared: bool
"""
# These attributes are used by _add_parent, so they must be initialized
# prior to instantiating the superclass.
Expand All @@ -137,6 +143,7 @@ def __init__(self, name, parents=None, size=None, free=None,
self.pe_count = util.numeric_type(pe_count)
self.pe_free = util.numeric_type(pe_free)
self.exported = exported
self._shared = shared

# TODO: validate pe_size if given
if not self.pe_size:
Expand All @@ -157,6 +164,10 @@ def __init__(self, name, parents=None, size=None, free=None,
# >0 is fixed
self.size_policy = self.size

if self._shared:
for pv in self.parents:
pv.format._vg_shared = True

def __repr__(self):
s = super(LVMVolumeGroupDevice, self).__repr__()
s += (" free = %(free)s PE Size = %(pe_size)s PE Count = %(pe_count)s\n"
Expand Down Expand Up @@ -257,11 +268,23 @@ def _create(self):
""" Create the device. """
log_method_call(self, self.name, status=self.status)
pv_list = [pv.path for pv in self.parents]
extra = dict()
if self._shared:
extra["shared"] = ""
try:
blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size)
blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size, **extra)
except blockdev.LVMError as err:
raise errors.LVMError(err)

if self._shared:
if availability.BLOCKDEV_LVM_PLUGIN_SHARED.available:
try:
blockdev.lvm.vglock_start(self.name)
except blockdev.LVMError as err:
raise errors.LVMError(err)
else:
raise errors.LVMError("Shared LVM is not fully supported: %s" % ",".join(availability.BLOCKDEV_LVM_PLUGIN_SHARED.availability_errors))

def _post_create(self):
self._complete = True
super(LVMVolumeGroupDevice, self)._post_create()
Expand Down Expand Up @@ -682,7 +705,7 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice):
def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
fmt=None, exists=False, sysfs_path='', grow=None, maxsize=None,
percent=None, cache_request=None, pvs=None, from_lvs=None,
stripe_size=0):
stripe_size=0, shared=False):

if not exists:
if seg_type not in [None, "linear", "thin", "thin-pool", "cache", "vdo-pool", "vdo", "cache-pool"] + lvm.raid_seg_types:
Expand Down Expand Up @@ -711,6 +734,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
self.seg_type = seg_type or "linear"
self._raid_level = None
self.ignore_skip_activation = 0
self._shared = shared

self.req_grow = None
self.req_max_size = Size(0)
Expand Down Expand Up @@ -2375,7 +2399,8 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
parent_lv=None, int_type=None, origin=None, vorigin=False,
metadata_size=None, chunk_size=None, profile=None, from_lvs=None,
compression=False, deduplication=False, index_memory=0,
write_policy=None, cache_mode=None, attach_to=None, stripe_size=0):
write_policy=None, cache_mode=None, attach_to=None, stripe_size=0,
shared=False):
"""
:param name: the device name (generally a device node's basename)
:type name: str
Expand Down Expand Up @@ -2406,6 +2431,8 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
:type cache_request: :class:`~.devices.lvm.LVMCacheRequest`
:keyword pvs: list of PVs to allocate extents from (size could be specified for each PV)
:type pvs: list of :class:`~.devices.StorageDevice` or :class:`LVPVSpec` objects (tuples)
:keyword shared: whether to activate the newly create LV in shared mode
:type shared: bool

For internal LVs only:

Expand Down Expand Up @@ -2481,7 +2508,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
fmt, exists, sysfs_path, grow, maxsize,
percent, cache_request, pvs, from_lvs,
stripe_size)
stripe_size, shared)
LVMVDOPoolMixin.__init__(self, compression, deduplication, index_memory,
write_policy)
LVMVDOLogicalVolumeMixin.__init__(self)
Expand Down Expand Up @@ -2714,7 +2741,13 @@ def _setup(self, orig=False):
controllable=self.controllable)
ignore_skip_activation = self.is_snapshot_lv or self.ignore_skip_activation > 0
try:
blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation)
if self._shared:
if availability.BLOCKDEV_LVM_PLUGIN_SHARED.available:
blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation, shared=True)
else:
raise errors.LVMError("Shared LVM is not fully supported: %s" % ",".join(availability.BLOCKDEV_LVM_PLUGIN_SHARED.availability_errors))
else:
blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation)
except blockdev.LVMError as err:
raise errors.LVMError(err)

Expand Down Expand Up @@ -2754,6 +2787,9 @@ def _create(self):
if self._stripe_size:
extra["stripesize"] = str(int(self._stripe_size.convert_to("KiB")))

if self._shared:
extra["activate"] = "sy"

try:
blockdev.lvm.lvcreate(self.vg.name, self._name, self.size,
type=self.seg_type, pv_list=pvs, **extra)
Expand Down
5 changes: 5 additions & 0 deletions blivet/formats/lvmpv.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def __init__(self, **kwargs):
self.pe_start = kwargs.get("pe_start", lvm.LVM_PE_START)
self.data_alignment = kwargs.get("data_alignment", Size(0))
self._free = kwargs.get("free") # None means unknown
self._vg_shared = False

self.inconsistent_vg = False

Expand Down Expand Up @@ -155,6 +156,10 @@ def _create(self, **kwargs):
type=self.type, status=self.status)
lvm.lvm_devices_add(self.device)

if self._vg_shared:
log.info("Shared VG: skipping pvcreate, PV format will be created by vgcreate")
return

ea_yes = blockdev.ExtraArg.new("-y", "")

if lvm.HAVE_LVMDEVICES:
Expand Down
9 changes: 9 additions & 0 deletions blivet/tasks/availability.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,14 @@ def available_resource(name):
else:
BLOCKDEV_LVM_TECH_VDO = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support LVM VDO technology")

if hasattr(blockdev.LVMTech, "SHARED"):
BLOCKDEV_LVM_SHARED = BlockDevTechInfo(plugin_name="lvm",
check_fn=blockdev.lvm_is_tech_avail,
technologies={blockdev.LVMTech.SHARED: blockdev.LVMTechMode.MODIFY}) # pylint: disable=no-member
BLOCKDEV_LVM_TECH_SHARED = BlockDevMethod(BLOCKDEV_LVM_SHARED)
else:
BLOCKDEV_LVM_TECH_SHARED = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support shared LVM technology")

# libblockdev mdraid plugin required technologies and modes
BLOCKDEV_MD_ALL_MODES = (blockdev.MDTechMode.CREATE |
blockdev.MDTechMode.DELETE |
Expand Down Expand Up @@ -469,6 +477,7 @@ def available_resource(name):
BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("libblockdev loop plugin", BLOCKDEV_LOOP_TECH)
BLOCKDEV_LVM_PLUGIN = blockdev_plugin("libblockdev lvm plugin", BLOCKDEV_LVM_TECH)
BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("libblockdev lvm plugin (vdo technology)", BLOCKDEV_LVM_TECH_VDO)
BLOCKDEV_LVM_PLUGIN_SHARED = blockdev_plugin("libblockdev lvm plugin (shared LVM technology)", BLOCKDEV_LVM_TECH_SHARED)
BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("libblockdev mdraid plugin", BLOCKDEV_MD_TECH)
BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("libblockdev mpath plugin", BLOCKDEV_MPATH_TECH)
BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("libblockdev swap plugin", BLOCKDEV_SWAP_TECH)
Expand Down
30 changes: 30 additions & 0 deletions tests/unit_tests/devices_test/lvm_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,36 @@ def test_skip_activate(self):
lv.setup()
lvm.lvactivate.assert_called_with(vg.name, lv.lvname, ignore_skip=False)

@patch("blivet.tasks.availability.BLOCKDEV_LVM_PLUGIN_SHARED",
new=blivet.tasks.availability.ExternalResource(blivet.tasks.availability.AvailableMethod, ""))
def test_lv_activate_shared(self):
pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
size=Size("1 GiB"), exists=True)
vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True)
lv = LVMLogicalVolumeDevice("data_lv", parents=[vg], size=Size("500 MiB"), exists=True, shared=True)

with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
with patch.object(lv, "_pre_setup"):
lv.setup()
lvm.lvactivate.assert_called_with(vg.name, lv.lvname, ignore_skip=False, shared=True)

@patch("blivet.tasks.availability.BLOCKDEV_LVM_PLUGIN_SHARED",
new=blivet.tasks.availability.ExternalResource(blivet.tasks.availability.AvailableMethod, ""))
def test_vg_create_shared(self):
pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
size=Size("1 GiB"), exists=True)
vg = LVMVolumeGroupDevice("testvg", parents=[pv], shared=True)

self.assertTrue(pv.format._vg_shared)
with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
pv.format._create()
lvm.pvcreate.assert_not_called()

with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
vg._create()
lvm.vgcreate.assert_called_with(vg.name, [pv.path], Size("4 MiB"), shared="")
lvm.vglock_start.assert_called_with(vg.name)

def test_vg_is_empty(self):
pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
size=Size("1024 MiB"))
Expand Down
Loading