Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Stratis support #439

Merged
merged 6 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ keys:

This integer specifies the LUKS version to use.

- `encryption_clevis_pin`
richm marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -51,6 +51,15 @@
encryption_password:
description: encryption_password
type: str
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 @@ -1320,11 +1329,89 @@
leaves = [a for a in ancestors if a.isleaf]


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

Check warning on line 1333 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1332-L1333

Added lines #L1332 - L1333 were not covered by tests

def _update_from_device(self, param_name):
if param_name == 'fs_type':

Check warning on line 1336 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1335-L1336

Added lines #L1335 - L1336 were not covered by tests
# 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

Check warning on line 1341 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1340-L1341

Added lines #L1340 - L1341 were not covered by tests
else:
return super()._update_from_device(param_name)

Check warning on line 1343 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1343

Added line #L1343 was not covered by tests

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

Check warning on line 1348 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1345-L1348

Added lines #L1345 - L1348 were not covered by tests

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

Check warning on line 1354 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1350-L1354

Added lines #L1350 - L1354 were not covered by tests

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)

Check warning on line 1358 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1356-L1358

Added lines #L1356 - L1358 were not covered by tests

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'],

Check warning on line 1362 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1360-L1362

Added lines #L1360 - L1362 were not covered by tests
size,
parent_device.name,
parent_device.free_space))
else:
log.info("adjusting %s size from %s to %s to fit in %s free space",

Check warning on line 1367 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1367

Added line #L1367 was not covered by tests
self._volume['name'],
size,
parent_device.free_space,
parent_device.name)
return parent_device.free_space
return size

Check warning on line 1373 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1372-L1373

Added lines #L1372 - L1373 were not covered by tests

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")

Check warning on line 1377 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1375-L1377

Added lines #L1375 - L1377 were not covered by tests

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")

Check warning on line 1381 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1379-L1381

Added lines #L1379 - L1381 were not covered by tests

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

Check warning on line 1385 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1383-L1385

Added lines #L1383 - L1385 were not covered by tests

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

Check warning on line 1389 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1387-L1389

Added lines #L1387 - L1389 were not covered by tests

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,

Check warning on line 1393 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1391-L1393

Added lines #L1391 - L1393 were not covered by tests
self._volume['name']))

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

Check warning on line 1396 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1396

Added line #L1396 was not covered by tests

try:
device = self._blivet.new_stratis_filesystem(name=self._volume['name'],

Check warning on line 1399 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1398-L1399

Added lines #L1398 - L1399 were not covered by tests
parents=[parent_device],
size=size)
except Exception as e:
raise BlivetAnsibleError("failed to set up volume '%s': %s" % (self._volume['name'], str(e)))

Check warning on line 1403 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1402-L1403

Added lines #L1402 - L1403 were not covered by tests

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

Check warning on line 1406 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1405-L1406

Added lines #L1405 - L1406 were not covered by tests


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


Expand Down Expand Up @@ -1823,9 +1910,108 @@
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

Check warning on line 1914 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1913-L1914

Added lines #L1913 - L1914 were not covered by tests

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

Check warning on line 1917 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1916-L1917

Added lines #L1916 - L1917 were not covered by tests

def _update_from_device(self, param_name):

Check warning on line 1919 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1919

Added line #L1919 was not covered by tests
""" Return True if param_name's value was retrieved from a looked-up device. """
log.debug("BlivetStratisPool._update_from_device: %s", self._device)

Check warning on line 1921 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1921

Added line #L1921 was not covered by tests

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
richm marked this conversation as resolved.
Show resolved Hide resolved
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

Check warning on line 1932 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1923-L1932

Added lines #L1923 - L1932 were not covered by tests
else:
return False

Check warning on line 1934 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1934

Added line #L1934 was not covered by tests

return True

Check warning on line 1936 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1936

Added line #L1936 was not covered by tests

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

Check warning on line 1940 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1938-L1940

Added lines #L1938 - L1940 were not covered by tests

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

Check warning on line 1945 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1942-L1945

Added lines #L1942 - L1945 were not covered by tests

return False

Check warning on line 1947 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1947

Added line #L1947 was not covered by tests

def _manage_members(self):

Check warning on line 1949 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1949

Added line #L1949 was not covered by tests
""" Schedule actions as needed to configure this pool's members. """
if not self._device:
return

Check warning on line 1952 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1951-L1952

Added lines #L1951 - L1952 were not covered by tests

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)]

Check warning on line 1955 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1954-L1955

Added lines #L1954 - L1955 were not covered by tests

if remove_disks:
raise BlivetAnsibleError("cannot remove members '%s' from pool '%s': Stratis doesn't "

Check warning on line 1958 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1957-L1958

Added lines #L1957 - L1958 were not covered by tests
"support removing members from existing pools" %
(", ".join(d.name for d in remove_disks),
self._device.name))

if not add_disks:
return

Check warning on line 1964 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1963-L1964

Added lines #L1963 - L1964 were not covered by tests

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,

Check warning on line 1972 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1966-L1972

Added lines #L1966 - L1972 were not covered by tests
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")

Check warning on line 1979 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1976-L1979

Added lines #L1976 - L1979 were not covered by tests

return fmt

Check warning on line 1981 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1981

Added line #L1981 was not covered by tests

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'],

Check warning on line 1988 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1983-L1988

Added lines #L1983 - L1988 were not covered by tests
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'],

Check warning on line 1991 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1991

Added line #L1991 was not covered by tests
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'],

Check warning on line 1998 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L1998

Added line #L1998 was not covered by tests
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)))

Check warning on line 2005 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L2004-L2005

Added lines #L2004 - L2005 were not covered by tests

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

Check warning on line 2008 in library/blivet.py

View check run for this annotation

Codecov / codecov/patch

library/blivet.py#L2007-L2008

Added lines #L2007 - L2008 were not covered by tests


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


Expand Down Expand Up @@ -2140,6 +2326,9 @@
encryption_key_size=dict(type='int'),
encryption_luks_version=dict(type='str'),
encryption_password=dict(type='str', no_log=True),
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'

Check warning on line 47 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L47

Added line #L47 was not covered by tests


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

def _fixup_md_path(path):

Check warning on line 50 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L50

Added line #L50 was not covered by tests
if not os.path.exists(DEV_MD_DIR):
return path

Expand All @@ -63,8 +61,31 @@
return ret


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

Check warning on line 66 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L64-L66

Added lines #L64 - L66 were not covered by tests

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

Check warning on line 75 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L68-L75

Added lines #L68 - L75 were not covered by tests


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)

Check warning on line 82 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L78-L82

Added lines #L78 - L82 were not covered by tests
else:
return path

Check warning on line 84 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L84

Added line #L84 was not covered by tests


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]

Check warning on line 88 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L88

Added line #L88 was not covered by tests
info = dict()
for line in buf.splitlines():
dev = dict()
Expand All @@ -76,7 +97,7 @@
raise
if key:
if key.lower() == "name":
value = fixup_md_path(value)
value = fixup_path(value)

Check warning on line 100 in library/blockdev_info.py

View check run for this annotation

Codecov / codecov/patch

library/blockdev_info.py#L100

Added line #L100 was not covered by tests

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 @@ -85,6 +85,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
Loading