Skip to content

Commit

Permalink
Add support for creating and managing LVM cache volumes
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechtrefny committed Nov 3, 2021
1 parent d522b48 commit b3a1d10
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 2 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,25 @@ will take on the device. Virtual size of VDO volume is set by `size` parameter.
e.g.: "30g", "50GiB".
Default value is equal to the size of the volume.

#### `cached`
This specifies whether the volume should be cached or not.
This is currently supported only for LVM volumes where dm-cache
is used.
__NOTE__: Only creating new cached volumes and removing cache from
an existing volume is currently supported. Enabling cache
for an existing volume is not yet supported.

#### `cache_size`
Size of the cache. `cache_size` format is intended to be human-readable,
e.g.: "30g", "50GiB".

#### `cache_mode`
Mode for the cache. Supported values include `writethrough` (default) and `writeback`.

#### `cache_devices`
List of devices (physical volumes) that will be used for the cache. These should be
physical volumes on fast SSD or NVMe drives.

#### `storage_safe_mode`
When true (the default), an error will occur instead of automatically removing existing devices and/or formatting.

Expand Down
5 changes: 5 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ storage_volume_defaults:
compression: null
deduplication: null
vdo_pool_size: null

cached: false
cache_size: 0
cache_mode: null
cache_devices: []
50 changes: 49 additions & 1 deletion library/blivet.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ def _destroy(self):
def _manage_encryption(self):
self._device = self._manage_one_encryption(self._device)

def _manage_cache(self):
pass

def _resize(self):
""" Schedule actions as needed to ensure the device has the desired size. """
size = self._get_size()
Expand Down Expand Up @@ -550,6 +553,7 @@ def manage(self):
raise BlivetAnsibleError("failed to look up or create device '%s'" % self._volume['name'])

self._manage_encryption()
self._manage_cache()

# schedule reformat if appropriate
if self._device.raw_device.exists:
Expand Down Expand Up @@ -665,6 +669,35 @@ def _get_size(self):
size = self._blivet_pool._device.align(size, roundup=True)
return size

def _detach_cache(self):
""" Detach cache from the volume and remove the unused cache pool """
try:
cpool_name = self._device.cache.detach()
except Exception as e:
raise BlivetAnsibleError("failed to detach cache from volume '%s': %s" % (self._device.name, str(e)))

# full reset is needed for the cache pool to be added to the devicetree so we can remove it
self._blivet.reset()

cpool_name = cpool_name.rstrip("_cpool")
cpool_device = self._blivet.devicetree.resolve_device("%s-%s" % (self._device.vg.name, cpool_name))

self._blivet.destroy_device(cpool_device)

def _attach_cache(self):
""" Create a new cache pool and attach it to the volume """
raise BlivetAnsibleError("adding cache to an existing volume is currently not supported")

def _manage_cache(self):
if not self._device:
# cache for newly created LVs is managed in _create
return

if self._volume['cached'] and not self._device.cached:
self._attach_cache()
if not self._volume['cached'] and self._device.cached:
self._detach_cache()

def _create(self):
if self._device:
return
Expand Down Expand Up @@ -721,6 +754,20 @@ def _create(self):
parent.name)
size = parent.free_space

if self._volume['cached']:
fast_pvs = []
for pv in self._volume['cache_devices']:
pv_device = self._blivet.devicetree.resolve_device(pv)
if pv_device is None:
raise BlivetAnsibleError("cache device '%s' not found" % pv)
fast_pvs.append(pv_device)

cache_request = devices.lvm.LVMCacheRequest(size=Size(self._volume['cache_size']),
mode=self._volume['cache_mode'],
pvs=fast_pvs)
else:
cache_request = None

try:
if create_vdo:
device = self._blivet.new_lv(name=self._volume['name'], vdo_lv=create_vdo,
Expand All @@ -731,7 +778,8 @@ def _create(self):
# vdo_lv optional parameter
device = self._blivet.new_lv(name=self._volume['name'],
parents=[parent],
size=size, fmt=fmt)
size=size, fmt=fmt,
cache_request=cache_request)
except Exception as e:
raise BlivetAnsibleError("failed to set up volume '%s': %s" % (self._volume['name'], str(e)))

Expand Down
41 changes: 41 additions & 0 deletions tests/test-verify-volume-cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---

- name: check cache options
block:

- name: Get information about the LV
command: "lvs --noheadings --nameprefixes --units=b --nosuffix --unquoted -o name,attr,cache_total_blocks,chunk_size,segtype
{{ storage_test_pool.name }}/{{ storage_test_volume.name }}"
register: lvs
changed_when: false

- set_fact:
storage_test_lv_segtype: "{{ lvs.stdout|regex_search('LVM2_SEGTYPE=(\\S+)', '\\1') }}"

- name: check segment type
assert:
that: "{{ storage_test_lv_segtype[0] == 'cache' if storage_test_volume.cached|bool else storage_test_lv_segtype[0] == 'linear' }}"
msg: "Unexpected segtype {{ storage_test_lv_segtype }} for {{ storage_test_volume.name }}"

- set_fact:
storage_test_lv_cache_size: "{{ lvs.stdout|regex_search('LVM2_CACHE_TOTAL_BLOCKS=([0-9]*) LVM2_CHUNK_SIZE=([0-9]+)', '\\1', '\\2') }}"
when: storage_test_volume.cached|bool

- name: parse the requested cache size
bsize:
size: "{{ storage_test_volume.cache_size }}"
register: storage_test_requested_cache_size
when: storage_test_volume.cached|bool

- set_fact:
storage_test_expected_cache_size: "{{ storage_test_requested_cache_size.bytes }}"
when: storage_test_volume.cached|bool

- name: Check cache size
assert:
that: "{{ (storage_test_expected_cache_size|int -
storage_test_lv_cache_size[0]|int * storage_test_lv_cache_size[1]|int)|abs / storage_test_expected_cache_size|int < 0.01 }}"
msg: Unexpected cache size, expected {{ storage_test_expected_cache_size }}, got {{ storage_test_lv_cache_size[0]|int * storage_test_lv_cache_size[1]|int }}
when: storage_test_volume.cached|bool

when: storage_test_volume.type == 'lvm' and _storage_test_volume_present
2 changes: 1 addition & 1 deletion tests/test-verify-volume.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
- set_fact:
_storage_volume_tests: ['mount', 'fstab', 'fs', 'device', 'encryption', 'md', 'size'] # fs: type, label device: name, type, disks
_storage_volume_tests: ['mount', 'fstab', 'fs', 'device', 'encryption', 'md', 'size', 'cache'] # fs: type, label device: name, type, disks
# future:
# device:
# compression
Expand Down
71 changes: 71 additions & 0 deletions tests/tests_create_lvm_cache_then_remove.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
- hosts: all
become: true
vars:
storage_safe_mode: false
mount_location1: '/opt/test1'
mount_location2: '/opt/test2'
volume_group_size: '10g'
volume_size: '5g'
cache_size: '4g'

tasks:
- include_role:
name: linux-system-roles.storage

- name: Mark tasks to be skipped
set_fact:
storage_skip_checks:
- blivet_available
- packages_installed
- service_facts

- include_tasks: get_unused_disk.yml
vars:
min_size: "{{ volume_group_size }}"
max_return: 2
disks_needed: 2

- name: Create a cached LVM logical volume under volume group 'foo'
include_role:
name: linux-system-roles.storage
vars:
storage_pools:
- name: foo
disks: "{{ unused_disks }}"
volumes:
- name: test
size: "{{ volume_size }}"
cached: true
cache_size: "{{ cache_size }}"
cache_devices: "{{ [unused_disks[1]] }}"

- include_tasks: verify-role-results.yml

- name: Remove (detach) cache from the 'test' LV created above
include_role:
name: linux-system-roles.storage
vars:
storage_pools:
- name: foo
disks: "{{ unused_disks }}"
volumes:
- name: test
size: "{{ volume_size }}"
cached: false

- include_tasks: verify-role-results.yml

- name: Clean up
include_role:
name: linux-system-roles.storage
vars:
storage_pools:
- name: foo
disks: "{{ unused_disks }}"
state: "absent"
volumes:
- name: test
size: "{{ volume_size }}"

- include_tasks: verify-role-results.yml

0 comments on commit b3a1d10

Please sign in to comment.