Skip to content

Commit

Permalink
Merge branch 'main' into main-support_pvresize
Browse files Browse the repository at this point in the history
  • Loading branch information
richm authored Jun 11, 2024
2 parents 957534e + 00dabe1 commit 19c6e82
Show file tree
Hide file tree
Showing 17 changed files with 621 additions and 71 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ keys:

This integer specifies the LUKS version to use.

- `encryption_clevis_pin`

For Stratis pools, the clevis method that should be used to encrypt the created pool.
Accepted values are: `tang` and `tpm2`

- `encryption_tang_url`

When creating a Stratis pool encrypted via NBDE using a tang server,
specifies the URL of the server.

- `encryption_tang_thumbprint`

When creating a Stratis pool encrypted via NBDE using a tang server,
specifies the thumbprint of the server.

### `storage_volumes`

The `storage_volumes` variable is a list of volumes to manage. Each volume has the following
Expand Down
193 changes: 191 additions & 2 deletions library/blivet.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
grow_to_fill:
description: grow_to_fill
type: bool
encryption_clevis_pin:
description: encryption_clevis_pin
type: str
encryption_tang_url:
description: encryption_tang_url
type: str
encryption_tang_thumbprint:
description: encryption_tang_thumbprint
type: str
name:
description: name
type: str
Expand Down Expand Up @@ -1324,11 +1333,89 @@ def _destroy(self):
leaves = [a for a in ancestors if a.isleaf]


class BlivetStratisVolume(BlivetVolume):
blivet_device_class = devices.StratisFilesystemDevice if hasattr(devices, "StratisFilesystemDevice") else None

def _update_from_device(self, param_name):
if param_name == 'fs_type':
# Blivet internally uses "stratis xfs" as filesystem type for stratis to distinguish
# between "xfs" and "xfs on stratis" but we want to use "xfs" here because that's
# the type used for mounting and what the other system tools see
self._volume['fs_type'] = self._device.format.mount_type
return True
else:
return super()._update_from_device(param_name)

def _get_device_id(self):
if not self._blivet_pool._device:
return None
return "%s/%s" % (self._blivet_pool._device.name, self._volume['name'])

def _get_size(self):
size = super(BlivetStratisVolume, self)._get_size()
if isinstance(self._volume['size'], str) and '%' in self._volume['size']:
size = self._blivet_pool._device.align(size, roundup=True)
return size

def _trim_size(self, size, parent_device):
trim_percent = (1.0 - float(parent_device.free_space / size)) * 100
log.debug("size: %s ; %s", size, trim_percent)

if size > parent_device.free_space:
if trim_percent > MAX_TRIM_PERCENT:
raise BlivetAnsibleError("specified size for volume '%s' '%s' exceeds available space in pool '%s' (%s)" % (self._volume['name'],
size,
parent_device.name,
parent_device.free_space))
else:
log.info("adjusting %s size from %s to %s to fit in %s free space",
self._volume['name'],
size,
parent_device.free_space,
parent_device.name)
return parent_device.free_space
return size

def _get_format(self):
if self._volume['fs_type'] and self._volume['fs_type'] not in ('xfs', 'stratis xfs'):
raise BlivetAnsibleError("format cannot be specified for Stratis volumes")

def _reformat(self):
if self._volume['fs_type'] and self._volume['fs_type'] not in ('xfs', 'stratis xfs'):
raise BlivetAnsibleError("format cannot be changed for Stratis volumes")

def _manage_cache(self):
if self._volume['cached']:
raise BlivetAnsibleError("caching is not supported for Stratis volumes")

def _create(self):
if self._device:
return

parent_device = self._blivet_pool._device
if parent_device is None:
raise BlivetAnsibleError("failed to find pool '%s' for volume '%s'" % (self._blivet_pool._device.name,
self._volume['name']))

size = self._trim_size(self._get_size(), parent_device)

try:
device = self._blivet.new_stratis_filesystem(name=self._volume['name'],
parents=[parent_device],
size=size)
except Exception as e:
raise BlivetAnsibleError("failed to set up volume '%s': %s" % (self._volume['name'], str(e)))

self._blivet.create_device(device)
self._device = device


_BLIVET_VOLUME_TYPES = {
"disk": BlivetDiskVolume,
"lvm": BlivetLVMVolume,
"partition": BlivetPartitionVolume,
"raid": BlivetMDRaidVolume
"raid": BlivetMDRaidVolume,
"stratis": BlivetStratisVolume,
}


Expand Down Expand Up @@ -1843,9 +1930,108 @@ def _look_up_device(self):
self._thinpools = [d for d in self._device.children if d.type == 'lvmthinpool']


class BlivetStratisPool(BlivetPool):
blivet_device_class = devices.StratisPoolDevice if hasattr(devices, "StratisPoolDevice") else None

def _type_check(self):
return self._device.type == "stratis pool"

def _update_from_device(self, param_name):
""" Return True if param_name's value was retrieved from a looked-up device. """
log.debug("BlivetStratisPool._update_from_device: %s", self._device)

if param_name == 'disks':
self._pool['disks'] = [d.name for d in self._device.disks]
elif param_name == 'encryption':
self._pool['encryption'] = self._device.encrypted
elif param_name == 'encryption_clevis_pin':
self._pool['encryption_clevis_pin'] = self._device._clevis.pin
elif param_name == 'encryption_tang_url':
self._pool['encryption_tang_url'] = self._device._clevis.tang_url
elif param_name == 'encryption_tang_thumbprint':
self._pool['encryption_tang_thumbprint'] = self._device._clevis.tang_thumbprint
else:
return False

return True

def _member_management_is_destructive(self):
if self._device is None:
return False

if self._pool['encryption'] and not self._device.encrypted:
return True
elif not self._pool['encryption'] and self._device.encrypted:
return True

return False

def _manage_members(self):
""" Schedule actions as needed to configure this pool's members. """
if not self._device:
return

add_disks = [d for d in self._disks if d not in self._device.ancestors]
remove_disks = [bd for bd in self._device.blockdevs if not any(d in bd.ancestors for d in self._disks)]

if remove_disks:
raise BlivetAnsibleError("cannot remove members '%s' from pool '%s': Stratis doesn't "
"support removing members from existing pools" %
(", ".join(d.name for d in remove_disks),
self._device.name))

if not add_disks:
return

for disk in add_disks:
member = self._create_one_member(disk)
try:
ac = ActionAddMember(self._device, member)
self._blivet.devicetree.actions.add(ac)
except Exception as e:
raise BlivetAnsibleError("failed to add disk '%s' to pool '%s': %s" % (disk.name,
self._pool['name'],
str(e)))

def _get_format(self):
fmt = get_format("stratis")
if not fmt.supported or not fmt.formattable:
raise BlivetAnsibleError("required tools for managing Stratis are missing")

return fmt

def _create(self):
if not self._device:
members = self._create_members()
try:
if self._spec_dict['encryption'] and self._spec_dict['encryption_clevis_pin']:
clevis_config = devices.stratis.StratisClevisConfig(pin=self._spec_dict['encryption_clevis_pin'],
tang_url=self._spec_dict['encryption_tang_url'],
tang_thumbprint=self._spec_dict['encryption_tang_thumbprint'])
pool_device = self._blivet.new_stratis_pool(name=self._pool['name'],
parents=members,
encrypted=self._spec_dict['encryption'],
passphrase=self._spec_dict.get('encryption_password') or None,
key_file=self._spec_dict.get('encryption_key') or None,
clevis=clevis_config)
else:
pool_device = self._blivet.new_stratis_pool(name=self._pool['name'],
parents=members,
encrypted=self._spec_dict['encryption'],
passphrase=self._spec_dict.get('encryption_password') or None,
key_file=self._spec_dict.get('encryption_key') or None)

except Exception as e:
raise BlivetAnsibleError("failed to set up pool '%s': %s" % (self._pool['name'], str(e)))

self._blivet.create_device(pool_device)
self._device = pool_device


_BLIVET_POOL_TYPES = {
"partition": BlivetPartitionPool,
"lvm": BlivetLVMPool
"lvm": BlivetLVMPool,
"stratis": BlivetStratisPool,
}


Expand Down Expand Up @@ -2161,6 +2347,9 @@ def run_module():
encryption_luks_version=dict(type='str'),
encryption_password=dict(type='str', no_log=True),
grow_to_fill=dict(type='bool'),
encryption_clevis_pin=dict(type='str'),
encryption_tang_url=dict(type='str'),
encryption_tang_thumbprint=dict(type='str'),
name=dict(type='str'),
raid_level=dict(type='str'),
raid_device_count=dict(type='int'),
Expand Down
33 changes: 27 additions & 6 deletions library/blockdev_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,10 @@

LSBLK_DEVICE_TYPES = {"part": "partition"}
DEV_MD_DIR = '/dev/md'
DEV_STRATIS_DIR = '/dev/stratis'


def fixup_md_path(path):
if not path.startswith("/dev/md"):
return path

def _fixup_md_path(path):
if not os.path.exists(DEV_MD_DIR):
return path

Expand All @@ -63,8 +61,31 @@ def fixup_md_path(path):
return ret


def _fixup_stratis_path(path):
if not os.path.exists(DEV_STRATIS_DIR):
return path

ret = path
for pool in os.listdir(DEV_STRATIS_DIR):
for fs in os.listdir(os.path.join(DEV_STRATIS_DIR, pool)):
stratis_path = os.path.join(DEV_STRATIS_DIR, pool, fs)
if os.path.realpath(stratis_path) == os.path.realpath(path):
ret = stratis_path
break
return ret


def fixup_path(path):
if path.startswith("/dev/md"):
return _fixup_md_path(path)
elif path.startswith("/dev/mapper/stratis-"):
return _fixup_stratis_path(path)
else:
return path


def get_block_info(module):
buf = module.run_command(["lsblk", "-o", "NAME,FSTYPE,LABEL,UUID,TYPE,SIZE", "-p", "-P", "-a"])[1]
buf = module.run_command(["lsblk", "-o", "NAME,FSTYPE,LABEL,UUID,TYPE,SIZE,MOUNTPOINT", "-p", "-P", "-a"])[1]
info = dict()
for line in buf.splitlines():
dev = dict()
Expand All @@ -76,7 +97,7 @@ def get_block_info(module):
raise
if key:
if key.lower() == "name":
value = fixup_md_path(value)
value = fixup_path(value)

dev[key.lower()] = LSBLK_DEVICE_TYPES.get(value, value)
if dev:
Expand Down
3 changes: 3 additions & 0 deletions tests/test-verify-pool-members.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
- name: Check VDO
include_tasks: verify-pool-members-vdo.yml

- name: Check Stratis
include_tasks: verify-pool-stratis.yml

- name: Clean up test variables
set_fact:
_storage_test_expected_pv_type: null
Expand Down
51 changes: 28 additions & 23 deletions tests/test-verify-volume-fs.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
---
# type
- name: Verify fs type
assert:
that: storage_test_blkinfo.info[storage_test_volume._device].fstype ==
storage_test_volume.fs_type or
(storage_test_blkinfo.info[storage_test_volume._device].fstype | length
== 0 and storage_test_volume.fs_type == "unformatted")
when: storage_test_volume.fs_type and _storage_test_volume_present
- name: Check volume filesystem
when: storage_test_volume.type != "stratis"
block:
- name: Verify fs type
assert:
that: storage_test_blkinfo.info[storage_test_volume._device].fstype ==
storage_test_volume.fs_type or
(storage_test_blkinfo.info[storage_test_volume._device].fstype | length
== 0 and storage_test_volume.fs_type == "unformatted")
when:
- storage_test_volume.fs_type
- _storage_test_volume_present

# label
- name: Verify fs label
assert:
that: storage_test_blkinfo.info[storage_test_volume._device].label ==
storage_test_volume.fs_label
msg: >-
Volume '{{ storage_test_volume.name }}' labels do not match when they
should
('{{ storage_test_blkinfo.info[storage_test_volume._device].label }}',
'{{ storage_test_volume.fs_label }}')
when:
- _storage_test_volume_present | bool
# label for GFS2 is set manually with the extra `-t` fs_create_options
# so we can't verify it here because it was not set with fs_label so
# the label from blkinfo doesn't match the expected "empty" fs_label
- storage_test_volume.fs_type != "gfs2"
# label
- name: Verify fs label
assert:
that: storage_test_blkinfo.info[storage_test_volume._device].label ==
storage_test_volume.fs_label
msg: >-
Volume '{{ storage_test_volume.name }}' labels do not match when they
should
('{{ storage_test_blkinfo.info[storage_test_volume._device].label }}',
'{{ storage_test_volume.fs_label }}')
when:
- _storage_test_volume_present | bool
# label for GFS2 is set manually with the extra `-t` fs_create_options
# so we can't verify it here because it was not set with fs_label so
# the label from blkinfo doesn't match the expected "empty" fs_label
- storage_test_volume.fs_type != "gfs2"
Loading

0 comments on commit 19c6e82

Please sign in to comment.