[master 7/7] Document the LV class unification changes in the release
notes
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
To give some basic hints to users as well as future developers and maintainers
of Blivet.
---
release_notes.rst | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/release_notes.rst b/release_notes.rst
index 57dcf04..29b2e71 100644
--- a/release_notes.rst
+++ b/release_notes.rst
@@ -1,6 +1,49 @@
2.0
====
+A single class for all LVs
+---------------------------
+
+In order to be better prepared for supporting things like *lvconvert*, Blivet
+now represents all LVs with a single class (keeping the name
+``LVMLogicalVolumeDevice``).
+
+
+Using the class
+++++++++++++++++
+
+In order to create LVs of various types, different values of the ``seg_type``
+parameter need to be passed. For example, to create a thin pool, ``thin-pool``
+segment type needs to be specified (optionally together with the
+thin-pool-specific parameters like ``metadata_size``) . The same applies to thin
+LVs and the ``thin`` segment type. To create a snapshot LV, one needs to specify
+the ``origin`` LV or set the ``vorigin`` flag to ``True``. Internal LVs require
+``parent_lv`` and ``int_type`` specifying the type of the internal LV.
+
+To determine the type of some LV, the newly added ``is_thin_lv``,
+``is_thin_pool``, ``is_snapshot_lv`` and ``is_internal_lv`` properties can be
+used.
+
+
+Implementation details
++++++++++++++++++++++++
+
+To avoid having a single gigantic class with hundreds of lines of code, the
+``LVMLogicalVolumeDevice`` class makes use of iheritance and "merges" together
+the ``LVMLogicalVolumeBase`` class and mixins for specific types of LVs (thin
+pool, thin LV,...) adding the type-specific methods and properties as well as
+type-specific implementations of various methods. The ``@type_specific``
+decorator makes sure that the right implementation of a method is called
+whenever there is a type-specific one (for example thin pools are created in a
+different way than good old linear LVs).
+
+The code that is common to all LVs lives in the ``LVMLogicalVolumeBase`` class
+together with properties that are required by this code. Type-specific code
+lives in the particular mixin classes and the generic/fallback implementations
+live in the (ultimate) ``LVMLogicalVolumeDevice`` class' methods decorated with
+the ``@type_specific`` decorator.
+
+
LVM RAID
---------
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/805e9572c421c3e3b8e9f35775a0...
8 years, 4 months
[master 6/7] Add a test for the to-be-used @type_specific decorator
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
When objects of various (sub)types need to be represented by a single class
(e.g. because their (sub)types can change in time), it's nice to implement the
(sub)type specific code in mixin classes the single resulting class inherits
from. With thatthere are two (major) options how to implement the methods of
such "ultimate" class:
1) Have gigantic ifs at the beginning of the methods making sure the right code
is executed for the particular object and its (sub)type.
2) Implement and use the @type_specific decorator that makes sure the method
of the right mixin class is called.
This adds an example implementation of 2) included in the test. A generic
@type_specific decorator would be even more complex and harder to use which is
why the decorator is tailored for the particular class and lives under its
namespace.
---
tests/devices_test/lvm_test.py | 132 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
index b153ce7..14cb8f1 100644
--- a/tests/devices_test/lvm_test.py
+++ b/tests/devices_test/lvm_test.py
@@ -370,3 +370,135 @@ def test_target_size(self):
lv.target_size = orig_size
self.assertEqual(lv.target_size, orig_size)
self.assertEqual(lv.size, orig_size)
+
+
+class TypeSpecificCallsTest(unittest.TestCase):
+ def test_type_specific_calls(self):
+ class A(object):
+ def __init__(self, a):
+ self._a = a
+
+ @property
+ def is_a(self):
+ return self._a == "A"
+
+ def say_hello(self):
+ return "Hello from A"
+
+ @property
+ def greeting(self):
+ return self._greet or "Hi, this is A"
+
+ @greeting.setter
+ def greeting(self, val):
+ self._greet = "Set by A: %s" % val # pylint: disable=attribute-defined-outside-init
+
+ class B(object):
+ def __init__(self, b):
+ self._b = b
+
+ @property
+ def is_b(self):
+ return self._b == "B"
+
+ def say_hello(self):
+ return "Hello from B"
+
+ @property
+ def greeting(self):
+ return self._greet or "Hi, this is B"
+
+ @greeting.setter
+ def greeting(self, val):
+ self._greet = "Set by B: %s" % val # pylint: disable=attribute-defined-outside-init
+
+ class C(A, B):
+ def __init__(self, a, b):
+ A.__init__(self, a)
+ B.__init__(self, b)
+ self._greet = None
+
+ def _get_type_classes(self):
+ """Method to get type classes for this particular instance"""
+ ret = []
+ if self.is_a:
+ ret.append(A)
+ if self.is_b:
+ ret.append(B)
+ return ret
+
+ def _try_specific_call(self, method, *args, **kwargs):
+ """Try to call a type-specific method for this particular instance"""
+ clss = self._get_type_classes()
+ for cls in clss:
+ if hasattr(cls, method):
+ # found, get the specific property
+ if isinstance(vars(cls)[method], property):
+ if len(args) == 0 and len(kwargs.keys()) == 0:
+ # this is how you call the getter method of the property object
+ ret = getattr(cls, method).__get__(self)
+ else:
+ # this is how you call the setter method of the property object
+ ret = getattr(cls, method).__set__(self, *args, **kwargs)
+ else:
+ # or call the type-specific method
+ ret = getattr(cls, method)(self, *args, **kwargs)
+ return (True, ret)
+ # not found, let the caller know
+ return (False, None)
+
+ # decorator
+ def type_specific(meth): # pylint: disable=no-self-argument
+ """Decorator that makes sure the type-specific code is executed if available"""
+ def decorated(self, *args, **kwargs):
+ found, ret = self._try_specific_call(meth.__name__, *args, **kwargs) # pylint: disable=no-member
+ if found:
+ # nothing more to do here
+ return ret
+ else:
+ return meth(self, *args, **kwargs) # pylint: disable=not-callable
+
+ return decorated
+
+ @type_specific
+ def say_hello(self):
+ return "Hello from C"
+
+ @property
+ @type_specific
+ def greeting(self):
+ return self._greet or "Hi, this is C"
+
+ @greeting.setter
+ @type_specific
+ def greeting(self, val): # pylint: disable=arguments-differ
+ self._greet = val # pylint: disable=attribute-defined-outside-init
+
+ # a non-specific instance
+ c = C(a="x", b="y")
+ self.assertEqual(c.say_hello(), "Hello from C")
+ self.assertEqual(c.greeting, "Hi, this is C")
+ c.greeting = "Welcome"
+ self.assertEqual(c.greeting, "Welcome")
+
+ # an A-specific instance
+ c = C(a="A", b="y")
+ self.assertEqual(c.say_hello(), "Hello from A")
+ self.assertEqual(c.greeting, "Hi, this is A")
+ c.greeting = "Welcome"
+ self.assertEqual(c.greeting, "Set by A: Welcome")
+
+ # a B-specific instance
+ c = C(a="x", b="B")
+ self.assertEqual(c.say_hello(), "Hello from B")
+ self.assertEqual(c.greeting, "Hi, this is B")
+ c.greeting = "Welcome"
+ self.assertEqual(c.greeting, "Set by B: Welcome")
+
+ # both A-B-specific instance
+ # A is listed first so it should win
+ c = C(a="A", b="B")
+ self.assertEqual(c.say_hello(), "Hello from A")
+ self.assertEqual(c.greeting, "Hi, this is A")
+ c.greeting = "Welcome"
+ self.assertEqual(c.greeting, "Set by A: Welcome")
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/bc117a55d0e3a9103aa4f5457c28...
8 years, 4 months
[master 5/7] Adapt our tests to the changes in representation of LVs
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
No changes in values or API except for how LVs are created.
---
tests/devicefactory_test.py | 4 ++--
tests/devices_test/lvm_test.py | 39 +++++++++------------------------------
tests/devicetree_test.py | 10 +++++-----
3 files changed, 16 insertions(+), 37 deletions(-)
diff --git a/tests/devicefactory_test.py b/tests/devicefactory_test.py
index 0545f2b..9f0516d 100644
--- a/tests/devicefactory_test.py
+++ b/tests/devicefactory_test.py
@@ -11,7 +11,6 @@
from blivet.devices import DiskFile
from blivet.devices import LUKSDevice
from blivet.devices import LVMLogicalVolumeDevice
-from blivet.devices import LVMThinLogicalVolumeDevice
from blivet.devices import MDRaidArrayDevice
from blivet.devices import PartitionDevice
from blivet.errors import RaidError
@@ -422,7 +421,8 @@ def test_get_container(self):
class LVMThinPFactoryTestCase(LVMFactoryTestCase):
- device_class = LVMThinLogicalVolumeDevice
+ # TODO: check that the LV we get is a thin pool
+ device_class = LVMLogicalVolumeDevice
device_type = devicefactory.DEVICE_TYPE_LVM_THINP
encryption_supported = False
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
index 14cb8f1..be13337 100644
--- a/tests/devices_test/lvm_test.py
+++ b/tests/devices_test/lvm_test.py
@@ -6,10 +6,6 @@
from blivet.devices import StorageDevice
from blivet.devices import LVMLogicalVolumeDevice
-from blivet.devices import LVMSnapShotDevice
-from blivet.devices import LVMThinLogicalVolumeDevice
-from blivet.devices import LVMThinPoolDevice
-from blivet.devices import LVMThinSnapShotDevice
from blivet.devices import LVMVolumeGroupDevice
from blivet.devices.lvm import LVMCacheRequest
from blivet.devices.lvm import LVPVSpec
@@ -18,10 +14,6 @@
DEVICE_CLASSES = [
LVMLogicalVolumeDevice,
- LVMSnapShotDevice,
- LVMThinLogicalVolumeDevice,
- LVMThinPoolDevice,
- LVMThinSnapShotDevice,
LVMVolumeGroupDevice,
StorageDevice
]
@@ -37,20 +29,17 @@ def test_lvmsnap_shot_device_init(self):
lv = LVMLogicalVolumeDevice("testlv", parents=[vg],
fmt=blivet.formats.get_format("xfs"))
- with self.assertRaisesRegex(ValueError, "lvm snapshot devices require an origin lv"):
- LVMSnapShotDevice("snap1", parents=[vg])
-
with self.assertRaisesRegex(ValueError, "lvm snapshot origin volume must already exist"):
- LVMSnapShotDevice("snap1", parents=[vg], origin=lv)
+ LVMLogicalVolumeDevice("snap1", parents=[vg], origin=lv)
with self.assertRaisesRegex(ValueError, "lvm snapshot origin must be a logical volume"):
- LVMSnapShotDevice("snap1", parents=[vg], origin=pv)
+ LVMLogicalVolumeDevice("snap1", parents=[vg], origin=pv)
with self.assertRaisesRegex(ValueError, "only existing vorigin snapshots are supported"):
- LVMSnapShotDevice("snap1", parents=[vg], vorigin=True)
+ LVMLogicalVolumeDevice("snap1", parents=[vg], vorigin=True)
lv.exists = True
- snap1 = LVMSnapShotDevice("snap1", parents=[vg], origin=lv)
+ snap1 = LVMLogicalVolumeDevice("snap1", parents=[vg], origin=lv)
self.assertEqual(snap1.format.type, lv.format.type)
lv.format = blivet.formats.get_format("DM_snapshot_cow", exists=True)
@@ -68,22 +57,18 @@ def test_lvmthin_snap_shot_device_init(self):
pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
size=Size("1 GiB"))
vg = LVMVolumeGroupDevice("testvg", parents=[pv])
- pool = LVMThinPoolDevice("pool1", parents=[vg], size=Size("500 MiB"))
- thinlv = LVMThinLogicalVolumeDevice("thinlv", parents=[pool],
- size=Size("200 MiB"))
-
- with self.assertRaisesRegex(ValueError, "lvm thin snapshots require an origin"):
- LVMThinSnapShotDevice("snap1", parents=[pool])
+ pool = LVMLogicalVolumeDevice("pool1", parents=[vg], size=Size("500 MiB"), seg_type="thin-pool")
+ thinlv = LVMLogicalVolumeDevice("thinlv", parents=[pool], size=Size("200 MiB"), seg_type="thin")
with self.assertRaisesRegex(ValueError, "lvm snapshot origin volume must already exist"):
- LVMThinSnapShotDevice("snap1", parents=[pool], origin=thinlv)
+ LVMLogicalVolumeDevice("snap1", parents=[pool], origin=thinlv, seg_type="thin")
with self.assertRaisesRegex(ValueError, "lvm snapshot origin must be a logical volume"):
- LVMThinSnapShotDevice("snap1", parents=[pool], origin=pv)
+ LVMLogicalVolumeDevice("snap1", parents=[pool], origin=pv, seg_type="thin")
# now make the constructor succeed so we can test some properties
thinlv.exists = True
- snap1 = LVMThinSnapShotDevice("snap1", parents=[pool], origin=thinlv)
+ snap1 = LVMLogicalVolumeDevice("snap1", parents=[pool], origin=thinlv, seg_type="thin")
self.assertEqual(snap1.isleaf, True)
self.assertEqual(snap1.direct, True)
self.assertEqual(thinlv.isleaf, True)
@@ -287,12 +272,6 @@ def test_lvm_logical_volume_insuf_seg_type(self):
fmt=blivet.formats.get_format("xfs"),
exists=False, pvs=[pv_spec, pv_spec2])
- # no non-linear thin pools (yet)
- with self.assertRaises(ValueError):
- lv = LVMThinPoolDevice("testlv", parents=[vg], size=Size("512 MiB"),
- fmt=blivet.formats.get_format("xfs"),
- exists=False, seg_type="striped")
-
def test_lvm_logical_volume_metadata_size(self):
pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
size=Size("1025 MiB"))
diff --git a/tests/devicetree_test.py b/tests/devicetree_test.py
index e163e63..5cb2c0e 100644
--- a/tests/devicetree_test.py
+++ b/tests/devicetree_test.py
@@ -7,8 +7,8 @@
from blivet import devicefactory
from blivet import util
from blivet.udev import trigger
-from blivet.devices import LVMSnapShotDevice, LVMThinSnapShotDevice
from blivet.devices import StorageDevice
+from blivet.devices.lvm import LVMLogicalVolumeDevice
from blivet.devicetree import DeviceTree
from blivet.formats import get_format
@@ -193,8 +193,8 @@ def _set_up_storage(self):
def setUp(self):
super(BlivetResetTestCase, self).setUp() # pylint: disable=bad-super-call
root = self.blivet.lvs[0]
- snap = LVMSnapShotDevice("rootsnap1", parents=[root.vg], origin=root,
- size=Size("768MiB"))
+ snap = LVMLogicalVolumeDevice("rootsnap1", parents=[root.vg], origin=root,
+ size=Size("768MiB"))
self.blivet.create_device(snap)
self.blivet.do_it()
@@ -227,8 +227,8 @@ def setUp(self):
super(BlivetResetTestCase, self).setUp() # pylint: disable=bad-super-call
root = self.blivet.thinlvs[0]
- snap = LVMThinSnapShotDevice("rootsnap1", parents=[root.pool],
- origin=root)
+ snap = LVMLogicalVolumeDevice("rootsnap1", parents=[root.pool],
+ origin=root)
self.blivet.create_device(snap)
self.blivet.do_it()
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/db093872af63ff53ee3a571d293a...
8 years, 4 months
[master 4/7] Transform the is_name_valid() into an instance method
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
Apart from the tests we alway call this code on a particular instance. And
sometimes it's impossible to just tell if a name is valid without looking on
some other properties of the object.
---
blivet/devices/btrfs.py | 3 +--
blivet/devices/device.py | 3 +--
blivet/devices/file.py | 3 +--
blivet/devices/nfs.py | 3 +--
blivet/devices/storage.py | 5 ++---
tests/devices_test/device_names_test.py | 26 ++++++++++++++++++++------
6 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/blivet/devices/btrfs.py b/blivet/devices/btrfs.py
index e2f6ee1..988d830 100644
--- a/blivet/devices/btrfs.py
+++ b/blivet/devices/btrfs.py
@@ -151,8 +151,7 @@ def fstab_spec(self):
spec = super(BTRFSDevice, self).fstab_spec
return spec
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, name):
# Override StorageDevice.is_name_valid to allow pretty much anything
return not('\x00' in name)
diff --git a/blivet/devices/device.py b/blivet/devices/device.py
index a111a58..8bc8410 100644
--- a/blivet/devices/device.py
+++ b/blivet/devices/device.py
@@ -301,8 +301,7 @@ def packages(self):
return packages
- @classmethod
- def is_name_valid(cls, name): # pylint: disable=unused-argument
+ def is_name_valid(self, name): # pylint: disable=unused-argument
"""Is the device name valid for the device type?"""
# By default anything goes
diff --git a/blivet/devices/file.py b/blivet/devices/file.py
index dc53a7a..8bf0e6f 100644
--- a/blivet/devices/file.py
+++ b/blivet/devices/file.py
@@ -128,8 +128,7 @@ def _destroy(self):
log_method_call(self, self.name, status=self.status)
os.unlink(self.path)
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, name):
# Override StorageDevice.is_name_valid to allow /
return not('\x00' in name or name == '.' or name == '..')
diff --git a/blivet/devices/nfs.py b/blivet/devices/nfs.py
index 3143d7c..f4c96f7 100644
--- a/blivet/devices/nfs.py
+++ b/blivet/devices/nfs.py
@@ -74,7 +74,6 @@ def destroy(self):
def update_size(self):
pass
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, name):
# Override StorageDevice.is_name_valid to allow /
return not('\x00' in name or name == '.' or name == '..')
diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py
index c91b9d1..7440070 100644
--- a/blivet/devices/storage.py
+++ b/blivet/devices/storage.py
@@ -806,8 +806,7 @@ def populate_ksdata(self, data):
if data.mountpoint.endswith("."):
data.mountpoint += str(self.id)
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, name):
# This device corresponds to a file in /dev, so no /'s or nulls,
# and the name cannot be . or ..
@@ -815,7 +814,7 @@ def is_name_valid(cls, name):
# is an imperfect world of joy and sorrow mingled. For cciss, split
# the path into its components and do the real check on each piece
if name.startswith("cciss/"):
- return all(cls.is_name_valid(n) for n in name.split('/'))
+ return all(self.is_name_valid(n) for n in name.split('/'))
badchars = any(c in ('\x00', '/') for c in name)
return not(badchars or name == '.' or name == '..')
diff --git a/tests/devices_test/device_names_test.py b/tests/devices_test/device_names_test.py
index 2d7ae7f..de255d0 100644
--- a/tests/devices_test/device_names_test.py
+++ b/tests/devices_test/device_names_test.py
@@ -5,6 +5,8 @@
from blivet.devices import LVMVolumeGroupDevice
from blivet.devices import LVMLogicalVolumeDevice
from blivet.devices import StorageDevice
+from blivet.size import Size
+import blivet
class DeviceNameTestCase(unittest.TestCase):
@@ -16,29 +18,41 @@ def test_storage_device(self):
good_names = ['sda1', '1sda', 'good-name', 'cciss/c0d0']
bad_names = ['sda/1', 'sda\x00', '.', '..', 'cciss/..']
+ sd = StorageDevice("tester")
+
for name in good_names:
- self.assertTrue(StorageDevice.is_name_valid(name))
+ self.assertTrue(sd.is_name_valid(name))
for name in bad_names:
- self.assertFalse(StorageDevice.is_name_valid(name))
+ self.assertFalse(sd.is_name_valid(name))
def test_volume_group(self):
good_names = ['vg00', 'group-name', 'groupname-']
bad_names = ['-leading-hyphen', 'únicode', 'sp aces']
+ pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
+ size=Size("1 GiB"))
+ vg = LVMVolumeGroupDevice("testvg", parents=[pv])
+
for name in good_names:
- self.assertTrue(LVMVolumeGroupDevice.is_name_valid(name))
+ self.assertTrue(vg.is_name_valid(name))
for name in bad_names:
- self.assertFalse(LVMVolumeGroupDevice.is_name_valid(name))
+ self.assertFalse(vg.is_name_valid(name))
def test_logical_volume(self):
good_names = ['lv00', 'volume-name', 'volumename-']
bad_names = ['-leading-hyphen', 'únicode', 'sp aces',
'snapshot47', 'pvmove0', 'sub_tmetastring']
+ pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
+ size=Size("1 GiB"))
+ vg = LVMVolumeGroupDevice("testvg", parents=[pv])
+ lv = LVMLogicalVolumeDevice("testlv", parents=[vg],
+ fmt=blivet.formats.get_format("xfs"))
+
for name in good_names:
- self.assertTrue(LVMLogicalVolumeDevice.is_name_valid(name))
+ self.assertTrue(lv.is_name_valid(name))
for name in bad_names:
- self.assertFalse(LVMLogicalVolumeDevice.is_name_valid(name))
+ self.assertFalse(lv.is_name_valid(name))
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/b7282125ce85e1f1010b8c339616...
8 years, 4 months
[master 3/7] Adapt our code to changes in the representation of LVs
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
All LVs are now instances of a single class. The type is determined by the
properties of the object like its segment type, whether it has a parent LV
(internal LVs) or an origin (snapshots). Thus we need to make sure the right
values are passed in all the places we create LV objects.
---
blivet/blivet.py | 16 +++++++---------
blivet/devicefactory.py | 4 ++--
blivet/devices/__init__.py | 2 +-
blivet/devices/lvm.py | 1 +
blivet/partitioning.py | 2 +-
blivet/populator/helpers/lvm.py | 32 +++++++++++++-------------------
6 files changed, 25 insertions(+), 32 deletions(-)
diff --git a/blivet/blivet.py b/blivet/blivet.py
index 19ff1b3..3ab3bf3 100644
--- a/blivet/blivet.py
+++ b/blivet/blivet.py
@@ -32,7 +32,7 @@
from .storage_log import log_method_call, log_exception_info
from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice
-from .devices import LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice
+from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice
from .devices import MDRaidArrayDevice, PartitionDevice, TmpFSDevice, device_path_to_name
from .deviceaction import ActionCreateDevice, ActionCreateFormat, ActionDestroyDevice
from .deviceaction import ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat
@@ -916,6 +916,11 @@ def new_lv(self, *args, **kwargs):
else:
vg = parent
+ if thin_volume:
+ kwargs["seg_type"] = "thin"
+ if thin_pool:
+ kwargs["seg_type"] = "thin-pool"
+
mountpoint = kwargs.pop("mountpoint", None)
if 'fmt_type' in kwargs:
kwargs["fmt"] = get_format(kwargs.pop("fmt_type"),
@@ -956,14 +961,7 @@ def new_lv(self, *args, **kwargs):
if cache_req:
raise ValueError("Creating cached thin volumes and pools is not supported")
- if thin_pool:
- device_class = LVMThinPoolDevice
- elif thin_volume:
- device_class = LVMThinLogicalVolumeDevice
- else:
- device_class = LVMLogicalVolumeDevice
-
- return device_class(name, *args, **kwargs)
+ return LVMLogicalVolumeDevice(name, *args, **kwargs)
def new_btrfs(self, *args, **kwargs):
""" Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice.
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
index 1fbfb01..771329d 100644
--- a/blivet/devicefactory.py
+++ b/blivet/devicefactory.py
@@ -23,7 +23,7 @@
from .storage_log import log_method_call
from .errors import DeviceFactoryError, StorageError
from .devices import BTRFSDevice, DiskDevice
-from .devices import LUKSDevice, LVMLogicalVolumeDevice, LVMThinPoolDevice
+from .devices import LUKSDevice, LVMLogicalVolumeDevice
from .devices import PartitionDevice, MDRaidArrayDevice
from .formats import get_format
from .devicelibs import btrfs
@@ -70,7 +70,7 @@ def is_supported_device_type(device_type):
elif device_type == DEVICE_TYPE_DISK:
devices = [DiskDevice]
elif device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP):
- devices = [LVMLogicalVolumeDevice, LVMThinPoolDevice]
+ devices = [LVMLogicalVolumeDevice]
elif device_type == DEVICE_TYPE_PARTITION:
devices = [PartitionDevice]
elif device_type == DEVICE_TYPE_MD:
diff --git a/blivet/devices/__init__.py b/blivet/devices/__init__.py
index 4f697ca..0b75b78 100644
--- a/blivet/devices/__init__.py
+++ b/blivet/devices/__init__.py
@@ -26,7 +26,7 @@
from .partition import PartitionDevice
from .dm import DMDevice, DMLinearDevice, DMCryptDevice
from .luks import LUKSDevice
-from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice
+from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice
from .md import MDBiosRaidArrayDevice, MDContainerDevice, MDRaidArrayDevice
from .btrfs import BTRFSDevice, BTRFSVolumeDevice, BTRFSSubVolumeDevice, BTRFSSnapShotDevice
from .file import FileDevice, DirectoryDevice, SparseFileDevice
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
index 7d6b329..556375e 100644
--- a/blivet/devices/lvm.py
+++ b/blivet/devices/lvm.py
@@ -995,6 +995,7 @@ def parent_lv(self, parent_lv):
self._parent_lv = parent_lv
if self._parent_lv:
self._parent_lv.add_internal_lv(self)
+ self._vg = self._parent_lv.vg
@property
@util.requires_property("is_internal_lv")
diff --git a/blivet/partitioning.py b/blivet/partitioning.py
index dfbd218..298f94c 100644
--- a/blivet/partitioning.py
+++ b/blivet/partitioning.py
@@ -1473,7 +1473,7 @@ class ThinPoolChunk(VGChunk):
def __init__(self, pool, requests=None):
"""
:param pool: the thin pool whose free space this chunk represents
- :type pool: :class:`~.devices.LVMThinPoolDevice`
+ :type pool: :class:`~.devices.LVMLogicalVolumeDevice`
:keyword requests: list of requests to add initially
:type requests: list of :class:`LVRequest`
"""
diff --git a/blivet/populator/helpers/lvm.py b/blivet/populator/helpers/lvm.py
index ff18ea0..aad680f 100644
--- a/blivet/populator/helpers/lvm.py
+++ b/blivet/populator/helpers/lvm.py
@@ -27,9 +27,7 @@
from ... import udev
from ...devicelibs import lvm
-from ...devices import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice
-from ...devices import LVMThinPoolDevice, LVMThinSnapShotDevice, LVMThinLogicalVolumeDevice
-from ...devices.lvm import get_internal_lv_class
+from ...devices.lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMInternalLVtype
from ...errors import DeviceTreeError, DuplicateVGError
from ...flags import flags
from ...size import Size
@@ -147,7 +145,6 @@ def add_lv(lv):
lv_size = Size(lv.size)
lv_type = lv.segtype
- lv_class = LVMLogicalVolumeDevice
lv_parents = [vg_device]
lv_kwargs = {}
name = "%s-%s" % (vg_name, lv_name)
@@ -175,7 +172,6 @@ def add_lv(lv):
origin = self._devicetree.get_device_by_name(origin_device_name)
lv_kwargs["origin"] = origin
- lv_class = LVMSnapShotDevice
elif lv_attr[0] == 'v':
# skip vorigins
return
@@ -187,7 +183,8 @@ def add_lv(lv):
return
elif lv_attr[0] == 't':
# thin pool
- lv_class = LVMThinPoolDevice
+ # nothing to do here
+ pass
elif lv_attr[0] == 'V':
# thin volume
pool_name = blockdev.lvm.thlvpoolname(vg_name, lv_name)
@@ -200,9 +197,6 @@ def add_lv(lv):
add_required_lv(origin_device_name, "failed to locate origin lv")
origin = self._devicetree.get_device_by_name(origin_device_name)
lv_kwargs["origin"] = origin
- lv_class = LVMThinSnapShotDevice
- else:
- lv_class = LVMThinLogicalVolumeDevice
lv_parents = [self._devicetree.get_device_by_name(pool_device_name)]
elif lv_name.endswith(']'):
@@ -222,9 +216,9 @@ def add_lv(lv):
lv_dev = self._devicetree.get_device_by_uuid(lv_uuid)
if lv_dev is None:
- lv_device = lv_class(lv_name, parents=lv_parents,
- uuid=lv_uuid, size=lv_size, seg_type=lv_type,
- exists=True, **lv_kwargs)
+ lv_device = LVMLogicalVolumeDevice(lv_name, parents=lv_parents,
+ uuid=lv_uuid, size=lv_size, seg_type=lv_type,
+ exists=True, **lv_kwargs)
self._devicetree._add_device(lv_device)
if flags.installer_mode:
lv_device.setup()
@@ -249,17 +243,18 @@ def create_internal_lv(lv):
lv_uuid = lv.uuid
lv_attr = lv.attr
lv_size = Size(lv.size)
- lv_type = lv.segtype
+ seg_type = lv.segtype
- matching_cls = get_internal_lv_class(lv_attr)
- if matching_cls is None:
- raise DeviceTreeError("No internal LV class supported for type '%s'" % lv_attr[0])
+ lv_type = LVMInternalLVtype.get_type(lv_attr, lv_name)
+ if lv_type is LVMInternalLVtype.unknown:
+ raise DeviceTreeError("Internal LVs of type '%s' are not supported" % lv_attr[0])
# strip the "[]"s marking the LV as internal
lv_name = lv_name.strip("[]")
# don't know the parent LV yet, will be set later
- new_lv = matching_cls(lv_name, vg_device, parent_lv=None, size=lv_size, uuid=lv_uuid, exists=True, seg_type=lv_type)
+ new_lv = LVMLogicalVolumeDevice(lv_name, vg_device, parent_lv=None, int_type=lv_type,
+ size=lv_size, uuid=lv_uuid, exists=True, seg_type=seg_type)
if new_lv.status:
new_lv.update_sysfs_path()
new_lv.update_size()
@@ -299,8 +294,7 @@ def create_internal_lv(lv):
orphan_lvs[full_name] = new_lv
all_lvs.append(new_lv)
- # assign parents to internal LVs (and vice versa, see
- # :class:`~.devices.lvm.LVMInternalLogicalVolumeDevice`)
+ # assign parents to internal LVs (and vice versa)
for lv in orphan_lvs.values():
parent_lv = lvm.determine_parent_lv(vg_name, lv, all_lvs)
if parent_lv:
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/6a09fbe8713d355733bc9bc0a832...
8 years, 4 months
[master 2/7] Rewrite the code for representing LVs
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
Having specific classes for various types of LVs is nice because Python makes
sure the right code is executed based on the type (class) of the LV. However, a
big drawback of this is that it is not possible to change the type (class) of
the object once it is created. This makes things like 'lvconvert' or even thin
snapshot creation and activation impossible to represent.
Thus we need to represent all LVs with single class making a lot of decisions on
our own. However, that doesn't mean we have to put all the code into a single
class. By utilizing some nice tricks, we can easily put type-specific code into
type-specific mixin classes and make the final/ultimate class inherit from
them. That way the type of an LV is more a dynamic property than a
once-and-for-always-given thing.
---
blivet/devicelibs/lvm.py | 2 +
blivet/devices/lvm.py | 1654 +++++++++++++++++++++++-----------------------
2 files changed, 845 insertions(+), 811 deletions(-)
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index f878291..194055c 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -24,6 +24,7 @@
import re
from collections import namedtuple
+import itertools
import gi
gi.require_version("BlockDev", "1.0")
@@ -50,6 +51,7 @@
LVM_THINP_MAX_CHUNK_SIZE = Size("1 GiB")
raid_levels = raid.RAIDLevels(["linear", "striped", "raid1", "raid4", "raid5", "raid6", "raid10"])
+raid_seg_types = list(itertools.chain.from_iterable([level.names for level in raid_levels if level.name != "linear"]))
ThPoolProfile = namedtuple("ThPoolProfile", ["name", "desc"])
KNOWN_THPOOL_PROFILES = (ThPoolProfile("thin-generic", N_("Generic")),
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
index e820978..7d6b329 100644
--- a/blivet/devices/lvm.py
+++ b/blivet/devices/lvm.py
@@ -20,15 +20,14 @@
#
from decimal import Decimal
-from six import add_metaclass
-import abc
import copy
import pprint
import re
import os
import time
-import itertools
from collections import namedtuple
+from functools import wraps
+from enum import Enum
import gi
gi.require_version("BlockDev", "1.0")
@@ -44,7 +43,6 @@
from .. import udev
from ..size import Size, KiB, MiB, ROUND_UP, ROUND_DOWN
from ..tasks import availability
-from ..threads import SynchronizedABCMeta
import logging
log = logging.getLogger("blivet")
@@ -58,23 +56,6 @@
from .md import MDRaidArrayDevice
from .cache import Cache, CacheStats, CacheRequest
-_INTERNAL_LV_CLASSES = []
-
-
-def get_internal_lv_class(lv_attr):
- if lv_attr[0] == "C":
- # cache pools and internal data LV of cache pools need a more complicated check
- if lv_attr[6] == "C":
- # target type == cache -> cache pool
- return LVMCachePoolLogicalVolumeDevice
- else:
- return LVMDataLogicalVolumeDevice
- for cls in _INTERNAL_LV_CLASSES:
- if lv_attr[0] in cls.attr_letters:
- return cls
-
- return None
-
class LVPVSpec(object):
""" Class for specifying how much space on a PV should be allocated for some LV """
@@ -86,6 +67,11 @@ def __init__(self, pv, size):
""" A namedtuple class holding the information about PV's (usable) size and free space """
+class NotTypeSpecific(Exception):
+ """Exception class for invalid type-specific calls"""
+ pass
+
+
class LVMVolumeGroupDevice(ContainerDevice):
""" An LVM Volume Group """
@@ -299,10 +285,7 @@ def _add_log_vol(self, lv):
# verify we have the space, then add it
# do not verify for growing vg (because of ks)
- # FIXME: add a "isthin" property and/or "ispool"?
- if not lv.exists and not self.growable and \
- not isinstance(lv, LVMThinLogicalVolumeDevice) and \
- lv.size > self.free_space:
+ if not lv.exists and not self.growable and not lv.is_thin_lv and lv.size > self.free_space:
raise errors.DeviceError("new lv is too large to fit in free space", self.name)
log.debug("Adding %s/%s to %s", lv.name, lv.size, self.name)
@@ -490,11 +473,11 @@ def lvs(self):
@property
def thinpools(self):
- return [l for l in self._lvs if isinstance(l, LVMThinPoolDevice)]
+ return [l for l in self._lvs if l.is_thin_pool]
@property
def thinlvs(self):
- return [l for l in self._lvs if isinstance(l, LVMThinLogicalVolumeDevice)]
+ return [l for l in self._lvs if l.is_thin_lv]
@property
def cached_lvs(self):
@@ -536,8 +519,7 @@ def populate_ksdata(self, data):
# reserved percent/space
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, name):
# No . or ..
if name == '.' or name == '..':
return False
@@ -557,59 +539,26 @@ def is_name_valid(cls, name):
return True
-class LVMLogicalVolumeDevice(DMDevice, RaidDevice):
+class LVMLogicalVolumeBase(DMDevice, RaidDevice):
+ """Abstract base class for LVM LVs
+
+ Attributes, properties and methods defined in this class are common to all
+ LVs.
+
+ """
- """ An LVM Logical Volume """
_type = "lvmlv"
- _resizable = True
_packages = ["lvm2"]
- _container_class = LVMVolumeGroupDevice
_external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN]
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):
- """
- :param name: the device name (generally a device node's basename)
- :type name: str
- :keyword exists: does this device exist?
- :type exists: bool
- :keyword size: the device's size
- :type size: :class:`~.size.Size`
- :keyword parents: a list of parent devices
- :type parents: list of :class:`StorageDevice`
- :keyword fmt: this device's formatting
- :type fmt: :class:`~.formats.DeviceFormat` or a subclass of it
- :keyword sysfs_path: sysfs device path
- :type sysfs_path: str
- :keyword uuid: the device UUID
- :type uuid: str
-
- For existing LVs only:
-
- :keyword seg_type: segment type (eg: "linear", "raid1")
- :type seg_type: str
-
- For non-existent LVs only:
-
- :keyword grow: whether to grow this LV
- :type grow: bool
- :keyword maxsize: maximum size for growable LV
- :type maxsize: :class:`~.size.Size`
- :keyword percent: percent of VG space to take
- :type percent: int
- :keyword cache_request: parameters of requested cache (if any)
- :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)
-
- """
if not exists:
- raid_level_names = list(itertools.chain.from_iterable([level.names for level in lvm.raid_levels]))
- if seg_type not in [None, "linear"] + raid_level_names:
+ if seg_type not in [None, "linear", "thin", "thin-pool", "cache"] + lvm.raid_seg_types:
raise ValueError("Invalid or unsupported segment type: %s" % seg_type)
- if seg_type and seg_type != "linear" and not pvs:
+ if seg_type and seg_type in lvm.raid_seg_types and not pvs:
raise ValueError("List of PVs has to be given for every non-linear LV")
elif (not seg_type or seg_type == "linear") and pvs:
if not all(isinstance(pv, LVPVSpec) for pv in pvs):
@@ -632,7 +581,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
self.uuid = uuid
self.seg_type = seg_type or "linear"
self._raid_level = None
- if self.seg_type in itertools.chain.from_iterable([level.names for level in lvm.raid_levels]):
+ if self.seg_type in lvm.raid_seg_types:
self._raid_level = lvm.raid_levels.raid_level(self.seg_type)
else:
self._raid_level = lvm.raid_levels.raid_level("linear")
@@ -681,11 +630,6 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
if self._pv_specs:
self._assign_pv_space()
- # check that we got parents as expected and add this device to them now
- # that it is fully-initialized
- self._check_parents()
- self._add_to_parents()
-
def _assign_pv_space(self):
if not self.is_raid_lv:
# nothing to do for non-RAID (and thus non-striped) LVs here
@@ -693,52 +637,50 @@ def _assign_pv_space(self):
for spec in self._pv_specs:
spec.size = self._raid_level.get_base_member_size(self.size + self._metadata_size, len(self._pv_specs))
- def _check_parents(self):
- """Check that this device has parents as expected"""
-
- if isinstance(self.parents, (list, ParentList)):
- if len(self.parents) != 1:
- raise ValueError("constructor requires a single %s instance" % self._container_class.__name__)
-
- container = self.parents[0]
- else:
- container = self.parents
-
- if not isinstance(container, self._container_class):
- raise ValueError("constructor requires a %s instance" % self._container_class.__name__)
-
- def _add_to_parents(self):
- """Add this device to its parents"""
-
- # a normal LV has only exactly parent -- the VG it belongs to
- self._parents[0]._add_log_vol(self)
-
@property
def members(self):
return self.vg.pvs
@property
def is_raid_lv(self):
- return self.seg_type != "linear" and self._raid_level.name != "linear"
+ seg_type = self.seg_type
+ if self.seg_type == "cache":
+ # for a cached LV we are interested in the segment type of its
+ # origin LV (the original non-cached LV)
+ for lv in self._internal_lvs:
+ if lv.int_lv_type == LVMInternalLVtype.origin:
+ seg_type = lv.seg_type
+ return seg_type in lvm.raid_seg_types
+
+ @property
+ def vg(self):
+ """This Logical Volume's Volume Group."""
+ if self._parents:
+ return self._parents[0]
+ else:
+ return None
@property
def _num_raid_pvs(self):
if self.exists:
- image_lvs = [int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMImageLogicalVolumeDevice)]
+ image_lvs = [int_lv for int_lv in self._internal_lvs
+ if int_lv.is_internal_lv and int_lv.int_lv_type == LVMInternalLVtype.image]
return len(image_lvs) or 1
else:
return len(self._pv_specs)
@property
def log_size(self):
- log_lvs = (int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMLogLogicalVolumeDevice))
+ log_lvs = (int_lv for int_lv in self._internal_lvs
+ if int_lv.is_internal_lv and int_lv.int_lv_type == LVMInternalLVtype.log)
return Size(sum(lv.size for lv in log_lvs))
@property
def metadata_size(self):
""" Size of the meta data space this LV has available (see also :property:`metadata_vg_space_used`) """
if self.exists:
- md_lvs = (int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMMetadataLogicalVolumeDevice))
+ md_lvs = (int_lv for int_lv in self._internal_lvs
+ if int_lv.is_internal_lv and int_lv.int_lv_type == LVMInternalLVtype.meta)
return Size(sum(lv.size for lv in md_lvs))
ret = self._metadata_size
@@ -746,19 +688,9 @@ def metadata_size(self):
ret += self.cache.md_size
return ret
- def __repr__(self):
- s = DMDevice.__repr__(self)
- s += (" VG device = %(vgdev)r\n"
- " segment type = %(type)s percent = %(percent)s\n"
- " VG space used = %(vgspace)s" %
- {"vgdev": self.vg, "percent": self.req_percent,
- "type": self.seg_type,
- "vgspace": self.vg_space_used})
- return s
-
@property
def dict(self):
- d = super(LVMLogicalVolumeDevice, self).dict
+ d = super(LVMLogicalVolumeBase, self).dict
if self.exists:
d.update({"vgspace": self.vg_space_used})
else:
@@ -770,31 +702,15 @@ def dict(self):
def mirrored(self):
return self._raid_level and self._raid_level.has_redundancy()
- def _set_size(self, size):
- if not isinstance(size, Size):
- raise ValueError("new size must of type Size")
-
- size = self.vg.align(size)
- log.debug("trying to set lv %s size to %s", self.name, size)
- # Don't refuse to set size if we think there's not enough space in the
- # VG for an existing LV, since it's existence proves there is enough
- # space for it. A similar reasoning applies to shrinking the LV.
- if not self.exists and \
- not isinstance(self, LVMThinLogicalVolumeDevice) and \
- size > self.size and size > self.vg.free_space + self.vg_space_used:
- log.error("failed to set size: %s short", size - (self.vg.free_space + self.vg_space_used))
- raise ValueError("not enough free space in volume group")
-
- super(LVMLogicalVolumeDevice, self)._set_size(size)
-
- size = property(StorageDevice._get_size, _set_size)
-
@property
- def max_size(self):
- """ The maximum size this lv can be. """
- max_lv = self.size + self.vg.free_space
- max_format = self.format.max_size
- return min(max_lv, max_format) if max_format else max_lv
+ def vg_space_used(self):
+ """ Space occupied by this LV, not including snapshots. """
+ if self.cached:
+ cache_size = self.cache.size
+ else:
+ cache_size = Size(0)
+
+ return self.data_vg_space_used + self.metadata_vg_space_used + cache_size
@property
def data_vg_space_used(self):
@@ -835,16 +751,6 @@ def metadata_vg_space_used(self):
return non_raid_base
@property
- def vg_space_used(self):
- """ Space occupied by this LV, not including snapshots. """
- if self.cached:
- cache_size = self.cache.size
- else:
- cache_size = Size(0)
-
- return self.data_vg_space_used + self.metadata_vg_space_used + cache_size
-
- @property
def pv_space_used(self):
"""
:returns: space occupied by this LV on its VG's PVs (if we have and idea)
@@ -853,16 +759,6 @@ def pv_space_used(self):
"""
return self._pv_specs
- def _set_format(self, fmt):
- super(LVMLogicalVolumeDevice, self)._set_format(fmt)
- for snapshot in (s for s in self.snapshots if not s.exists):
- snapshot._update_format_from_origin()
-
- @property
- def vg(self):
- """ This Logical Volume's Volume Group. """
- return self.parents[0]
-
@property
def container(self):
return self.vg
@@ -893,19 +789,24 @@ def _get_name(self):
else:
return super()._get_name()
- @property
- def lvname(self):
- """ The LV's name (not including VG name). """
- return self._name
-
- @property
- def complete(self):
- """ Test if vg exits and if it has all pvs. """
- return self.vg.complete
+ def check_size(self):
+ """ Check to make sure the size of the device is allowed by the
+ format used.
- def setup_parents(self, orig=False):
- # parent is a vg, which has no formatting (or device for that matter)
- Device.setup_parents(self, orig=orig)
+ Returns:
+ 0 - ok
+ 1 - Too large
+ -1 - Too small
+ """
+ if self.format.max_size and self.size > self.format.max_size:
+ return 1
+ elif (self.format.min_size and
+ (not self.req_grow and
+ self.size < self.format.min_size) or
+ (self.req_grow and self.req_max_size and
+ self.req_max_size < self.format.min_size)):
+ return -1
+ return 0
def _pre_setup(self, orig=False):
# If the lvmetad socket exists and any PV is inactive before we call
@@ -913,7 +814,7 @@ def _pre_setup(self, orig=False):
# activation before trying to manually activate this LV.
auto_activate = (lvm.lvmetad_socket_exists() and
any(not pv.status for pv in self.vg.pvs))
- if not super(LVMLogicalVolumeDevice, self)._pre_setup(orig=orig):
+ if not super(LVMLogicalVolumeBase, self)._pre_setup(orig=orig):
return False
if auto_activate:
@@ -939,12 +840,6 @@ def _pre_setup(self, orig=False):
return True
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- blockdev.lvm.lvactivate(self.vg.name, self._name)
-
def _teardown(self, recursive=None):
""" Close, or tear down, a device. """
log_method_call(self, self.name, status=self.status,
@@ -963,291 +858,206 @@ def _post_teardown(self, recursive=False):
else:
raise
- def _pre_create(self):
- super(LVMLogicalVolumeDevice, self)._pre_create()
+ def _pre_destroy(self):
+ StorageDevice._pre_destroy(self)
+ # set up the vg's pvs so lvm can remove the lv
+ self.vg.setup_parents(orig=True)
- try:
- vg_info = blockdev.lvm.vginfo(self.vg.name)
- except blockdev.LVMError as lvmerr:
- log.error("Failed to get free space for the %s VG: %s", self.vg.name, lvmerr)
- # nothing more can be done, we don't know the VG's free space
- return
+ @property
+ def lvname(self):
+ """ The LV's name (not including VG name). """
+ return self._name
- extent_size = Size(vg_info.extent_size)
- extents_free = vg_info.free_count
- can_use = extent_size * extents_free
+ @property
+ def complete(self):
+ """ Test if vg exits and if it has all pvs. """
+ return self.vg.complete
- if self.size > can_use:
- msg = ("%s LV's size (%s) exceeds the VG's usable free space (%s),"
- "shrinking the LV") % (self.name, self.size, can_use)
- log.warning(msg)
- self.size = can_use
+ @property
+ def isleaf(self):
+ # Thin snapshots do not need to be removed prior to removal of the
+ # origin, but the old snapshots do.
+ non_thin_snapshots = any(s for s in self.snapshots
+ if not s.is_thin_lv)
+ return super(LVMLogicalVolumeBase, self).isleaf and not non_thin_snapshots
- def _create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- # should we use --zero for safety's sake?
- if not self.cache:
- # just a plain LV
- # TODO: specify sizes together with PVs once LVM and libblockdev support it
- pvs = [spec.pv.path for spec in self._pv_specs]
- pvs = pvs or None
+ def add_internal_lv(self, int_lv):
+ if int_lv not in self._internal_lvs:
+ self._internal_lvs.append(int_lv)
- blockdev.lvm.lvcreate(self.vg.name, self._name, self.size,
- type=self.seg_type, pv_list=pvs)
+ def remove_internal_lv(self, int_lv):
+ if int_lv in self._internal_lvs:
+ self._internal_lvs.remove(int_lv)
else:
- mode = blockdev.lvm.cache_get_mode_from_str(self.cache.mode)
- fast_pvs = [pv.path for pv in self.cache.fast_pvs]
+ msg = "the specified internal LV '%s' doesn't belong to this LV ('%s')" % (int_lv.lv_name,
+ self.name)
+ raise ValueError(msg)
- if self._pv_specs:
- # (slow) PVs specified for this LV
- slow_pvs = [spec.pv.path for spec in self._pv_specs]
+ def populate_ksdata(self, data):
+ super(LVMLogicalVolumeBase, self).populate_ksdata(data)
+ data.vgname = self.vg.name
+ data.name = self.lvname
+ data.preexist = self.exists
+ data.resize = (self.exists and self.target_size and
+ self.target_size != self.current_size)
+ if not self.exists:
+ data.grow = self.req_grow
+ if self.req_grow:
+ data.size = self.req_size.convert_to(MiB)
+ data.max_size_mb = self.req_max_size.convert_to(MiB)
else:
- # get the list of all fast PV devices used in the VG so that we can
- # consider the rest to be slow PVs and generate a list of them
- all_fast_pvs_names = set()
- for lv in self.vg.lvs:
- if lv.cached and lv.cache.fast_pvs:
- all_fast_pvs_names |= set(pv.name for pv in lv.cache.fast_pvs)
- slow_pvs = [pv.path for pv in self.vg.pvs if pv.name not in all_fast_pvs_names]
+ data.size = self.size.convert_to(MiB)
- slow_pvs = util.dedup_list(slow_pvs)
+ data.percent = self.req_percent
+ elif data.resize:
+ data.size = self.target_size.convert_to(MiB)
- # VG name, LV name, data size, cache size, metadata size, mode, flags, slow PVs, fast PVs
- # XXX: we need to pass slow_pvs+fast_pvs (without duplicates) as slow PVs because parts of the
- # fast PVs may be required for allocation of the LV (it may span over the slow PVs and parts of
- # fast PVs)
- blockdev.lvm.cache_create_cached_lv(self.vg.name, self._name, self.size, self.cache.size, self.cache.md_size,
- mode, 0, util.dedup_list(slow_pvs + fast_pvs), fast_pvs)
-
- def _post_create(self):
- super()._post_create()
- # update the free space info of the PVs this LV could have taken space
- # from (either specified or potentially all PVs from the VG)
- if self._pv_specs:
- used_pvs = [spec.pv for spec in self._pv_specs]
- else:
- used_pvs = self.vg.pvs
- for pv in used_pvs:
- # None means "not set" and triggers a dynamic fetch of the actual
- # value when queried
- pv.format.free = None
-
- def _pre_destroy(self):
- StorageDevice._pre_destroy(self)
- # set up the vg's pvs so lvm can remove the lv
- self.vg.setup_parents(orig=True)
-
- def _destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- blockdev.lvm.lvremove(self.vg.name, self._name)
-
- def resize(self):
- log_method_call(self, self.name, status=self.status)
-
- # Setup VG parents (in case they are dmraid partitions for example)
- self.vg.setup_parents(orig=True)
-
- if self.original_format.exists:
- self.original_format.teardown()
- if self.format.exists:
- self.format.teardown()
-
- udev.settle()
- blockdev.lvm.lvresize(self.vg.name, self._name, self.size)
-
- @property
- def isleaf(self):
- # Thin snapshots do not need to be removed prior to removal of the
- # origin, but the old snapshots do.
- non_thin_snapshots = any(s for s in self.snapshots
- if not isinstance(s, LVMThinSnapShotDevice))
- return (super(LVMLogicalVolumeDevice, self).isleaf and
- not non_thin_snapshots)
+ @property
+ def cached(self):
+ return bool(self.cache)
@property
- def direct(self):
- """ Is this device directly accessible? """
- # an LV can contain a direct filesystem if it is a leaf device or if
- # its only dependent devices are snapshots
- return super(LVMLogicalVolumeDevice, self).isleaf
-
- def dracut_setup_args(self):
- # Note no map_name usage here, this is a lvm cmdline name, which
- # is different (ofcourse)
- return set(["rd.lvm.lv=%s/%s" % (self.vg.name, self._name)])
-
- def check_size(self):
- """ Check to make sure the size of the device is allowed by the
- format used.
-
- Returns:
- 0 - ok
- 1 - Too large
- -1 - Too small
- """
- if self.format.max_size and self.size > self.format.max_size:
- return 1
- elif (self.format.min_size and
- (not self.req_grow and
- self.size < self.format.min_size) or
- (self.req_grow and self.req_max_size and
- self.req_max_size < self.format.min_size)):
- return -1
- return 0
-
- def remove_hook(self, modparent=True):
- if modparent:
- self.vg._remove_log_vol(self)
-
- super(LVMLogicalVolumeDevice, self).remove_hook(modparent=modparent)
-
- def add_hook(self, new=True):
- super(LVMLogicalVolumeDevice, self).add_hook(new=new)
- if new:
- return
+ def cache(self):
+ if self.exists and not self._cache:
+ # check if we have a cache pool internal LV
+ for lv in self._internal_lvs:
+ if lv.int_lv_type == LVMInternalLVtype.cache_pool:
+ self._cache = LVMCache(self, size=lv.size, exists=True)
- if self not in self.vg.lvs:
- self.vg._add_log_vol(self)
+ return self._cache
- def populate_ksdata(self, data):
- super(LVMLogicalVolumeDevice, self).populate_ksdata(data)
- data.vgname = self.vg.name
- data.name = self.lvname
- data.preexist = self.exists
- data.resize = (self.exists and self.target_size and
- self.target_size != self.current_size)
- if not self.exists:
- data.grow = self.req_grow
- if self.req_grow:
- data.size = self.req_size.convert_to(MiB)
- data.max_size_mb = self.req_max_size.convert_to(MiB)
- else:
- data.size = self.size.convert_to(MiB)
- data.percent = self.req_percent
- elif data.resize:
- data.size = self.target_size.convert_to(MiB)
+class LVMInternalLVtype(Enum):
+ data = 1
+ meta = 2
+ log = 3
+ image = 4
+ origin = 5
+ cache_pool = 6
+ unknown = 99
@classmethod
- def is_name_valid(cls, name):
- # Check that the LV name is valid
-
- # Start with the checks shared with volume groups
- if not LVMVolumeGroupDevice.is_name_valid(name):
- return False
-
- # And now the ridiculous ones
- # These strings are taken from apply_lvname_restrictions in lib/misc/lvm-string.c
- reserved_prefixes = set(['pvmove', 'snapshot'])
- reserved_substrings = set(['_cdata', '_cmeta', '_mimage', '_mlog', '_pmspare', '_rimage',
- '_rmeta', '_tdata', '_tmeta', '_vorigin'])
+ def get_type(cls, lv_attr, _lv_name):
+ attr_letters = {cls.data: ("T", "C"),
+ cls.meta: ("e",),
+ cls.log: ("l", "L"),
+ cls.image: ("i",),
+ cls.origin: ("o",),
+ cls.cache_pool: ("C",)}
+
+ if lv_attr[0] == "C":
+ # cache pools and internal data LV of cache pools need a more complicated check
+ if lv_attr[6] == "C":
+ # target type == cache -> cache pool
+ return cls.cache_pool
+ else:
+ return cls.data
- for prefix in reserved_prefixes:
- if name.startswith(prefix):
- return False
+ for lv_type, letters in attr_letters.items():
+ if lv_attr[0] in letters:
+ return lv_type
- for substring in reserved_substrings:
- if substring in name:
- return False
+ return cls.unknown
- return True
- def add_internal_lv(self, int_lv):
- if int_lv not in self._internal_lvs:
- self._internal_lvs.append(int_lv)
+class LVMInternalLogicalVolumeMixin(object):
+ def __init__(self, vg, parent_lv, lv_type):
+ self._vg = vg
+ self._parent_lv = parent_lv
+ self._lv_type = lv_type
+ if parent_lv:
+ self._parent_lv.add_internal_lv(self)
- def remove_internal_lv(self, int_lv):
- if int_lv in self._internal_lvs:
- self._internal_lvs.remove(int_lv)
- else:
- msg = "the specified internal LV '%s' doesn't belong to this LV ('%s')" % (int_lv.lv_name,
- self.name)
- raise ValueError(msg)
+ def _init_check(self):
+ # an internal LV should have no parents
+ if self._parent_lv and self._parents:
+ raise ValueError("an internal LV should have no parents")
@property
- def cached(self):
- return bool(self.cache)
+ def is_internal_lv(self):
+ return bool(self._parent_lv or self._lv_type)
@property
- def cache(self):
- if self.exists and not self._cache:
- # check if we have a cache pool internal LV
- pool = None
- for lv in self._internal_lvs:
- if isinstance(lv, LVMCachePoolLogicalVolumeDevice):
- pool = lv
-
- if pool is not None:
- self._cache = LVMCache(self, size=pool.size, exists=True)
+ def vg(self):
+ if self._parent_lv:
+ return self._parent_lv.vg
+ else:
+ return self._vg
- return self._cache
+ @property
+ def parent_lv(self):
+ return self._parent_lv
- def attach_cache(self, cache_pool_lv):
- blockdev.lvm.cache_attach(self.vg.name, self.lvname, cache_pool_lv.lvname)
- self._cache = LVMCache(self, size=cache_pool_lv.size, exists=True)
+ @parent_lv.setter
+ def parent_lv(self, parent_lv):
+ if self._parent_lv:
+ self._parent_lv.remove_internal_lv(self)
+ self._parent_lv = parent_lv
+ if self._parent_lv:
+ self._parent_lv.add_internal_lv(self)
+ @property
+ @util.requires_property("is_internal_lv")
+ def int_lv_type(self):
+ return self._lv_type
-@add_metaclass(SynchronizedABCMeta)
-class LVMInternalLogicalVolumeDevice(LVMLogicalVolumeDevice):
+ @property
+ @util.requires_property("is_internal_lv")
+ def takes_extra_space(self):
+ return self._lv_type in (LVMInternalLVtype.meta,
+ LVMInternalLVtype.log,
+ LVMInternalLVtype.cache_pool)
- """Abstract base class for internal LVs
+ @property
+ @util.requires_property("is_internal_lv")
+ def name_suffix(self):
+ suffixes = {LVMInternalLVtype.data: r"_[tc]data",
+ LVMInternalLVtype.meta: r"_[trc]meta(_[0-9]+)?",
+ LVMInternalLVtype.log: r"_mlog",
+ LVMInternalLVtype.image: r"_[rm]image(_[0-9]+)?",
+ LVMInternalLVtype.origin: r"_c?orig",
+ LVMInternalLVtype.cache_pool: r"_cache(_?pool)?"}
+ return suffixes.get(self._lv_type)
- A common class for all classes representing internal Logical Volumes like
- data and metadata parts of pools, RAID images, etc.
+ @property
+ def readonly(self):
+ return True
- Internal LVs are only referenced by their "parent" LVs (normal LVs they
- back) as all queries and manipulations with them should be done via their
- parent LVs.
+ @readonly.setter
+ def readonly(self, _value):
+ raise ValueError("Cannot make an internal LV read-write")
- """
+ @property
+ def type(self):
+ return "lvminternallv"
- _type = "lvminternallv"
+ @property
+ def resizable(self):
+ if DMDevice.resizable.__get__(self) and self._lv_type is LVMInternalLVtype.meta:
+ if self._parent_lv:
+ return self._parent_lv.is_thin_pool
+ else:
+ # hard to say at this point, just use the name
+ return not re.search(r'_[rc]meta', self.lvname)
+ else:
+ return False
- # generally changes should be done on the parent LV (exceptions should
- # override these)
- _resizable = False
- _readonly = True
+ def resize(self):
+ if self._lv_type is not LVMInternalLVtype.meta:
+ errors.DeviceError("The internal LV %s cannot be resized" % self.lvname)
+ if ((self._parent_lv and not self._parent_lv.is_thin_pool) or
+ re.search(r'_[rc]meta', self.lvname)):
+ raise errors.DeviceError("RAID and cache pool metadata LVs cannot be resized directly")
- attr_letters = abc.abstractproperty(doc="letters representing the type of the internal LV in the attrs")
- name_suffix = abc.abstractproperty(doc="pattern matching typical/default suffices for internal LVs of this type")
- takes_extra_space = abc.abstractproperty(doc="whether LVs of this type take space in a VG or are part of their parent LVs")
+ # skip the generic LVMInternalLogicalVolumeDevice class and call the
+ # resize() method of the LVMLogicalVolumeDevice
+ raise NotTypeSpecific()
- @classmethod
- def is_name_valid(cls, name):
+ def is_name_valid(self, _name):
# override checks for normal LVs, internal LVs typically have names that
# are forbidden for normal LVs
return True
- def __init__(self, name, vg, parent_lv=None, size=None, uuid=None,
- exists=False, seg_type=None, sysfs_path=''):
- """
- :param vg: the VG this internal LV belongs to
- :type vg: :class:`LVMVolumeGroupDevice`
- :param parent_lv: the parent LV of this internal LV
- :type parent_lv: :class:`LVMLogicalVolumeDevice`
-
- See :method:`LVMLogicalVolumeDevice.__init__` for details about the
- rest of the parameters.
- """
-
- # VG name has to be set for parent class' constructors
- self._vg = vg
-
- # so does the parent LV
- self._parent_lv = parent_lv
-
- # construct the internal LV just like a normal one just with no parents
- # and some parameters set to values reflecting the fact that this is an
- # internal LV
- super(LVMInternalLogicalVolumeDevice, self).__init__(name, parents=None,
- size=size, uuid=uuid, seg_type=seg_type, fmt=None, exists=exists,
- sysfs_path=sysfs_path, grow=None, maxsize=None, percent=None)
-
- if parent_lv:
- self._parent_lv.add_internal_lv(self)
-
def _check_parents(self):
# an internal LV should have no parents
if self._parents:
@@ -1258,27 +1068,6 @@ def _add_to_parents(self):
# meaning of 'parents')
pass
- @property
- def vg(self):
- return self._vg
-
- @vg.setter
- def vg(self, vg):
- # pylint: disable=arguments-differ
- self._vg = vg
-
- @property
- def parent_lv(self):
- return self._parent_lv
-
- @parent_lv.setter
- def parent_lv(self, parent_lv):
- if self._parent_lv:
- self._parent_lv.remove_internal_lv(self)
- self._parent_lv = parent_lv
- if self._parent_lv:
- self._parent_lv.add_internal_lv(self)
-
# internal LVs follow different rules limitting size
def _set_size(self, size):
if not isinstance(size, Size):
@@ -1286,12 +1075,12 @@ def _set_size(self, size):
if not self.takes_extra_space:
if size <= self.parent_lv.size:
- self._size = size
+ self._size = size # pylint: disable=attribute-defined-outside-init
else:
raise ValueError("Internal LV cannot be bigger than its parent LV")
else:
# same rules apply as for any other LV
- super(LVMInternalLogicalVolumeDevice, self)._set_size(size)
+ raise NotTypeSpecific()
@property
def max_size(self):
@@ -1301,33 +1090,17 @@ def max_size(self):
else:
return self.size + self.vg.free_space
- def __repr__(self):
- s = "%s:\n" % self.__class__.__name__
- s += (" name = %s, status = %s exists = %s\n" % (self.lvname, self.status, self.exists))
- s += (" uuid = %s, size = %s\n" % (self.uuid, self.size))
- s += (" parent LV = %r\n" % self.parent_lv)
- s += (" VG device = %(vgdev)r\n"
- " segment type = %(type)s percent = %(percent)s\n"
- " VG space used = %(vgspace)s" %
- {"vgdev": self.vg, "percent": self.req_percent,
- "type": self.seg_type,
- "vgspace": self.vg_space_used})
- return s
-
# generally changes should be done on the parent LV (exceptions should
# override these)
- def setup(self, orig=False):
+ def setup(self, _orig=False):
raise errors.DeviceError("An internal LV cannot be set up separately")
- def teardown(self, recursive=None):
+ def teardown(self, _recursive=None):
raise errors.DeviceError("An internal LV cannot be torn down separately")
def destroy(self):
raise errors.DeviceError("An internal LV cannot be destroyed separately")
- def resize(self):
- raise errors.DeviceError("An internal LV cannot be resized")
-
@property
def growable(self):
return False
@@ -1344,7 +1117,7 @@ def add_hook(self, new=True):
# skip LVMLogicalVolumeDevice in the class hierarchy -- we don't want to
# add an internal LV to the VG (it's only referenced by the parent LV)
# pylint: disable=bad-super-call
- super(LVMLogicalVolumeDevice, self).add_hook(new=new)
+ DMDevice.add_hook(self, new=new)
self._parent_lv.add_internal_lv(self)
def remove_hook(self, modparent=True):
@@ -1354,7 +1127,7 @@ def remove_hook(self, modparent=True):
# skip LVMLogicalVolumeDevice in the class hierarchy -- we cannot remove
# an internal LV from the VG (it's only referenced by the parent LV)
# pylint: disable=bad-super-call
- super(LVMLogicalVolumeDevice, self).remove_hook(modparent=modparent)
+ DMDevice.remove_hook(self, modparent=modparent)
@property
def direct(self):
@@ -1362,151 +1135,89 @@ def direct(self):
return False
-class LVMDataLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
+class LVMSnapshotMixin(object):
+ def __init__(self, origin=None, vorigin=False):
+ self.origin = origin
+ """ the snapshot's source volume """
- """Internal data LV (used by thin/cache pools)"""
+ self.vorigin = vorigin
+ """ a boolean flag indicating a vorigin snapshot """
- attr_letters = ["T", "C"]
- name_suffix = r"_[tc]data"
- takes_extra_space = False
-_INTERNAL_LV_CLASSES.append(LVMDataLogicalVolumeDevice)
+ def _init_check(self):
+ if not self.is_snapshot_lv:
+ # not a snapshot, nothing more to be done
+ return
+ if self.origin and not isinstance(self.origin, LVMLogicalVolumeDevice):
+ raise ValueError("lvm snapshot origin must be a logical volume")
+ if self.origin and not self.origin.exists:
+ raise ValueError("lvm snapshot origin volume must already exist")
+ if self.vorigin and not self.exists:
+ raise ValueError("only existing vorigin snapshots are supported")
-class LVMMetadataLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
+ if isinstance(self.origin, LVMLogicalVolumeDevice) and \
+ isinstance(self.parents[0], LVMVolumeGroupDevice) and \
+ self.origin.vg != self.parents[0]:
+ raise ValueError("lvm snapshot and origin must be in the same vg")
- """Internal metadata LV (used by thin/cache pools, RAIDs, etc.)"""
+ if self.is_thin_lv:
+ if self.origin and self.size and not self.exists:
+ raise ValueError("thin snapshot size is determined automatically")
- # thin pool metadata LVs can be resized directly
- _resizable = True
+ @property
+ def is_snapshot_lv(self):
+ return bool(self.origin or self.vorigin)
- attr_letters = ["e"]
- # RAIDs can have multiple (numbered) metadata LVs
- name_suffix = r"_[trc]meta(_[0-9]+)?"
- takes_extra_space = True
+ @property
+ def type(self):
+ if self.is_thin_lv:
+ return "lvmthinsnapshot"
+ else:
+ return "lvmsnapshot"
- # (only) thin pool metadata LVs can be resized directly
@property
def resizable(self):
- if self._parent_lv:
- return isinstance(self._parent_lv, LVMThinPoolDevice)
+ if self.is_thin_lv:
+ return False
else:
- # hard to say at this point, just use the name
- return not re.search(r'_[rc]meta', self.lvname)
+ raise NotTypeSpecific()
- # (only) thin pool metadata LVs can be resized directly
- def resize(self):
- if ((self._parent_lv and not isinstance(self._parent_lv, LVMThinPoolDevice)) or
- re.search(r'_[rc]meta', self.lvname)):
- raise errors.DeviceError("RAID and cache pool metadata LVs cannot be resized directly")
+ @property
+ def format_immutable(self):
+ return False
- # skip the generic LVMInternalLogicalVolumeDevice class and call the
- # resize() method of the LVMLogicalVolumeDevice
- # pylint: disable=bad-super-call
- super(LVMInternalLogicalVolumeDevice, self).resize()
+ # decorator
+ def old_snapshot_specific(meth): # pylint: disable=no-self-argument
+ """Decorator for methods that are specific only to old snapshots"""
+ @wraps(meth)
+ def decorated(self, *args, **kwargs):
+ if self.is_thin_lv:
+ raise NotTypeSpecific()
+ else:
+ return getattr(self, meth)(*args, **kwargs)
+ return decorated
-_INTERNAL_LV_CLASSES.append(LVMMetadataLogicalVolumeDevice)
+ @util.requires_property("is_snapshot_lv")
+ def merge(self):
+ """ Merge the snapshot back into its origin volume. """
+ log_method_call(self, self.name, status=self.status)
+ self.vg.setup()
+ try:
+ self.origin.teardown()
+ except errors.FSError:
+ # the merge will begin based on conditions described in the --merge
+ # section of lvconvert(8)
+ pass
+ try:
+ self.teardown()
+ except errors.FSError:
+ pass
-class LVMLogLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
-
- """Internal log LV (used by mirrored LVs)"""
-
- attr_letters = ["l", "L"]
- name_suffix = "_mlog"
- takes_extra_space = True
-_INTERNAL_LV_CLASSES.append(LVMLogLogicalVolumeDevice)
-
-
-class LVMImageLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
-
- """Internal image LV (used by mirror/RAID LVs)"""
-
- attr_letters = ["i"]
- # RAIDs have multiple (numbered) image LVs
- name_suffix = r"_[rm]image(_[0-9]+)?"
- takes_extra_space = False
-_INTERNAL_LV_CLASSES.append(LVMImageLogicalVolumeDevice)
-
-
-class LVMOriginLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
-
- """Internal origin LV (e.g. the raw/uncached part of a cached LV)"""
-
- attr_letters = ["o"]
- name_suffix = r"_c?orig"
- takes_extra_space = False
-_INTERNAL_LV_CLASSES.append(LVMOriginLogicalVolumeDevice)
-
-
-class LVMCachePoolLogicalVolumeDevice(LVMInternalLogicalVolumeDevice):
-
- """Internal cache pool logical volume"""
-
- attr_letters = ["C"]
- name_suffix = r"_cache(_?pool)?"
- takes_extra_space = True
-_INTERNAL_LV_CLASSES.append(LVMCachePoolLogicalVolumeDevice)
-
-
-@add_metaclass(SynchronizedABCMeta)
-class LVMSnapShotBase(object):
-
- """ Abstract base class for lvm snapshots
-
- This class is intended to be used with multiple inheritance in addition
- to some subclass of :class:`~.StorageDevice`.
-
- Snapshots do not have their origin/source volume as parent. They are
- like other LVs except that they have an origin attribute and are in that
- instance's snapshots list.
-
- Normal/old snapshots must be removed with their origin, while thin
- snapshots can remain after their origin is removed.
-
- It is also impossible to set the format for a non-existent snapshot
- explicitly as it always has the same format as its origin.
- """
- _type = "lvmsnapshotbase"
-
- def __init__(self, origin=None, vorigin=False, exists=False):
- """
- :keyword :class:`~.LVMLogicalVolumeDevice` origin: source volume
- :keyword bool vorigin: is this a vorigin snapshot?
- :keyword bool exists: is this an existing snapshot?
-
- vorigin is a special type of device that makes use of snapshots to
- create a sparse device. These snapshots have no origin lv, instead
- using space in the vg directly. Only preexisting vorigin snapshots
- are supported here.
- """
- self._origin_specified_check(origin, vorigin, exists)
- self._origin_type_check(origin)
- self._origin_existence_check(origin)
- self._vorigin_existence_check(vorigin, exists)
-
- self.origin = origin
- """ the snapshot's source volume """
-
- self.vorigin = vorigin
- """ a boolean flag indicating a vorigin snapshot """
-
- def _origin_specified_check(self, origin, vorigin, exists):
- # pylint: disable=unused-argument
- if not origin and not vorigin:
- raise ValueError("lvm snapshot devices require an origin lv")
-
- def _origin_type_check(self, origin):
- if origin and not isinstance(origin, LVMLogicalVolumeDevice):
- raise ValueError("lvm snapshot origin must be a logical volume")
-
- def _origin_existence_check(self, origin):
- if origin and not origin.exists:
- raise ValueError("lvm snapshot origin volume must already exist")
-
- def _vorigin_existence_check(self, vorigin, exists):
- if vorigin and not exists:
- raise ValueError("only existing vorigin snapshots are supported")
+ udev.settle()
+ blockdev.lvm.lvsnapshotmerge(self.vg.name, self.lvname)
+ @util.requires_property("is_snapshot_lv")
def _update_format_from_origin(self):
""" Update the snapshot's format to reflect the origin's.
@@ -1516,112 +1227,67 @@ def _update_format_from_origin(self):
origin.
"""
+ if not self.origin and self.vorigin:
+ # nothing to do for vorigin with no origin set
+ return
+
fmt = copy.deepcopy(self.origin.format)
fmt.exists = False
if hasattr(fmt, "mountpoint"):
fmt.mountpoint = ""
fmt._chrooted_mountpoint = None
- fmt.device = self.path # pylint: disable=no-member
+ fmt.device = self.path
- super(LVMSnapShotBase, self)._set_format(fmt) # pylint: disable=no-member
+ self._format = fmt # pylint: disable=attribute-defined-outside-init
- def _set_format(self, fmt):
+ def _set_format(self, _fmt):
# If a snapshot exists it can have a format that is distinct from its
# origin's. If it does not exist its format must be a copy of its
# origin's.
- if self.exists: # pylint: disable=no-member
- super(LVMSnapShotBase, self)._set_format(fmt) # pylint: disable=no-member
+ if self.exists:
+ raise NotTypeSpecific()
else:
- log.info("copying %s origin's format", self.name) # pylint: disable=no-member
+ log.info("copying %s origin's format", self.name)
self._update_format_from_origin()
- @abc.abstractmethod
- def _create(self):
- """ Create the device. """
- raise NotImplementedError()
-
- def merge(self):
- """ Merge the snapshot back into its origin volume. """
- log_method_call(self, self.name, status=self.status) # pylint: disable=no-member
- self.vg.setup() # pylint: disable=no-member
- try:
- self.origin.teardown()
- except errors.FSError:
- # the merge will begin based on conditions described in the --merge
- # section of lvconvert(8)
- pass
-
- try:
- self.teardown() # pylint: disable=no-member
- except errors.FSError:
- pass
-
- udev.settle()
- blockdev.lvm.lvsnapshotmerge(self.vg.name, self.lvname) # pylint: disable=no-member
-
-
-class LVMSnapShotDevice(LVMSnapShotBase, LVMLogicalVolumeDevice):
-
- """ An LVM snapshot """
- _type = "lvmsnapshot"
- _format_immutable = True
-
- 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, origin=None, vorigin=False):
- """ Create an LVMSnapShotDevice instance.
-
- This class is for the old-style (not thin) lvm snapshots. The origin
- volume cannot be removed without also removing all snapshots (not so
- for thin snapshots). Also, the snapshot is automatically activated
- or deactivated with its origin.
-
- :param str name: the device name (generally a device node basename)
- :keyword bool exists: does this device exist?
- :keyword :class:`~.size.Size` size: the device's size
- :keyword :class:`~.ParentList` parents: list of parent devices
- :keyword fmt: this device's formatting
- :type fmt: :class:`~.formats.DeviceFormat`
- :keyword str sysfs_path: sysfs device path
- :keyword str uuid: the device UUID
- :keyword str seg_type: segment type
-
- :keyword :class:`~.StorageDevice` origin: the origin/source volume
- :keyword bool vorigin: is this a vorigin snapshot?
-
- For non-existent devices only:
-
- :keyword bool grow: whether to grow this LV
- :keyword :class:`~.size.Size` maxsize: maximum size for growable LV
- :keyword int percent: percent of VG space to take
- """
- # pylint: disable=unused-argument
-
- if isinstance(origin, LVMLogicalVolumeDevice) and \
- isinstance(parents[0], LVMVolumeGroupDevice) and \
- origin.vg != parents[0]:
- raise ValueError("lvm snapshot and origin must be in the same vg")
-
- LVMSnapShotBase.__init__(self, origin=origin, vorigin=vorigin,
- exists=exists)
-
- LVMLogicalVolumeDevice.__init__(self, name, parents=parents, size=size,
- uuid=uuid, fmt=None, exists=exists,
- seg_type=seg_type,
- sysfs_path=sysfs_path, grow=grow,
- maxsize=maxsize, percent=percent)
-
+ @old_snapshot_specific
def setup(self, orig=False):
+ # the old snapshot cannot be setup and torn down
pass
+ def _setup(self, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status,
+ controllable=self.controllable)
+ blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=True)
+
+ @old_snapshot_specific
def teardown(self, recursive=False):
+ # the old snapshot cannot be setup and torn down
pass
def _create(self):
""" Create the device. """
- log_method_call(self, self.name, status=self.status)
- blockdev.lvm.lvsnapshotcreate(self.vg.name, self.origin.lvname, self._name, self.size)
+ if not self.is_thin_lv:
+ log_method_call(self, self.name, status=self.status)
+ blockdev.lvm.lvsnapshotcreate(self.vg.name, self.origin.lvname, self._name, self.size)
+ else:
+ pool_name = None
+ if not self.origin.is_thin_lv:
+ # if the origin is not a thin volume we need to tell lvm which pool
+ # to use
+ pool_name = self.pool.lvname
+
+ blockdev.lvm.thsnapshotcreate(self.vg.name, self.origin.lvname, self._name,
+ pool_name=pool_name)
+
+ def _post_create(self):
+ DMDevice._post_create(self)
+ if self.is_thin_lv:
+ # A snapshot's format exists as soon as the snapshot has been created.
+ self.format.exists = True
+ @old_snapshot_specific
def _destroy(self):
""" Destroy the device. """
log_method_call(self, self.name, status=self.status)
@@ -1630,14 +1296,19 @@ def _destroy(self):
# that it is okay to remove the active snapshot
blockdev.lvm.lvremove(self.vg.name, self._name, force=True)
- def _get_parted_device_path(self):
- return "%s-cow" % self.path
-
def depends_on(self, dep):
- # pylint: disable=bad-super-call
- return (self.origin == dep or
- super(LVMSnapShotBase, self).depends_on(dep))
+ if self.is_thin_lv:
+ if self.origin == dep and not self.exists:
+ return True
+ else:
+ raise NotTypeSpecific()
+ else:
+ if self.origin == dep:
+ return True
+ else:
+ raise NotTypeSpecific()
+ @old_snapshot_specific
def read_current_size(self):
log_method_call(self, exists=self.exists, path=self.path,
sysfs_path=self.sysfs_path)
@@ -1652,74 +1323,43 @@ def read_current_size(self):
return size
-class LVMThinPoolDevice(LVMLogicalVolumeDevice):
-
- """ An LVM Thin Pool """
- _type = "lvmthinpool"
- _resizable = False
-
- def __init__(self, name, parents=None, size=None, uuid=None,
- fmt=None, exists=False, sysfs_path='',
- grow=None, maxsize=None, percent=None,
- metadatasize=None, chunksize=None, seg_type=None, profile=None):
- """
- :param name: the device name (generally a device node's basename)
- :type name: str
- :keyword exists: does this device exist?
- :type exists: bool
- :keyword size: the device's size
- :type size: :class:`~.size.Size`
- :keyword parents: a list of parent devices
- :type parents: list of :class:`StorageDevice`
- :keyword fmt: this device's formatting
- :type fmt: :class:`~.formats.DeviceFormat` or a subclass of it
- :keyword sysfs_path: sysfs device path
- :type sysfs_path: str
- :keyword uuid: the device UUID
- :type uuid: str
- :keyword seg_type: segment type (only "linear" supported for non-existing ThinPool LVs)
- :type seg_type: str
-
- For non-existent pools only:
-
- :keyword grow: whether to grow this LV
- :type grow: bool
- :keyword maxsize: maximum size for growable LV
- :type maxsize: :class:`~.size.Size`
- :keyword percent: percent of VG space to take
- :type percent: int
- :keyword metadatasize: the size of the metadata LV
- :type metadatasize: :class:`~.size.Size`
- :keyword chunksize: chunk size for the pool
- :type chunksize: :class:`~.size.Size`
- :keyword profile: (allocation) profile for the pool or None (unspecified)
- :type profile: :class:`~.devicelibs.lvm.ThPoolProfile` or NoneType
+class LVMThinPoolMixin(object):
+ def __init__(self, metadata_size=None, chunk_size=None, profile=None):
+ self._metadata_size = metadata_size or Size(0)
+ self._chunk_size = chunk_size or Size(0)
+ self._profile = profile
+ self._lvs = []
- """
- if metadatasize is not None and \
- not blockdev.lvm.is_valid_thpool_md_size(metadatasize):
+ def _init_check(self):
+ if self._metadata_size and not blockdev.lvm.is_valid_thpool_md_size(self._metadata_size):
raise ValueError("invalid metadatasize value")
- if chunksize is not None and \
- not blockdev.lvm.is_valid_thpool_chunk_size(chunksize):
+ if self._chunk_size and not blockdev.lvm.is_valid_thpool_chunk_size(self._chunk_size):
raise ValueError("invalid chunksize value")
- if not exists and seg_type and seg_type != "linear":
- raise ValueError("creation of non-linear thin pool LVs is not supported (yet)")
+ @property
+ def is_thin_pool(self):
+ return self.seg_type == "thin-pool"
- super(LVMThinPoolDevice, self).__init__(name, parents=parents,
- size=size, uuid=uuid,
- fmt=fmt, exists=exists,
- sysfs_path=sysfs_path, grow=grow,
- maxsize=maxsize,
- percent=percent,
- seg_type=seg_type)
+ @property
+ def type(self):
+ return "lvmthinpool"
- self._metadata_size = metadatasize or Size(0)
- self.chunk_size = chunksize or Size(0)
- self.profile = profile
- self._lvs = []
+ @property
+ def resizable(self):
+ return False
+
+ @property
+ @util.requires_property("is_thin_pool")
+ def used_space(self):
+ return sum((l.pool_space_used for l in self.lvs), Size(0))
+ @property
+ @util.requires_property("is_thin_pool")
+ def free_space(self):
+ return self.size - self.used_space
+
+ @util.requires_property("is_thin_pool")
def _add_log_vol(self, lv):
""" Add an LV to this pool. """
if lv in self._lvs:
@@ -1730,6 +1370,7 @@ def _add_log_vol(self, lv):
log.debug("Adding %s/%s to %s", lv.name, lv.size, self.name)
self._lvs.append(lv)
+ @util.requires_property("is_thin_pool")
def _remove_log_vol(self, lv):
""" Remove an LV from this pool. """
if lv not in self._lvs:
@@ -1739,24 +1380,18 @@ def _remove_log_vol(self, lv):
self.vg._remove_log_vol(lv)
@property
+ @util.requires_property("is_thin_pool")
def lvs(self):
""" A list of this pool's LVs """
return self._lvs[:] # we don't want folks changing our list
@property
def vg_space_used(self):
- space = super(LVMThinPoolDevice, self).vg_space_used
+ # TODO: what about cached thin pools?
+ space = self.data_vg_space_used + self.metadata_vg_space_used
space += Size(blockdev.lvm.get_thpool_padding(space, self.vg.pe_size))
return space
- @property
- def used_space(self):
- return sum((l.pool_space_used for l in self.lvs), Size(0))
-
- @property
- def free_space(self):
- return self.size - self.used_space
-
def _create(self):
""" Create the device. """
log_method_call(self, self.name, status=self.status)
@@ -1780,7 +1415,7 @@ def direct(self):
return False
def populate_ksdata(self, data):
- super(LVMThinPoolDevice, self).populate_ksdata(data)
+ LVMLogicalVolumeBase.populate_ksdata(self, data)
data.mountpoint = "none"
data.thin_pool = True
data.metadata_size = self.metadata_size.convert_to(MiB)
@@ -1789,21 +1424,46 @@ def populate_ksdata(self, data):
data.profile = self.profile.name
-class LVMThinLogicalVolumeDevice(LVMLogicalVolumeDevice):
+class LVMThinLogicalVolumeMixin(object):
+ def __init__(self):
+ pass
- """ An LVM Thin Logical Volume """
- _type = "lvmthinlv"
- _container_class = LVMThinPoolDevice
+ def _init_check(self):
+ pass
+
+ def _check_parents(self):
+ """Check that this device has parents as expected"""
+ if isinstance(self.parents, (list, ParentList)):
+ if len(self.parents) != 1:
+ raise ValueError("constructor requires a single thin-pool LV")
+
+ container = self.parents[0]
+ else:
+ container = self.parents
+
+ if not container or not isinstance(container, LVMLogicalVolumeDevice) or not container.is_thin_pool:
+ raise ValueError("constructor requires a thin-pool LV")
@property
- def pool(self):
- return self.parents[0]
+ def is_thin_lv(self):
+ return self.seg_type == "thin"
@property
def vg(self):
+ # parents[0] is the pool, not the VG so set the VG here
return self.pool.vg
@property
+ def type(self):
+ return "lvmthinlv"
+
+ @property
+ @util.requires_property("is_thin_lv")
+ def pool(self):
+ return self.parents[0]
+
+ @property
+ @util.requires_property("is_thin_lv")
def pool_space_used(self):
""" The total space used within the thin pool by this volume.
@@ -1823,15 +1483,15 @@ def _set_size(self, size):
size = self.vg.align(size)
size = self.vg.align(util.numeric_type(size))
- super(LVMThinLogicalVolumeDevice, self)._set_size(size)
-
- size = property(StorageDevice._get_size, _set_size)
+ # just make sure the size is set (no VG size/free space check needed for
+ # a thin LV)
+ DMDevice._set_size(self, size)
def _pre_create(self):
# skip LVMLogicalVolumeDevice's _pre_create() method as it checks for a
# free space in a VG which doesn't make sense for a ThinLV and causes a
# bug by limitting the ThinLV's size to VG free space which is nonsense
- super(LVMLogicalVolumeDevice, self)._pre_create() # pylint: disable=bad-super-call
+ super(LVMLogicalVolumeBase, self)._pre_create() # pylint: disable=bad-super-call
def _create(self):
""" Create the device. """
@@ -1844,11 +1504,11 @@ def remove_hook(self, modparent=True):
self.pool._remove_log_vol(self)
# pylint: disable=bad-super-call
- super(LVMLogicalVolumeDevice, self).remove_hook(modparent=modparent)
+ super(LVMLogicalVolumeBase, self).remove_hook(modparent=modparent)
def add_hook(self, new=True):
# pylint: disable=bad-super-call
- super(LVMLogicalVolumeDevice, self).add_hook(new=new)
+ super(LVMLogicalVolumeBase, self).add_hook(new=new)
if new:
return
@@ -1856,82 +1516,454 @@ def add_hook(self, new=True):
self.pool._add_log_vol(self)
def populate_ksdata(self, data):
- super(LVMThinLogicalVolumeDevice, self).populate_ksdata(data)
+ LVMLogicalVolumeBase.populate_ksdata(self, data)
data.thin_volume = True
data.pool_name = self.pool.lvname
-class LVMThinSnapShotDevice(LVMSnapShotBase, LVMThinLogicalVolumeDevice):
+class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin, LVMSnapshotMixin,
+ LVMThinPoolMixin, LVMThinLogicalVolumeMixin):
+ """ An LVM Logical Volume """
- """ An LVM Thin Snapshot """
- _type = "lvmthinsnapshot"
- _resizable = False
- _format_immutable = True
+ # generally resizable, see :property:`resizable` for details
+ _resizable = True
- def __init__(self, name, parents=None, sysfs_path='', origin=None,
- fmt=None, uuid=None, size=None, exists=False, seg_type=None):
+ 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,
+ parent_lv=None, int_type=None, origin=None, vorigin=False,
+ metadata_size=None, chunk_size=None, profile=None):
"""
- :param str name: the name of the device
- :param :class:`~.ParentList` parents: parent devices
- :param str sysfs_path: path to this device's /sys directory
- :keyword origin: the origin(source) volume for the snapshot
- :type origin: :class:`~.LVMLogicalVolumeDevice` or None
- :keyword str seg_type: segment type
- :keyword :class:`~.formats.DeviceFormat` fmt: this device's format
- :keyword str uuid: the device UUID
- :keyword :class:`~.size.Size` size: the device's size
- :keyword bool exists: is this an existing device?
-
- LVM thin snapshots can remain after their origin volume is removed,
- unlike the older-style snapshots.
+ :param name: the device name (generally a device node's basename)
+ :type name: str
+ :keyword exists: does this device exist?
+ :type exists: bool
+ :keyword size: the device's size
+ :type size: :class:`~.size.Size`
+ :keyword parents: a list of parent devices
+ :type parents: list of :class:`StorageDevice`
+ :keyword fmt: this device's formatting
+ :type fmt: :class:`~.formats.DeviceFormat` or a subclass of it
+ :keyword sysfs_path: sysfs device path
+ :type sysfs_path: str
+ :keyword uuid: the device UUID
+ :type uuid: str
+ :keyword seg_type: segment type (eg: "linear", "raid1", "thin-pool", "thin",...)
+ :type seg_type: str
+
+ For non-existent LVs only:
+
+ :keyword grow: whether to grow this LV
+ :type grow: bool
+ :keyword maxsize: maximum size for growable LV
+ :type maxsize: :class:`~.size.Size`
+ :keyword percent: percent of VG space to take
+ :type percent: int
+ :keyword cache_request: parameters of requested cache (if any)
+ :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)
+
+ For internal LVs only:
+
+ :keyword parent_lv: parent LV of this internal LV
+ :type parent_lv: :class:`LVMLogicalVolumeDevice`
+ :keyword int_type: type of this internal LV
+ :type int_type: :class:`LVMInternalLVtype`
+
+ For snapshots only:
+
+ :keyword origin: origin of this snapshot
+ :type origin: :class:`~.StorageDevice`
+ :keyword bool vorigin: is this a vorigin snapshot?
+
+ For thin pools (seg_type="thin-pool") only:
+
+ :keyword metadata_size: the size of the metadata LV
+ :type metadata_size: :class:`~.size.Size`
+ :keyword chunk_size: chunk size for the pool
+ :type chunk_size: :class:`~.size.Size`
+ :keyword profile: (allocation) profile for the pool or None (unspecified)
+ :type profile: :class:`~.devicelibs.lvm.ThPoolProfile` or NoneType
"""
- # pylint: disable=unused-argument
- if isinstance(origin, LVMLogicalVolumeDevice) and \
- isinstance(parents[0], LVMThinPoolDevice) and \
- origin.vg != parents[0].vg:
- raise ValueError("lvm snapshot and origin must be in the same vg")
+ if isinstance(parents, (list, ParentList)):
+ vg = parents[0]
+ else:
+ vg = parents
+ if parent_lv or int_type:
+ # internal LVs are not in the DeviceTree and doesn't have the
+ # parent<->child relation like other devices
+ parents = None
+
+ self.seg_type = seg_type
+
+ LVMInternalLogicalVolumeMixin.__init__(self, vg, parent_lv, int_type)
+ LVMSnapshotMixin.__init__(self, origin, vorigin)
+ LVMThinPoolMixin.__init__(self, metadata_size, chunk_size, profile)
+ LVMThinLogicalVolumeMixin.__init__(self)
+ LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
+ fmt, exists, sysfs_path, grow, maxsize,
+ percent, cache_request, pvs)
+
+ LVMInternalLogicalVolumeMixin._init_check(self)
+ LVMSnapshotMixin._init_check(self)
+ LVMThinPoolMixin._init_check(self)
+ LVMThinLogicalVolumeMixin._init_check(self)
- if size and not exists:
- raise ValueError("thin snapshot size is determined automatically")
+ # check that we got parents as expected and add this device to them now
+ # that it is fully-initialized
+ self._check_parents()
+ self._add_to_parents()
- LVMSnapShotBase.__init__(self, origin=origin, exists=exists)
- LVMThinLogicalVolumeDevice.__init__(self, name, parents=parents,
- sysfs_path=sysfs_path, fmt=fmt,
- seg_type=seg_type,
- uuid=uuid, size=size, exists=exists)
+ def _get_type_classes(self):
+ """Method to get type classes for this particular instance"""
+ ret = []
+ if self.is_internal_lv:
+ ret.append(LVMInternalLogicalVolumeMixin)
+ if self.is_snapshot_lv:
+ ret.append(LVMSnapshotMixin)
+ if self.is_thin_pool:
+ ret.append(LVMThinPoolMixin)
+ if self.is_thin_lv:
+ ret.append(LVMThinLogicalVolumeMixin)
+ return ret
+
+ def _try_specific_call(self, name, *args, **kwargs):
+ """Try to call a type-specific method for this particular instance"""
+ clss = self._get_type_classes()
+ for cls in clss:
+ if hasattr(cls, name):
+ try:
+ # found, check if it is a method or property
+ if isinstance(getattr(cls, name), property):
+ if len(args) == 0 and len(kwargs.keys()) == 0:
+ # no *args nor **kwargs -> call the getter
+ ret = getattr(cls, name).__get__(self)
+ else:
+ # some args -> call the setter
+ ret = getattr(cls, name).__set__(self, *args, **kwargs)
+ else:
+ # or just call the method with all the args
+ ret = getattr(cls, name)(self, *args, **kwargs)
+ except NotTypeSpecific:
+ # no type-specific steps required for this class, just
+ # continue with another one
+ continue
+ else:
+ return (True, ret)
+ # not found, let the caller know
+ return (False, None)
+
+ # decorator
+ def type_specific(meth): # pylint: disable=no-self-argument
+ @wraps(meth)
+ def decorated(self, *args, **kwargs):
+ """Decorator that makes sure the type-specific code is executed if available"""
+ found, ret = self._try_specific_call(meth.__name__, *args, **kwargs) # pylint: disable=no-member
+ if found:
+ # nothing more to do here
+ return ret
+ else:
+ return meth(self, *args, **kwargs) # pylint: disable=not-callable
+
+ return decorated
+
+ def __repr__(self):
+ s = DMDevice.__repr__(self)
+ s += (" VG device = %(vgdev)r\n"
+ " segment type = %(type)s percent = %(percent)s\n"
+ " VG space used = %(vgspace)s" %
+ {"vgdev": self.vg, "percent": self.req_percent,
+ "type": self.seg_type,
+ "vgspace": self.vg_space_used})
+ if self.parent_lv:
+ s += " parent LV = %r\n" % self.parent_lv
+
+ return s
+
+ @type_specific
+ def _check_parents(self):
+ """Check that this device has parents as expected"""
+ if isinstance(self.parents, (list, ParentList)):
+ if len(self.parents) != 1:
+ raise ValueError("constructor requires a single LVMVolumeGroupDevice")
+
+ container = self.parents[0]
+ else:
+ container = self.parents
+
+ if not isinstance(container, LVMVolumeGroupDevice):
+ raise ValueError("constructor requires a LVMVolumeGroupDevice")
+
+ @type_specific
+ def _add_to_parents(self):
+ """Add this device to its parents"""
+ # a normal LV has only exactly one parent -- the VG it belongs to
+ self._parents[0]._add_log_vol(self)
+
+ @property
+ @type_specific
+ def vg(self):
+ """This Logical Volume's Volume Group."""
+ return super().vg
+
+ @type_specific
+ def _set_size(self, size):
+ if not isinstance(size, Size):
+ raise ValueError("new size must be of type Size")
+
+ size = self.vg.align(size)
+ log.debug("trying to set lv %s size to %s", self.name, size)
+ # Don't refuse to set size if we think there's not enough space in the
+ # VG for an existing LV, since it's existence proves there is enough
+ # space for it. A similar reasoning applies to shrinking the LV.
+ if not self.exists and size > self.size and size > self.vg.free_space + self.vg_space_used:
+ log.error("failed to set size: %s short", size - (self.vg.free_space + self.vg_space_used))
+ raise ValueError("not enough free space in volume group")
+
+ LVMLogicalVolumeBase._set_size(self, size)
+
+ size = property(StorageDevice._get_size, _set_size)
+
+ @property
+ @type_specific
+ def max_size(self):
+ """ The maximum size this lv can be. """
+ max_lv = self.size + self.vg.free_space
+ max_format = self.format.max_size
+ return min(max_lv, max_format) if max_format else max_lv
+
+ @property
+ @type_specific
+ def vg_space_used(self):
+ """ Space occupied by this LV, not including snapshots. """
+ return super().vg_space_used
+
+ @type_specific
+ def _set_format(self, fmt):
+ LVMLogicalVolumeBase._set_format(self, fmt)
+ for snapshot in (s for s in self.snapshots if not s.exists):
+ snapshot._update_format_from_origin()
+
+ def setup_parents(self, orig=False):
+ # parent is a vg, which has no formatting (or device for that matter)
+ Device.setup_parents(self, orig=orig)
+
+ @type_specific
+ def setup(self, orig=False):
+ return DMDevice.setup(self, orig)
+
+ @type_specific
+ def teardown(self, recursive=None):
+ return DMDevice.teardown(self, recursive)
+
+ @type_specific
+ def destroy(self):
+ return DMDevice.destroy(self)
+
+ @property
+ @type_specific
+ def growable(self):
+ return super().growable
+
+ @property
+ @type_specific
+ def readonly(self):
+ return super().readonly
- def _origin_specified_check(self, origin, vorigin, exists):
- if not exists and not origin:
- raise ValueError("non-existent lvm thin snapshots require an origin")
+ @property
+ @type_specific
+ def display_lv_name(self):
+ return self.lvname
+ @type_specific
def _setup(self, orig=False):
""" Open, or set up, a device. """
log_method_call(self, self.name, orig=orig, status=self.status,
controllable=self.controllable)
- blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=True)
+ blockdev.lvm.lvactivate(self.vg.name, self._name)
+ @type_specific
+ def _pre_create(self):
+ LVMLogicalVolumeBase._pre_create(self)
+
+ try:
+ vg_info = blockdev.lvm.vginfo(self.vg.name)
+ except blockdev.LVMError as lvmerr:
+ log.error("Failed to get free space for the %s VG: %s", self.vg.name, lvmerr)
+ # nothing more can be done, we don't know the VG's free space
+ return
+
+ extent_size = Size(vg_info.extent_size)
+ extents_free = vg_info.free_count
+ can_use = extent_size * extents_free
+
+ if self.size > can_use:
+ msg = ("%s LV's size (%s) exceeds the VG's usable free space (%s),"
+ "shrinking the LV") % (self.name, self.size, can_use)
+ log.warning(msg)
+ self.size = can_use
+
+ @type_specific
def _create(self):
""" Create the device. """
log_method_call(self, self.name, status=self.status)
- pool_name = None
- if not isinstance(self.origin, LVMThinLogicalVolumeDevice):
- # if the origin is not a thin volume we need to tell lvm which pool
- # to use
- pool_name = self.pool.lvname
+ # should we use --zero for safety's sake?
+ if not self.cache:
+ # just a plain LV
+ # TODO: specify sizes together with PVs once LVM and libblockdev support it
+ pvs = [spec.pv.path for spec in self._pv_specs]
+ pvs = pvs or None
- blockdev.lvm.thsnapshotcreate(self.vg.name, self.origin.lvname, self._name,
- pool_name=pool_name)
+ blockdev.lvm.lvcreate(self.vg.name, self._name, self.size,
+ type=self.seg_type, pv_list=pvs)
+ else:
+ mode = blockdev.lvm.cache_get_mode_from_str(self.cache.mode)
+ fast_pvs = [pv.path for pv in self.cache.fast_pvs]
+ if self._pv_specs:
+ # (slow) PVs specified for this LV
+ slow_pvs = [spec.pv.path for spec in self._pv_specs]
+ else:
+ # get the list of all fast PV devices used in the VG so that we can
+ # consider the rest to be slow PVs and generate a list of them
+ all_fast_pvs_names = set()
+ for lv in self.vg.lvs:
+ if lv.cached and lv.cache.fast_pvs:
+ all_fast_pvs_names |= set(pv.name for pv in lv.cache.fast_pvs)
+ slow_pvs = [pv.path for pv in self.vg.pvs if pv.name not in all_fast_pvs_names]
+
+ slow_pvs = util.dedup_list(slow_pvs)
+
+ # VG name, LV name, data size, cache size, metadata size, mode, flags, slow PVs, fast PVs
+ # XXX: we need to pass slow_pvs+fast_pvs (without duplicates) as slow PVs because parts of the
+ # fast PVs may be required for allocation of the LV (it may span over the slow PVs and parts of
+ # fast PVs)
+ blockdev.lvm.cache_create_cached_lv(self.vg.name, self._name, self.size, self.cache.size, self.cache.md_size,
+ mode, 0, util.dedup_list(slow_pvs + fast_pvs), fast_pvs)
+
+ @type_specific
def _post_create(self):
- super(LVMThinSnapShotDevice, self)._post_create()
- # A snapshot's format exists as soon as the snapshot has been created.
- self.format.exists = True
+ LVMLogicalVolumeBase._post_create(self)
+ # update the free space info of the PVs this LV could have taken space
+ # from (either specified or potentially all PVs from the VG)
+ if self._pv_specs:
+ used_pvs = [spec.pv for spec in self._pv_specs]
+ else:
+ used_pvs = self.vg.pvs
+ for pv in used_pvs:
+ # None means "not set" and triggers a dynamic fetch of the actual
+ # value when queried
+ pv.format.free = None
+
+ @type_specific
+ def _destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ blockdev.lvm.lvremove(self.vg.name, self._name)
+
+ @type_specific
+ def resize(self):
+ log_method_call(self, self.name, status=self.status)
+
+ # Setup VG parents (in case they are dmraid partitions for example)
+ self.vg.setup_parents(orig=True)
+ if self.original_format.exists:
+ self.original_format.teardown()
+ if self.format.exists:
+ self.format.teardown()
+
+ udev.settle()
+ blockdev.lvm.lvresize(self.vg.name, self._name, self.size)
+
+ @property
+ @type_specific
+ def direct(self):
+ """ Is this device directly accessible? """
+ # an LV can contain a direct filesystem if it is a leaf device or if
+ # its only dependent devices are snapshots
+ return super(LVMLogicalVolumeBase, self).isleaf
+
+ @property
+ @type_specific
+ def type(self):
+ return self._type
+
+ @property
+ @type_specific
+ def resizable(self):
+ return super().resizable
+
+ @property
+ @type_specific
+ def format_immutable(self):
+ return super().format_immutable
+
+ @type_specific
def depends_on(self, dep):
- # once a thin snapshot exists it no longer depends on its origin
- return ((self.origin == dep and not self.exists) or
- super(LVMThinSnapShotDevice, self).depends_on(dep))
+ return DMDevice.depends_on(self, dep)
+
+ @type_specific
+ def read_current_size(self):
+ return DMDevice.read_current_size(self)
+
+ @type_specific
+ def dracut_setup_args(self):
+ # Note no map_name usage here, this is a lvm cmdline name, which
+ # is different (ofcourse)
+ return set(["rd.lvm.lv=%s/%s" % (self.vg.name, self._name)])
+
+ @type_specific
+ def remove_hook(self, modparent=True):
+ if modparent:
+ self.vg._remove_log_vol(self)
+
+ LVMLogicalVolumeBase.remove_hook(self, modparent=modparent)
+
+ @type_specific
+ def add_hook(self, new=True):
+ LVMLogicalVolumeBase.add_hook(self, new=new)
+ if new:
+ return
+
+ if self not in self.vg.lvs:
+ self.vg._add_log_vol(self)
+
+ @type_specific
+ def populate_ksdata(self, data):
+ LVMLogicalVolumeBase.populate_ksdata(self, data)
+
+ @type_specific
+ def is_name_valid(self, name):
+ # Check that the LV name is valid
+
+ # Start with the checks shared with volume groups
+ if not LVMVolumeGroupDevice.is_name_valid(self, name):
+ return False
+
+ # And now the ridiculous ones
+ # These strings are taken from apply_lvname_restrictions in lib/misc/lvm-string.c
+ reserved_prefixes = set(['pvmove', 'snapshot'])
+ reserved_substrings = set(['_cdata', '_cmeta', '_mimage', '_mlog', '_pmspare', '_rimage',
+ '_rmeta', '_tdata', '_tmeta', '_vorigin'])
+
+ for prefix in reserved_prefixes:
+ if name.startswith(prefix):
+ return False
+
+ for substring in reserved_substrings:
+ if substring in name:
+ return False
+
+ return True
+
+ def attach_cache(self, cache_pool_lv):
+ if self.is_thin_lv or self.is_snapshot_lv or self.is_internal_lv:
+ raise errors.DeviceError("Cannot attach a cache pool to the '%s' LV" % self.name)
+ blockdev.lvm.cache_attach(self.vg.name, self.lvname, cache_pool_lv.lvname)
+ self._cache = LVMCache(self, size=cache_pool_lv.size, exists=True)
class LVMCache(Cache):
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/0277ab116aec099584f0dd91a34c...
8 years, 4 months
[master 1/7] Add a decorator for guarding methods and properties with
a
property
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
Sometimes we need to prevent a method from being called or property from being
evaluated if some conditition is not satisfied. The most common case is to check
value of some property of the given object. For example, it only makes sense to
call the lv.merge() method if 'lv' is a snapshot. Things like this can be
facilitated by a decorator that makes sure 'lv' is snapshot for example by
checking its 'is_snapshot' property. The code then looks like this:
@requires_property("is_snapshot")
def merge(self):
# the actual merge goes here
On an LV that is not a snapshot a ValueError is raised.
Of course, the decorator can be made even more generic if it turns out in the
future we need it to be.
---
blivet/util.py | 20 +++++++++++++++++++
tests/util_test.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+)
diff --git a/blivet/util.py b/blivet/util.py
index 8a96350..c914d4b 100644
--- a/blivet/util.py
+++ b/blivet/util.py
@@ -1082,3 +1082,23 @@ def __new__(cls, *args, **kwargs):
return nt.__new__(cls, *args_list)
return TheDefaultNamedTuple
+
+
+def requires_property(prop_name, val=True):
+ """
+ Function returning a decorator that can be used to guard methods and
+ properties with evaluation of the given property.
+
+ :param str prop_name: property to evaluate
+ :param val: guard value of the :param:`prop_name`
+ :type val: :class:`Object` (anything)
+ """
+ def guard(fn):
+ @wraps(fn)
+ def func(self, *args, **kwargs):
+ if getattr(self, prop_name) == val:
+ return fn(self, *args, **kwargs)
+ else:
+ raise ValueError("%s can only be accessed if %s evaluates to %s" % (fn.__name__, prop_name, val))
+ return func
+ return guard
diff --git a/tests/util_test.py b/tests/util_test.py
index d6792fe..295c519 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -56,3 +56,60 @@ def test_default_namedtuple(self):
dnt = TestTuple()
self.assertEqual(dnt, (None, None, 5, None))
+
+
+class Test(object):
+ def __init__(self, s):
+ self._s = s
+
+ @property
+ def s(self):
+ return self._s
+
+ @property
+ def ok(self):
+ return True
+
+ @property
+ def nok(self):
+ return False
+
+ @util.requires_property("s", "hi")
+ def say_hi(self):
+ return self.s + ", guys!"
+
+ @property
+ @util.requires_property("s", "hi")
+ def hi(self):
+ return self.s
+
+ @property
+ @util.requires_property("ok")
+ def good_news(self):
+ return "Everything okay!"
+
+ @property
+ @util.requires_property("nok")
+ def bad_news(self):
+ return "Nothing okay!"
+
+
+class TestRequiresProperty(unittest.TestCase):
+ def test_requires_property(self):
+ t = Test("hi")
+
+ self.assertEqual(t.s, "hi")
+ self.assertEqual(t.say_hi(), "hi, guys!")
+ self.assertEqual(t.hi, "hi")
+
+ t = Test("hello")
+ self.assertEqual(t.s, "hello")
+ with self.assertRaises(ValueError):
+ t.say_hi()
+ with self.assertRaises(ValueError):
+ t.hi()
+
+ self.assertEqual(t.good_news, "Everything okay!")
+
+ with self.assertRaises(ValueError):
+ t.bad_news
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/d4a8df0aa405a2bc75b3bd12ac11...
8 years, 4 months
Updated: [rhinstaller/blivet/pulls/310 master] LVM classes
unification
by dashea
These commits change how we represent LVs. Instead of having special classes for various types of LVs, we now have a single class with the type of LV being more a dynamic property than a once-given thing. This will allow us to implement various LVM-related features in the future that have so far been limited by our multi-class representation.
I'm not adding any new functionality or features so no tests are added. Just the existing ones are modified to continue working.
I'll add a section to the *release_notes.rst* file tomorrow as an extra commit to this PR.
--
To view this pull request on github, visit https://github.com/rhinstaller/blivet/pull/310
8 years, 4 months
[master 11/11] Document the LV class unification changes in the
release
notes
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
To give some basic hints to users as well as future developers and maintainers
of Blivet.
---
release_notes.rst | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/release_notes.rst b/release_notes.rst
index 57dcf04..29b2e71 100644
--- a/release_notes.rst
+++ b/release_notes.rst
@@ -1,6 +1,49 @@
2.0
====
+A single class for all LVs
+---------------------------
+
+In order to be better prepared for supporting things like *lvconvert*, Blivet
+now represents all LVs with a single class (keeping the name
+``LVMLogicalVolumeDevice``).
+
+
+Using the class
+++++++++++++++++
+
+In order to create LVs of various types, different values of the ``seg_type``
+parameter need to be passed. For example, to create a thin pool, ``thin-pool``
+segment type needs to be specified (optionally together with the
+thin-pool-specific parameters like ``metadata_size``) . The same applies to thin
+LVs and the ``thin`` segment type. To create a snapshot LV, one needs to specify
+the ``origin`` LV or set the ``vorigin`` flag to ``True``. Internal LVs require
+``parent_lv`` and ``int_type`` specifying the type of the internal LV.
+
+To determine the type of some LV, the newly added ``is_thin_lv``,
+``is_thin_pool``, ``is_snapshot_lv`` and ``is_internal_lv`` properties can be
+used.
+
+
+Implementation details
++++++++++++++++++++++++
+
+To avoid having a single gigantic class with hundreds of lines of code, the
+``LVMLogicalVolumeDevice`` class makes use of iheritance and "merges" together
+the ``LVMLogicalVolumeBase`` class and mixins for specific types of LVs (thin
+pool, thin LV,...) adding the type-specific methods and properties as well as
+type-specific implementations of various methods. The ``@type_specific``
+decorator makes sure that the right implementation of a method is called
+whenever there is a type-specific one (for example thin pools are created in a
+different way than good old linear LVs).
+
+The code that is common to all LVs lives in the ``LVMLogicalVolumeBase`` class
+together with properties that are required by this code. Type-specific code
+lives in the particular mixin classes and the generic/fallback implementations
+live in the (ultimate) ``LVMLogicalVolumeDevice`` class' methods decorated with
+the ``@type_specific`` decorator.
+
+
LVM RAID
---------
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/9d011401cfe46fdaaed0b3e2f251...
8 years, 4 months