Support LVM thin provisioning
This change extends the block device lvs attributes to allow creating a volume which represents a thin pool, and to create volumes which are allocated from this pool. Change-Id: Ic58f55c36236cc8c6279fbcb708e27dc2982f2d5
This commit is contained in:
parent
f61548d863
commit
833c5b8ceb
@ -23,6 +23,7 @@ from diskimage_builder.block_device.utils import parse_abs_size_spec
|
|||||||
from diskimage_builder.block_device.utils import remove_device
|
from diskimage_builder.block_device.utils import remove_device
|
||||||
|
|
||||||
PHYSICAL_EXTENT_BYTES = parse_abs_size_spec('4MiB')
|
PHYSICAL_EXTENT_BYTES = parse_abs_size_spec('4MiB')
|
||||||
|
LVS_TYPES = ['thin', 'thin-pool']
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -173,7 +174,8 @@ class VgsNode(NodeBase):
|
|||||||
|
|
||||||
|
|
||||||
class LvsNode(NodeBase):
|
class LvsNode(NodeBase):
|
||||||
def __init__(self, name, state, base, options, size, extents):
|
def __init__(self, name, state, base, options, size, extents, segtype,
|
||||||
|
thin_pool):
|
||||||
"""Logical Volume
|
"""Logical Volume
|
||||||
|
|
||||||
This is a placeholder node for a logical volume
|
This is a placeholder node for a logical volume
|
||||||
@ -186,21 +188,31 @@ class LvsNode(NodeBase):
|
|||||||
:param size: size of the LV, using the supported unit types
|
:param size: size of the LV, using the supported unit types
|
||||||
(MB, MiB, etc)
|
(MB, MiB, etc)
|
||||||
:param extents: size of the LV in extents
|
:param extents: size of the LV in extents
|
||||||
|
:param segtype: value passed to segment type, supports thin and
|
||||||
|
thin-pool
|
||||||
|
:param thin_pool: name of the thin pool to create this volume from
|
||||||
"""
|
"""
|
||||||
super(LvsNode, self).__init__(name, state)
|
super(LvsNode, self).__init__(name, state)
|
||||||
self.base = base
|
self.base = base
|
||||||
self.options = options
|
self.options = options
|
||||||
self.size = size
|
self.size = size
|
||||||
self.extents = extents
|
self.extents = extents
|
||||||
|
self.type = segtype
|
||||||
|
self.thin_pool = thin_pool
|
||||||
|
|
||||||
def _create(self):
|
def _create(self):
|
||||||
cmd = ["lvcreate", ]
|
cmd = ["lvcreate", ]
|
||||||
cmd.extend(['--name', self.name])
|
cmd.extend(['--name', self.name])
|
||||||
|
if self.type:
|
||||||
|
cmd.extend(['--type', self.type])
|
||||||
|
if self.thin_pool:
|
||||||
|
cmd.extend(['--thin-pool', self.thin_pool])
|
||||||
if self.size:
|
if self.size:
|
||||||
size = parse_abs_size_spec(self.size)
|
size = parse_abs_size_spec(self.size)
|
||||||
# ensuire size aligns with physical extents
|
# ensuire size aligns with physical extents
|
||||||
size = size - size % PHYSICAL_EXTENT_BYTES
|
size = size - size % PHYSICAL_EXTENT_BYTES
|
||||||
cmd.extend(['-L', '%dB' % size])
|
size_arg = '-V' if self.type == 'thin' else '-L'
|
||||||
|
cmd.extend([size_arg, '%dB' % size])
|
||||||
elif self.extents:
|
elif self.extents:
|
||||||
cmd.extend(['-l', self.extents])
|
cmd.extend(['-l', self.extents])
|
||||||
if self.options:
|
if self.options:
|
||||||
@ -391,10 +403,18 @@ class LVMPlugin(PluginBase):
|
|||||||
self._config_error("base:%s in lvs does not match a valid vg" %
|
self._config_error("base:%s in lvs does not match a valid vg" %
|
||||||
lvs_cfg['base'])
|
lvs_cfg['base'])
|
||||||
|
|
||||||
|
if 'type' in lvs_cfg:
|
||||||
|
if lvs_cfg['type'] not in LVS_TYPES:
|
||||||
|
self._config_error(
|
||||||
|
"Unsupported type:%s, supported types: %s" %
|
||||||
|
(lvs_cfg['type'], ', '.join(LVS_TYPES)))
|
||||||
|
|
||||||
lvs_item = LvsNode(lvs_cfg['name'], state, lvs_cfg['base'],
|
lvs_item = LvsNode(lvs_cfg['name'], state, lvs_cfg['base'],
|
||||||
lvs_cfg.get('options', None),
|
lvs_cfg.get('options', None),
|
||||||
lvs_cfg.get('size', None),
|
lvs_cfg.get('size', None),
|
||||||
lvs_cfg.get('extents', None))
|
lvs_cfg.get('extents', None),
|
||||||
|
lvs_cfg.get('type', None),
|
||||||
|
lvs_cfg.get('thin-pool', None))
|
||||||
self.lvs.append(lvs_item)
|
self.lvs.append(lvs_item)
|
||||||
|
|
||||||
# create the "driver" node
|
# create the "driver" node
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
- local_loop:
|
||||||
|
name: image0
|
||||||
|
|
||||||
|
- partitioning:
|
||||||
|
base: image0
|
||||||
|
name: mbr
|
||||||
|
label: mbr
|
||||||
|
partitions:
|
||||||
|
- name: root
|
||||||
|
base: image0
|
||||||
|
flags: [ boot,primary ]
|
||||||
|
size: 3G
|
||||||
|
|
||||||
|
- lvm:
|
||||||
|
base: mbr
|
||||||
|
pvs:
|
||||||
|
- name: pv
|
||||||
|
options: ["--force"]
|
||||||
|
base: root
|
||||||
|
|
||||||
|
vgs:
|
||||||
|
- name: vg
|
||||||
|
base: ["pv"]
|
||||||
|
options: ["--force"]
|
||||||
|
|
||||||
|
lvs:
|
||||||
|
- name: lv_thinpool
|
||||||
|
type: thin-pool
|
||||||
|
base: vg
|
||||||
|
size: 2800MiB
|
||||||
|
|
||||||
|
- name: lv_root
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 1800MiB
|
||||||
|
|
||||||
|
- name: lv_tmp
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 100MiB
|
||||||
|
|
||||||
|
- name: lv_var
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 500MiB
|
||||||
|
|
||||||
|
- name: lv_log
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 100MiB
|
||||||
|
|
||||||
|
- name: lv_audit
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 100MiB
|
||||||
|
|
||||||
|
- name: lv_home
|
||||||
|
type: thin
|
||||||
|
thin-pool: lv_thinpool
|
||||||
|
base: vg
|
||||||
|
size: 200MiB
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_root
|
||||||
|
base: lv_root
|
||||||
|
label: "img-rootfs"
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /
|
||||||
|
fstab:
|
||||||
|
options: "rw,relatime"
|
||||||
|
fsck-passno: 1
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_var
|
||||||
|
base: lv_var
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /var
|
||||||
|
fstab:
|
||||||
|
options: "rw,relatime"
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_log
|
||||||
|
base: lv_log
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /var/log
|
||||||
|
fstab:
|
||||||
|
options: "rw,relatime"
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_audit
|
||||||
|
base: lv_audit
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /var/log/audit
|
||||||
|
fstab:
|
||||||
|
options: "rw,relatime"
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_tmp
|
||||||
|
base: lv_tmp
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /tmp
|
||||||
|
fstab:
|
||||||
|
options: "rw,nosuid,nodev,noexec,relatime"
|
||||||
|
|
||||||
|
- mkfs:
|
||||||
|
name: fs_home
|
||||||
|
base: lv_home
|
||||||
|
type: "xfs"
|
||||||
|
mount:
|
||||||
|
mount_point: /home
|
||||||
|
fstab:
|
||||||
|
options: "rw,nodev,relatime"
|
@ -582,3 +582,129 @@ class TestLVM(tc.TestGraphGeneration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
manager.assert_has_calls(cmd_sequence)
|
manager.assert_has_calls(cmd_sequence)
|
||||||
|
|
||||||
|
@mock.patch('diskimage_builder.block_device.level1.lvm.exec_sudo')
|
||||||
|
def test_lvm_thin_provision(self, mock_exec_sudo):
|
||||||
|
# Test the command-sequence for a more complicated LVM setup
|
||||||
|
tree = self.load_config_file('lvm_tree_thin_provision.yaml')
|
||||||
|
config = config_tree_to_graph(tree)
|
||||||
|
|
||||||
|
state = BlockDeviceState()
|
||||||
|
|
||||||
|
graph, call_order = create_graph(config, self.fake_default_config,
|
||||||
|
state)
|
||||||
|
|
||||||
|
# Fake state for the two PV's specified by this config
|
||||||
|
state['blockdev'] = {}
|
||||||
|
state['blockdev']['root'] = {}
|
||||||
|
state['blockdev']['root']['device'] = '/dev/fake/root'
|
||||||
|
|
||||||
|
for node in call_order:
|
||||||
|
# XXX: This has not mocked out the "lower" layers of
|
||||||
|
# creating the devices, which we're assuming works OK, nor
|
||||||
|
# the upper layers.
|
||||||
|
if isinstance(node, (LVMNode, PvsNode,
|
||||||
|
VgsNode, LvsNode)):
|
||||||
|
# only the LVMNode actually does anything here...
|
||||||
|
node.create()
|
||||||
|
|
||||||
|
# ensure the sequence of calls correctly setup the devices
|
||||||
|
cmd_sequence = [
|
||||||
|
# create the pv's on the faked out block devices
|
||||||
|
mock.call(['pvcreate', '/dev/fake/root', '--force']),
|
||||||
|
# create a volume called "vg" out of these two pv's
|
||||||
|
mock.call(['vgcreate', 'vg', '/dev/fake/root', '--force']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_thinpool',
|
||||||
|
'--type', 'thin-pool', '-L', '2936012800B', 'vg']),
|
||||||
|
# create a bunch of lv's on vg using the pool
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_root', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '1887436800B',
|
||||||
|
'vg']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_tmp', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_var', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '524288000B', 'vg']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_log', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_audit', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']),
|
||||||
|
mock.call(['lvcreate', '--name', 'lv_home', '--type', 'thin',
|
||||||
|
'--thin-pool', 'lv_thinpool', '-V', '209715200B', 'vg'])]
|
||||||
|
|
||||||
|
self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence))
|
||||||
|
mock_exec_sudo.assert_has_calls(cmd_sequence)
|
||||||
|
|
||||||
|
# Ensure the correct LVM state was preserved
|
||||||
|
blockdev_state = {
|
||||||
|
'root': {'device': '/dev/fake/root'},
|
||||||
|
'lv_thinpool': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '2800MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_thinpool'
|
||||||
|
},
|
||||||
|
'lv_root': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '1800MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_root'
|
||||||
|
},
|
||||||
|
'lv_tmp': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '100MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_tmp'
|
||||||
|
},
|
||||||
|
'lv_var': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '500MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_var'
|
||||||
|
},
|
||||||
|
'lv_log': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '100MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_log'
|
||||||
|
},
|
||||||
|
'lv_audit': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '100MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_audit'
|
||||||
|
},
|
||||||
|
'lv_home': {
|
||||||
|
'vgs': 'vg',
|
||||||
|
'size': '200MiB',
|
||||||
|
'extents': None,
|
||||||
|
'opts': None,
|
||||||
|
'device': '/dev/mapper/vg-lv_home'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertDictEqual(state['blockdev'], blockdev_state)
|
||||||
|
|
||||||
|
@mock.patch('diskimage_builder.block_device.level1.lvm.exec_sudo')
|
||||||
|
def test_validate_lvs_type(self, mock_exec_sudo):
|
||||||
|
# Test the command-sequence for a more complicated LVM setup
|
||||||
|
tree = self.load_config_file('lvm_tree_thin_provision.yaml')
|
||||||
|
print(tree)
|
||||||
|
tree[2]['lvm']['lvs'][0]['type'] = 'thin-pol'
|
||||||
|
|
||||||
|
config = config_tree_to_graph(tree)
|
||||||
|
|
||||||
|
state = BlockDeviceState()
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
BlockDeviceSetupException,
|
||||||
|
"Unsupported type:thin-pol, supported types: thin, thin-pool",
|
||||||
|
create_graph,
|
||||||
|
config,
|
||||||
|
self.fake_default_config,
|
||||||
|
state)
|
||||||
|
@ -435,6 +435,14 @@ options
|
|||||||
(optional) List of options for the logical volume. It can contain any
|
(optional) List of options for the logical volume. It can contain any
|
||||||
option supported by the `lvcreate` command.
|
option supported by the `lvcreate` command.
|
||||||
|
|
||||||
|
type
|
||||||
|
(optional) When set to `thin-pool` a thin pool volume will be created. When
|
||||||
|
set to `thin` the thin volume will be backed by the thin pool named with the
|
||||||
|
`thin-pool` key.
|
||||||
|
|
||||||
|
thin-pool
|
||||||
|
(optional) Name of the thin pool to use for this thin volume.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
5
releasenotes/notes/thin-provision-c57db8003acec386.yaml
Normal file
5
releasenotes/notes/thin-provision-c57db8003acec386.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
LVM thin provisioning is now supported in the block device `lvs` node. Thin
|
||||||
|
pools can be defined and thin volumes associated with those pools.
|
Loading…
Reference in New Issue
Block a user