Skip to content

Test Fixtures

Pytest fixtures for storage test setup and teardown.

Common

sts.fixtures.common_fixtures

Common test fixtures for virtual block devices, system checks, and utilities.

debugfs_module_reader(managed_module)

Yield a Directory for the managed module's debugfs path.

Ensures debugfs is mounted and the module's debugfs directory exists.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
@pytest.fixture
def debugfs_module_reader(managed_module: ModuleInfo) -> Generator[Directory, None, None]:
    """Yield a Directory for the managed module's debugfs path.

    Ensures debugfs is mounted and the module's debugfs directory exists.
    """
    module_name = managed_module.name
    if not module_name:
        pytest.skip(f'Managed module fixture yielded ModuleInfo with name=None for {managed_module}')

    logger.debug(f'Setting up debugfs reader for already loaded module: {module_name}')

    # Module loading is handled by the managed_module fixture dependency.
    # We can assume managed_module.loaded is True here, otherwise the test would have skipped.

    # Ensure debugfs is mounted
    debugfs_path = Path('/sys/kernel/debug')
    if not debugfs_path.is_mount():
        logger.debug(f'Debugfs not mounted at {debugfs_path}, attempting mount.')
        try:
            # Check if debugfs filesystem type is already mounted somewhere else
            mount_output = run('mount')
            if f'debugfs on {debugfs_path}' not in mount_output.stdout and ' type debugfs' in mount_output.stdout:
                existing_mount = [line for line in mount_output.stdout.splitlines() if ' type debugfs' in line]
                logger.warning(f'Debugfs already mounted elsewhere: {existing_mount}. Proceeding anyway.')
            # Only attempt mount if not already mounted at the target path
            elif f'debugfs on {debugfs_path}' not in mount_output.stdout:
                run(f'mount -t debugfs none {debugfs_path}')

            if not debugfs_path.is_mount():
                pytest.skip(f'Failed to mount debugfs at {debugfs_path} after attempt')
        except OSError as e:
            pytest.skip(f'OS error mounting debugfs at {debugfs_path}: {e}')

    # Check module debugfs directory
    module_debugfs_dir = debugfs_path / module_name
    if not module_debugfs_dir.exists():
        pytest.skip(f'Debugfs directory {module_debugfs_dir} not found for module {module_name}')
    if not module_debugfs_dir.is_dir():
        pytest.skip(f'Path {module_debugfs_dir} exists but is not a directory')

    logger.debug(f'Providing Directory object for {module_debugfs_dir}')
    yield Directory(path=module_debugfs_dir)

    # No specific cleanup needed here; module unloading is handled by managed_module teardown.
    logger.debug(f'Finished using debugfs reader for module: {module_name}')

ensure_minimum_devices()

Fixture that ensures minimum number of devices without block size filtering.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
238
239
240
241
@pytest.fixture(scope='session')
def ensure_minimum_devices() -> Generator[list[Any], None, None]:
    """Fixture that ensures minimum number of devices without block size filtering."""
    yield from _ensure_minimum_devices_base(filter_by_block_size=False)

ensure_minimum_devices_with_same_block_sizes()

Fixture that ensures minimum number of devices with same block sizes.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
232
233
234
235
@pytest.fixture(scope='session')
def ensure_minimum_devices_with_same_block_sizes() -> Generator[list[Any], None, None]:
    """Fixture that ensures minimum number of devices with same block sizes."""
    yield from _ensure_minimum_devices_base(filter_by_block_size=True)

loop_devices(request)

Create and clean up loop devices for testing.

Parametrize count via @pytest.mark.parametrize('loop_devices', [2], indirect=True) or with custom size: [{'count': 1, 'size_mb': 4096}]. Defaults to 1 device, 1024 MB.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@pytest.fixture(scope='class')
def loop_devices(request: pytest.FixtureRequest) -> Generator[list[LoopDevice], None, None]:
    """Create and clean up loop devices for testing.

    Parametrize count via ``@pytest.mark.parametrize('loop_devices', [2], indirect=True)``
    or with custom size: ``[{'count': 1, 'size_mb': 4096}]``. Defaults to 1 device, 1024 MB.
    """
    raw_param = getattr(request, 'param', 1)
    if isinstance(raw_param, dict):
        params = cast('dict[str, Any]', raw_param)
        count: int = params.get('count', 1)
        size_mb: int = params.get('size_mb', 1024)
    else:
        count = raw_param
        size_mb = 1024

    try:
        devices: list[LoopDevice] = LoopDevice.create_multiple(count, size_mb=size_mb)
    except DeviceError as exc:
        pytest.skip(str(exc))

    yield devices

    for device in devices:
        device.remove()

prepare_1minutetip_disk()

Wipe /dev/vdb for 1minutetip ci.m1.small.ephemeral and return it as a single-item list.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
@pytest.fixture(scope='class')
def prepare_1minutetip_disk() -> list[BlockDevice]:
    """Wipe /dev/vdb for 1minutetip ci.m1.small.ephemeral and return it as a single-item list."""
    flag = Path('/var/tmp/STS_PREPARE_1MINUTETIP_DISK_FLAG')
    disk_path = '/dev/vdb'
    try:
        disk = BlockDevice(path=disk_path)
    except DeviceNotFoundError:
        pytest.fail(f'Disk {disk_path} not found')

    # Wipe disk if flag file does not exist, this is to avoid wiping the disk multiple times.
    # We need to remove partition table that is always there after provisioning.
    if not flag.exists():
        assert disk.wipe_device()
        flag.touch()
    else:
        logger.debug(f'Disk {disk_path} already wiped')

    # Return a list of one device to be used with setup_vg fixture
    return [disk]

ramdisk_loop_devices(request)

Create ramdisk-backed loopback devices via targetcli for fast I/O testing.

Defaults to 1 device, 512 MB. Parametrize with [{'count': 2, 'size_mb': 256}].

Warning

Total device size should not exceed available RAM.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
@pytest.fixture(scope='class')
def ramdisk_loop_devices(request: pytest.FixtureRequest) -> Generator[list[str], None, None]:
    """Create ramdisk-backed loopback devices via targetcli for fast I/O testing.

    Defaults to 1 device, 512 MB. Parametrize with ``[{'count': 2, 'size_mb': 256}]``.

    Warning:
        Total device size should not exceed available RAM.
    """
    # Ensure targetcli is installed
    if not ensure_installed('targetcli'):
        pytest.skip('targetcli package not available')

    # Handle different parameter formats
    raw_param = getattr(request, 'param', {})
    if isinstance(raw_param, dict):
        params = cast('dict[str, Any]', raw_param)
        count: int = params.get('count', 1)
        size_mb: int = params.get('size_mb', 512)
    else:
        count = 1
        size_mb = 512

    # Use a unique prefix for these devices
    lun_prefix = 'sts-ramdisk-'

    # Initialize loopback target
    loopback = Loopback()
    result = loopback.create_target()
    if not result.succeeded:
        pytest.skip(f'Failed to create loopback target: {result.stderr}')

    wwn = loopback.target_wwn
    if not wwn:
        loopback.delete_target()
        pytest.skip('Failed to get loopback target WWN')

    lun = LoopbackLUN(target_wwn=wwn)
    backstores: list[BackstoreRamdisk] = []

    try:
        for n in range(count):
            backstore = BackstoreRamdisk(name=f'{lun_prefix}{n}')
            size_bytes: int = size_mb * 1024 * 1024
            result = backstore.create_backstore(size=str(size_bytes))
            if not result.succeeded:
                pytest.skip(f'Failed to create ramdisk backstore {n}: {result.stderr}')
            backstores.append(backstore)

            result = lun.create_lun(storage_object=backstore.path)
            if not result.succeeded:
                pytest.skip(f'Failed to create LUN {n}: {result.stderr}')

        # Find the created devices
        devices = [f'/dev/{dev.name}' for dev in get_free_disks() if dev.model and lun_prefix in dev.model]

        if len(devices) < count:
            pytest.skip(f'Expected {count} ramdisk devices, found {len(devices)}')

        logger.debug(f'Created {count} ramdisk loopback device(s): {devices}')
        yield devices[:count]

    finally:
        for n in range(len(backstores)):
            lun.delete_lun(n)

        for backstore in backstores:
            backstore.delete_backstore()

        loopback.delete_target()

scsi_debug_devices(request)

Create SCSI debug devices for testing.

Yields count**2 device paths (num_tgts * add_host). Defaults to 1. Parametrize with @pytest.mark.parametrize('scsi_debug_devices', [2], indirect=True).

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
@pytest.fixture(scope='class')
def scsi_debug_devices(request: pytest.FixtureRequest) -> Generator[list[str], None, None]:
    """Create SCSI debug devices for testing.

    Yields ``count**2`` device paths (num_tgts * add_host). Defaults to 1.
    Parametrize with ``@pytest.mark.parametrize('scsi_debug_devices', [2], indirect=True)``.
    """
    count = getattr(request, 'param', 1)  # Default to 1 device if not specified
    total = count**2  # expected_devices = num_tgts * add_host
    logger.info(f'Creating {total} scsi_debug devices')

    # Clean up existing scsi_debug module
    try:
        if not ModuleManager().unload('scsi_debug'):
            logger.warning('Failed to unload existing scsi_debug module')
    except (ModuleInUseError, RuntimeError) as e:
        logger.warning(f'Failed to unload existing scsi_debug module: {e}')

    # Create SCSI debug device with specified number of targets
    device = ScsiDebugDevice.create(
        size=1024 * 1024 * 1024,  # 1GB
        options=f'num_tgts={count} add_host={count}',
    )
    if not device:
        pytest.skip('Failed to create SCSI debug device')

    # Get all SCSI debug devices
    devices = ScsiDebugDevice.get_devices()
    if not devices or len(devices) < total:
        device.remove()
        pytest.skip(f'Expected {total} SCSI debug devices, got {len(devices or [])}')

    # Yield device paths
    yield [f'/dev/{dev}' for dev in devices[:total]]

    # Clean up
    device.remove()

time_controller()

Yield a TimeController with NTP disabled, restored on teardown.

Warning: Requires root privileges. Temporarily changes system time.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
@pytest.fixture
def time_controller() -> Generator[TimeController, None, None]:
    """Yield a TimeController with NTP disabled, restored on teardown.

    Warning: Requires root privileges. Temporarily changes system time.
    """
    tc = TimeController()

    # Disable NTP before test
    if not tc.disable_ntp():
        pytest.skip('Failed to disable NTP - requires root privileges')

    try:
        yield tc
    finally:
        tc.restore_ntp()

timed_operation()

Provide a context manager that logs the duration of an operation.

Source code in sts_libs/src/sts/fixtures/common_fixtures.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
@pytest.fixture
def timed_operation() -> Callable[[str], AbstractContextManager[None]]:
    """Provide a context manager that logs the duration of an operation."""

    @contextmanager
    def _timed_operation(description: str) -> Generator[None, None, None]:
        start = datetime.now(tz=UTC)
        logger.info(f'Starting: {description}')
        try:
            yield
        finally:
            duration = datetime.now(tz=UTC) - start
            logger.info(f'Completed: {description} (took {duration.total_seconds():.1f}s)')

    return _timed_operation

Block Layer

Device Mapper

sts.fixtures.dm_fixtures

Device Mapper test fixtures.

Fixtures for linear, error, zero, delay, flakey, thin-pool, thin, cache, and VDO dm targets. Each fixture creates a device and handles cleanup automatically.

MountedDmContext pydantic-model

Bases: StsBaseModel

State for a DM device formatted and mounted for I/O testing.

Show JSON schema:
{
  "$defs": {
    "BlockdevInfo": {
      "description": "Parsed ``blockdev --report`` output.",
      "properties": {
        "ro": {
          "default": false,
          "title": "Ro",
          "type": "boolean"
        },
        "ra": {
          "default": 0,
          "title": "Ra",
          "type": "integer"
        },
        "log-sec": {
          "default": 0,
          "title": "Log-Sec",
          "type": "integer"
        },
        "phy-sec": {
          "default": 0,
          "title": "Phy-Sec",
          "type": "integer"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        },
        "size": {
          "default": 0,
          "title": "Size",
          "type": "integer"
        }
      },
      "title": "BlockdevInfo",
      "type": "object"
    },
    "DmDevice": {
      "additionalProperties": false,
      "description": "Base class for all Device Mapper devices.\n\nBefore ``create()``, the device is just a target configuration\n(start, size_sectors, args). After ``create()``, it becomes a full\nblock device with dm_name, path, table, and all BlockDevice functionality.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "blockdev_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/BlockdevInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "lsblk_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/LsblkInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        },
        "size_sectors": {
          "default": 0,
          "title": "Size Sectors",
          "type": "integer"
        },
        "args": {
          "default": "",
          "title": "Args",
          "type": "string"
        },
        "dm_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Dm Name"
        },
        "target_type": {
          "default": "",
          "title": "Target Type",
          "type": "string"
        },
        "table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Table"
        },
        "is_created": {
          "default": false,
          "title": "Is Created",
          "type": "boolean"
        }
      },
      "title": "DmDevice",
      "type": "object"
    },
    "LsblkInfo": {
      "description": "Parsed ``lsblk -JOb`` output.",
      "properties": {
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "rm": {
          "default": false,
          "title": "Rm",
          "type": "boolean"
        },
        "hctl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Hctl"
        },
        "state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "State"
        },
        "pttype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pttype"
        },
        "wwn": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Wwn"
        },
        "fstype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Fstype"
        },
        "mountpoint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mountpoint"
        },
        "type": {
          "default": "disk",
          "title": "Type",
          "type": "string"
        },
        "tran": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Tran"
        },
        "maj:min": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Maj:Min"
        },
        "pkname": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pkname"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        }
      },
      "title": "LsblkInfo",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a DM device formatted and mounted for I/O testing.",
  "properties": {
    "device": {
      "$ref": "#/$defs/DmDevice"
    },
    "mount_point": {
      "title": "Mount Point",
      "type": "string"
    }
  },
  "required": [
    "device",
    "mount_point"
  ],
  "title": "MountedDmContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
44
45
46
47
48
class MountedDmContext(StsBaseModel):
    """State for a DM device formatted and mounted for I/O testing."""

    device: DmDevice
    mount_point: str

VdoFormattedContext pydantic-model

Bases: StsBaseModel

State for a device formatted with vdoformat.

Show JSON schema:
{
  "$defs": {
    "BlockDevice": {
      "additionalProperties": false,
      "description": "Block device with lsblk/blockdev info, properties, and discovery.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "blockdev_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/BlockdevInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "lsblk_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/LsblkInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "BlockDevice",
      "type": "object"
    },
    "BlockdevInfo": {
      "description": "Parsed ``blockdev --report`` output.",
      "properties": {
        "ro": {
          "default": false,
          "title": "Ro",
          "type": "boolean"
        },
        "ra": {
          "default": 0,
          "title": "Ra",
          "type": "integer"
        },
        "log-sec": {
          "default": 0,
          "title": "Log-Sec",
          "type": "integer"
        },
        "phy-sec": {
          "default": 0,
          "title": "Phy-Sec",
          "type": "integer"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        },
        "size": {
          "default": 0,
          "title": "Size",
          "type": "integer"
        }
      },
      "title": "BlockdevInfo",
      "type": "object"
    },
    "LsblkInfo": {
      "description": "Parsed ``lsblk -JOb`` output.",
      "properties": {
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "rm": {
          "default": false,
          "title": "Rm",
          "type": "boolean"
        },
        "hctl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Hctl"
        },
        "state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "State"
        },
        "pttype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pttype"
        },
        "wwn": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Wwn"
        },
        "fstype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Fstype"
        },
        "mountpoint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mountpoint"
        },
        "type": {
          "default": "disk",
          "title": "Type",
          "type": "string"
        },
        "tran": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Tran"
        },
        "maj:min": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Maj:Min"
        },
        "pkname": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pkname"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        }
      },
      "title": "LsblkInfo",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a device formatted with vdoformat.",
  "properties": {
    "device_path": {
      "title": "Device Path",
      "type": "string"
    },
    "device": {
      "$ref": "#/$defs/BlockDevice"
    }
  },
  "required": [
    "device_path",
    "device"
  ],
  "title": "VdoFormattedContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
51
52
53
54
55
class VdoFormattedContext(StsBaseModel):
    """State for a device formatted with vdoformat."""

    device_path: str
    device: BlockDevice

cache_dm_device(loop_devices, request)

Create and clean up a dm-cache device from three loop devices (origin, cache, metadata).

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
@pytest.fixture
def cache_dm_device(
    loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> Generator[CacheDevice, None, None]:
    """Create and clean up a dm-cache device from three loop devices (origin, cache, metadata)."""
    if len(loop_devices) < 3:
        pytest.fail('Cache device requires at least 3 loop devices (origin, cache, metadata)')

    origin_device_path = str(loop_devices[0].path)
    cache_device_path = str(loop_devices[1].path)
    metadata_device_path = str(loop_devices[2].path)
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_CACHE_DM_NAME)
    block_size_sectors = params.get('block_size_sectors', DEFAULT_CACHE_BLOCK_SIZE_SECTORS)
    writethrough = params.get('writethrough', False)
    passthrough = params.get('passthrough', False)
    metadata2 = params.get('metadata2', False)
    no_discard_passdown = params.get('no_discard_passdown', False)
    policy = params.get('policy', 'default')
    policy_args = params.get('policy_args')

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Zero the metadata device (required for fresh cache)
    if not write_zeroes(metadata_device_path, bs=4096, count=1, conv='fsync'):
        pytest.fail('Failed to zero metadata device')

    # Create block devices
    origin_device = BlockDevice(path=origin_device_path)
    cache_device = BlockDevice(path=cache_device_path)
    metadata_device = BlockDevice(path=metadata_device_path)

    # Create cache device
    cache_dev = CacheDevice.from_block_devices(
        metadata_device=metadata_device,
        cache_device=cache_device,
        origin_device=origin_device,
        block_size_sectors=block_size_sectors,
        writethrough=writethrough,
        passthrough=passthrough,
        metadata2=metadata2,
        no_discard_passdown=no_discard_passdown,
        policy=policy,
        policy_args=policy_args,
    )
    if cache_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create cache device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created cache device: {cache_dev.dm_name}')
    yield cache_dev

    # Cleanup: remove the device
    cache_dev.remove(force=True).assert_ok()

check_kernel_format_support()

Skip test if the running kernel does not support VDO kernel-side formatting.

Requires kernel >= 6.12.0-241.el10. Uses VersionInfo to parse and compare the kernel version string.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
@pytest.fixture
def check_kernel_format_support() -> None:
    """Skip test if the running kernel does not support VDO kernel-side formatting.

    Requires kernel >= 6.12.0-241.el10. Uses VersionInfo to parse and compare
    the kernel version string.
    """
    system = SystemInfo()
    kernel_str = system.kernel
    if not kernel_str:
        pytest.skip('Cannot determine kernel version')

    try:
        kernel_version = VersionInfo.from_string(kernel_str)
    except ValueError:
        pytest.skip(f'Cannot parse kernel version: {kernel_str}')

    if kernel_version < MIN_VDO_KERNEL_FORMAT_VERSION:
        pytest.skip(f'VDO kernel format requires >= 6.12.0-241.el10, got {kernel_str}')

create_dm_device_from_targets(dm_name, targets)

Create a DM device from multiple targets.

This is a helper function for creating concatenated or multi-target devices.

Parameters:

Name Type Description Default
dm_name str

Device mapper name

required
targets list[DmDevice]

List of DmDevice instances (in configuration state)

required

Returns:

Type Description
DmDevice | None

DmDevice instance or None if creation failed

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def create_dm_device_from_targets(dm_name: str, targets: list[DmDevice]) -> DmDevice | None:
    """Create a DM device from multiple targets.

    This is a helper function for creating concatenated or multi-target devices.

    Args:
        dm_name: Device mapper name
        targets: List of DmDevice instances (in configuration state)

    Returns:
        DmDevice instance or None if creation failed
    """
    if not targets:
        return None

    # Build table from targets
    table_lines = [str(target) for target in targets]
    table = '\n'.join(table_lines)

    logger.info(f'Creating device {dm_name} with table: {table}')
    result = run(f'dmsetup create {dm_name} --table "{table}"')

    if result.failed:
        logger.error(f'Failed to create device {dm_name}: {result.stderr}')
        return None

    return DmDevice.get_by_name(dm_name)

delay_device_positional(loop_devices, request)

Create a delay device using DelayDevice.create_positional().

Like delay_dm_device but uses the positional creation API. Supports 3, 6, and 9 argument formats based on which keys are parametrized.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
@pytest.fixture
def delay_device_positional(
    loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> Generator[DelayDevice, None, None]:
    """Create a delay device using ``DelayDevice.create_positional()``.

    Like ``delay_dm_device`` but uses the positional creation API. Supports 3, 6,
    and 9 argument formats based on which keys are parametrized.
    """
    params = cast('dict[str, Any]', getattr(request, 'param', {}))

    dm_name = params.get('dm_name', 'test-delay-positional')
    device_path = str(loop_devices[0].path)
    offset = params.get('offset', 0)
    delay_ms = params.get('delay_ms', DEFAULT_DELAY_MS)

    # Get write parameters (6-arg format)
    write_delay_ms = params.get('write_delay_ms')
    write_offset = params.get('write_offset', 0)
    write_device_index: int = params.get('write_device_index', 0)

    # Get flush parameters (9-arg format)
    flush_delay_ms = params.get('flush_delay_ms')
    flush_offset = params.get('flush_offset', 0)
    flush_device_index: int = params.get('flush_device_index', 0)

    # Calculate size accounting for offsets
    block_device = BlockDevice(path=device_path).discover()
    if write_delay_ms is not None:
        if flush_delay_ms is not None:
            max_offset = max(offset, write_offset, flush_offset)
        else:
            max_offset = max(offset, write_offset)
    else:
        max_offset = offset
    if block_device.size is None:
        pytest.fail(f'Cannot determine size of device {device_path}')
    size = block_device.size // block_device.sector_size - max_offset

    # Clean up any existing device
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Build creation arguments
    create_args: dict[str, Any] = {
        'device_path': device_path,
        'offset': offset,
        'delay_ms': delay_ms,
        'size': size,
    }

    if write_delay_ms is not None:
        write_dev: LoopDevice = loop_devices[write_device_index]
        write_device_path: str = str(write_dev.path)
        create_args.update(
            {
                'write_device_path': write_device_path,
                'write_offset': write_offset,
                'write_delay_ms': write_delay_ms,
            }
        )

        if flush_delay_ms is not None:
            flush_dev: LoopDevice = loop_devices[flush_device_index]
            flush_device_path: str = str(flush_dev.path)
            create_args.update(
                {
                    'flush_device_path': flush_device_path,
                    'flush_offset': flush_offset,
                    'flush_delay_ms': flush_delay_ms,
                }
            )

    delay_dev = DelayDevice.create_positional(**create_args)

    if delay_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create delay device {dm_name}')

    logger.debug(f'Created delay device (positional): {delay_dev.dm_name}')
    yield delay_dev

    # Cleanup
    delay_dev.remove(force=True).assert_ok()

delay_dm_device(loop_devices, request)

Create and clean up a delay DM device. Supports 3, 6, and 9 argument formats.

The argument format is selected by which keys are present in parametrize: - 3-arg (default): delay_ms, offset -- uniform delay for all ops - 6-arg: read_delay_ms, write_delay_ms -- separate read/write delays - 9-arg: adds flush_delay_ms for independent flush delay

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
@pytest.fixture
def delay_dm_device(loop_devices: list[LoopDevice], request: pytest.FixtureRequest) -> Generator[DmDevice, None, None]:
    """Create and clean up a delay DM device. Supports 3, 6, and 9 argument formats.

    The argument format is selected by which keys are present in parametrize:
    - 3-arg (default): ``delay_ms``, ``offset`` -- uniform delay for all ops
    - 6-arg: ``read_delay_ms``, ``write_delay_ms`` -- separate read/write delays
    - 9-arg: adds ``flush_delay_ms`` for independent flush delay
    """
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_DELAY_DM_NAME)
    size = params.get('size')

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Determine which format to use based on parameters
    read_delay_ms = params.get('read_delay_ms')
    write_delay_ms = params.get('write_delay_ms')
    flush_delay_ms = params.get('flush_delay_ms')

    # Get devices
    read_device = BlockDevice(path=loop_devices[0].path).discover()

    if read_delay_ms is not None and write_delay_ms is not None:
        # 6 or 9 argument format
        read_offset: int = params.get('read_offset', 0)
        write_offset: int = params.get('write_offset', 0)
        write_device_index: int = params.get('write_device_index', 0)
        write_device = BlockDevice(path=loop_devices[write_device_index].path).discover()

        if flush_delay_ms is not None:
            # 9 argument format
            flush_offset: int = params.get('flush_offset', 0)
            flush_device_index: int = params.get('flush_device_index', 0)
            flush_device = BlockDevice(path=loop_devices[flush_device_index].path).discover()

            # Calculate size accounting for offsets to avoid accessing beyond device boundaries
            if size is None and read_device.size is not None:
                max_offset = max(read_offset, write_offset, flush_offset)
                size = read_device.size // read_device.sector_size - max_offset

            delay_dev = DelayDevice.from_block_devices_rwf(
                read_device=read_device,
                read_offset=read_offset,
                read_delay_ms=read_delay_ms,
                write_device=write_device,
                write_offset=write_offset,
                write_delay_ms=write_delay_ms,
                flush_device=flush_device,
                flush_offset=flush_offset,
                flush_delay_ms=flush_delay_ms,
                size=size,
            )
        else:
            # 6 argument format
            # Calculate size accounting for offsets to avoid accessing beyond device boundaries
            if size is None and read_device.size is not None:
                max_offset = max(read_offset, write_offset)
                size = read_device.size // read_device.sector_size - max_offset

            delay_dev = DelayDevice.from_block_devices_rw(
                read_device=read_device,
                read_offset=read_offset,
                read_delay_ms=read_delay_ms,
                write_device=write_device,
                write_offset=write_offset,
                write_delay_ms=write_delay_ms,
                size=size,
            )
    else:
        # 3 argument format (default)
        delay_ms = params.get('delay_ms', DEFAULT_DELAY_MS)
        offset = params.get('offset', 0)

        # Calculate size accounting for offset to avoid accessing beyond device boundaries
        if size is None and read_device.size is not None:
            size = read_device.size // read_device.sector_size - offset

        delay_dev = DelayDevice.from_block_device(
            device=read_device,
            delay_ms=delay_ms,
            offset=offset,
            size_sectors=size,
        )

    if delay_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create delay device {dm_name}')

    logger.debug(f'Created delay device: {delay_dev.dm_name}')
    yield delay_dev

    # Cleanup: remove the device
    delay_dev.remove(force=True).assert_ok()

error_dm_device(request)

Create and clean up an error DM device (returns I/O errors, no backing device).

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@pytest.fixture
def error_dm_device(request: pytest.FixtureRequest) -> Generator[ErrorDevice, None, None]:
    """Create and clean up an error DM device (returns I/O errors, no backing device)."""
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_ERROR_DM_NAME)
    size = params.get('size', DEFAULT_ERROR_SIZE_SECTORS)

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Create error device (start is always 0 for single-segment devices)
    error_dev = ErrorDevice.create_config(start=0, size=size)
    if error_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create error device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created error device: {error_dev.dm_name}')
    yield error_dev

    # Cleanup: remove the device
    error_dev.remove(force=True).assert_ok()

flakey_dm_device(loop_devices, request)

Create and clean up a flakey DM device that simulates unreliable storage.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
@pytest.fixture
def flakey_dm_device(
    loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> Generator[FlakeyDevice, None, None]:
    """Create and clean up a flakey DM device that simulates unreliable storage."""
    device_path = str(loop_devices[0].path)
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_FLAKEY_DM_NAME)
    up_interval = params.get('up_interval', DEFAULT_FLAKEY_UP_INTERVAL)
    down_interval = params.get('down_interval', DEFAULT_FLAKEY_DOWN_INTERVAL)
    offset = params.get('offset', 0)
    size = params.get('size')
    drop_writes = params.get('drop_writes', False)
    error_writes = params.get('error_writes', False)
    corrupt_bio_byte = params.get('corrupt_bio_byte')

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Create flakey device
    device = BlockDevice(path=device_path)
    flakey_dev = FlakeyDevice.from_block_device(
        device=device,
        up_interval=up_interval,
        down_interval=down_interval,
        offset=offset,
        size_sectors=size,
        drop_writes=drop_writes,
        error_writes=error_writes,
        corrupt_bio_byte=corrupt_bio_byte,
    )
    if flakey_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create flakey device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created flakey device: {flakey_dev.dm_name}')
    yield flakey_dev

    # Cleanup: remove the device
    flakey_dev.remove(force=True).assert_ok()

linear_dm_device(loop_devices, request)

Create and clean up a linear DM device from the first loop device.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@pytest.fixture
def linear_dm_device(
    loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> Generator[LinearDevice, None, None]:
    """Create and clean up a linear DM device from the first loop device."""
    device_path = str(loop_devices[0].path)
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_LINEAR_DM_NAME)
    size = params.get('size')
    offset = params.get('offset', 0)

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Create linear device
    device = BlockDevice(path=device_path)
    linear_dev = LinearDevice.from_block_device(device, size_sectors=size, offset=offset)
    if linear_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create linear device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created linear device: {linear_dev.dm_name}')
    yield linear_dev

    # Cleanup: remove the device
    linear_dev.remove(force=True).assert_ok()

mounted_dm_device(request)

Format and mount a DM device, yielding a MountedDmContext.

Parametrize dm_device_fixture (default 'delay_dm_device'), fs_type (default 'ext4'), and mount_point (default '/mnt/sts-dm-test').

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
@pytest.fixture
def mounted_dm_device(
    request: pytest.FixtureRequest,
) -> Generator[MountedDmContext, None, None]:
    """Format and mount a DM device, yielding a MountedDmContext.

    Parametrize ``dm_device_fixture`` (default ``'delay_dm_device'``),
    ``fs_type`` (default ``'ext4'``), and ``mount_point`` (default ``'/mnt/sts-dm-test'``).
    """
    params = getattr(request, 'param', {})

    dm_device_fixture = params.get('dm_device_fixture', 'delay_dm_device')
    fs_type = params.get('fs_type', 'ext4')
    mount_point = params.get('mount_point', '/mnt/sts-dm-test')

    # Get the DM device from the specified fixture
    dm_device = request.getfixturevalue(dm_device_fixture)
    dm_device_path = dm_device.dm_device_path

    if dm_device_path is None:
        pytest.fail('DM device path not available')

    # Create filesystem
    logger.debug(f'Creating {fs_type} filesystem on {dm_device_path}')
    if not mkfs(dm_device_path, fs_type, force=True):
        pytest.fail(f'Failed to create {fs_type} filesystem on {dm_device_path}')

    # Create mount point and mount
    mount_dir = Directory(path=Path(mount_point), create=True)
    if not mount_dir.exists:
        pytest.fail(f'Failed to create mount point {mount_point}')

    logger.debug(f'Mounting {dm_device_path} at {mount_point}')
    if not mount(dm_device_path, mount_point):
        mount_dir.remove_dir()
        pytest.fail(f'Failed to mount {dm_device_path} at {mount_point}')

    yield MountedDmContext(device=dm_device, mount_point=mount_point)

    # Cleanup: unmount and remove mount point
    if not umount(mount_point):
        logger.warning(f'Failed to unmount {mount_point}')
    mount_dir.remove_dir()
    logger.debug(f'Unmounted and removed {mount_point}')

thin_dm_device(thin_pool_dm_device, request)

Create and clean up a thin device from the thin_pool_dm_device fixture.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
@pytest.fixture
def thin_dm_device(
    thin_pool_dm_device: ThinPoolDevice, request: pytest.FixtureRequest
) -> Generator[ThinDevice, None, None]:
    """Create and clean up a thin device from the thin_pool_dm_device fixture."""
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_THIN_DM_NAME)
    thin_id = params.get('thin_id', 0)
    size = params.get('size', DEFAULT_THIN_SIZE_SECTORS)

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Create thin device using pool's create_thin method
    thin_dev = thin_pool_dm_device.create_thin(
        thin_id=thin_id,
        size=size,
        dm_name=dm_name,
    )
    if thin_dev is None:
        pytest.fail(f'Failed to create thin device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created thin device: {thin_dev.dm_name}')
    yield thin_dev

    # Cleanup: use pool's delete_thin method for proper cleanup
    thin_pool_dm_device.delete_thin(thin_id, force=True)

thin_pool_dm_device(loop_devices, request)

Create and clean up a thin-pool DM device from two loop devices (data + metadata).

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
@pytest.fixture
def thin_pool_dm_device(
    loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> Generator[ThinPoolDevice, None, None]:
    """Create and clean up a thin-pool DM device from two loop devices (data + metadata)."""
    if len(loop_devices) < 2:
        pytest.fail('Thin pool requires at least 2 loop devices (data and metadata)')

    data_device_path = str(loop_devices[0].path)
    metadata_device_path = str(loop_devices[1].path)
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_THIN_POOL_DM_NAME)
    block_size_sectors = params.get('block_size_sectors', DEFAULT_THIN_BLOCK_SIZE_SECTORS)
    low_water_mark = params.get('low_water_mark', DEFAULT_THIN_LOW_WATER_MARK)
    features = params.get('features', ['skip_block_zeroing'])

    # Clean up any existing device from previous failed runs
    existing = DmDevice.get_by_name(dm_name)
    if existing:
        existing.remove(force=True)

    # Zero the metadata device (required for fresh thin-pool)
    if not write_zeroes(metadata_device_path, bs=4096, count=1, conv='fsync'):
        pytest.fail('Failed to zero metadata device')

    # Create block devices
    data_device = BlockDevice(path=data_device_path)
    metadata_device = BlockDevice(path=metadata_device_path)

    # Create thin-pool device
    pool_dev = ThinPoolDevice.from_block_devices(
        metadata_device=metadata_device,
        data_device=data_device,
        block_size_sectors=block_size_sectors,
        low_water_mark=low_water_mark,
        features=features,
    )
    if pool_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create thin-pool device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created thin-pool device: {pool_dev.dm_name}')
    yield pool_dev

    # Cleanup: remove the device
    pool_dev.remove(force=True).assert_ok()

vdo_dm_device(vdo_formatted_device, request)

Create and clean up a VDO DM device on top of a vdoformat-formatted backing device.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
@pytest.fixture
def vdo_dm_device(
    vdo_formatted_device: VdoFormattedContext, request: pytest.FixtureRequest
) -> Generator[DmDevice, None, None]:
    """Create and clean up a VDO DM device on top of a vdoformat-formatted backing device."""
    device = vdo_formatted_device.device
    params = dict(getattr(request, 'param', {}))

    dm_name = params.pop('dm_name', DEFAULT_VDO_DM_NAME)
    logical_size_sectors = params.pop('logical_size_sectors', DEFAULT_VDO_LOGICAL_SIZE_SECTORS)

    # Required VDO parameters with defaults
    minimum_io_size = params.pop('minimum_io_size', 4096)
    block_map_cache_size_mb = params.pop('block_map_cache_size_mb', 128)
    block_map_period = params.pop('block_map_period', 16380)

    # Clean up any existing device from previous failed runs
    run(f'dmsetup remove -f {dm_name}')

    # Create VDO device using VdoDevice.from_block_device()
    vdo_dev = VdoDevice.from_block_device(
        device=device,
        logical_size_sectors=logical_size_sectors,
        minimum_io_size=minimum_io_size,
        block_map_cache_size_mb=block_map_cache_size_mb,
        block_map_period=block_map_period,
        **params,
    )
    if vdo_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create VDO device {dm_name}')

    logger.debug(f'Created VDO device: {vdo_dev.dm_name}')
    yield vdo_dev

    # Cleanup: remove the device
    vdo_dev.remove(force=True).assert_ok()

vdo_formatted_device(load_vdo_module, loop_devices, request)

Format the first loop device with vdoformat and return a VdoFormattedContext.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
@pytest.fixture
def vdo_formatted_device(
    load_vdo_module: str, loop_devices: list[LoopDevice], request: pytest.FixtureRequest
) -> VdoFormattedContext:
    """Format the first loop device with vdoformat and return a VdoFormattedContext."""
    _ = load_vdo_module  # Ensure VDO module is loaded

    device_path = str(loop_devices[0].path)
    params = getattr(request, 'param', {})

    logical_size = params.get('logical_size', DEFAULT_VDO_LOGICAL_SIZE_HUMAN)
    slab_bits = params.get('slab_bits')
    uds_memory_size = params.get('uds_memory_size')
    uds_sparse = params.get('uds_sparse', False)
    force = params.get('force', True)

    # Format device with VdoFormat class
    vdo_format = VdoFormat(
        device=device_path,
        logical_size=logical_size,
        slab_bits=slab_bits,
        uds_memory_size=uds_memory_size,
        uds_sparse=uds_sparse,
        force=force,
    )

    if not vdo_format.format():
        pytest.fail(f'vdoformat failed on {device_path} (tool may not be installed)')

    logger.debug(f'Formatted {device_path} with vdoformat (logical_size={logical_size})')

    device = BlockDevice(path=device_path)
    return VdoFormattedContext(device_path=device_path, device=device)

vdo_kernel_formatted_device(load_vdo_module, loop_devices, request, check_kernel_format_support)

Create a VDO device using kernel-side formatting (no vdoformat).

Requires kernel >= 6.12.0-241.el10 where dm-vdo auto-formats zeroed backing devices. Automatically skips on unsupported kernels.

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
@pytest.fixture
def vdo_kernel_formatted_device(
    load_vdo_module: str,
    loop_devices: list[LoopDevice],
    request: pytest.FixtureRequest,
    check_kernel_format_support: None,
) -> Generator[DmDevice, None, None]:
    """Create a VDO device using kernel-side formatting (no vdoformat).

    Requires kernel >= 6.12.0-241.el10 where dm-vdo auto-formats zeroed backing
    devices. Automatically skips on unsupported kernels.
    """
    _ = load_vdo_module
    _ = check_kernel_format_support

    device_path = str(loop_devices[0].path)
    params = dict(getattr(request, 'param', {}))

    dm_name = params.pop('dm_name', DEFAULT_VDO_KERNEL_FORMAT_DM_NAME)
    logical_size_sectors = params.pop('logical_size_sectors', DEFAULT_VDO_LOGICAL_SIZE_SECTORS)

    minimum_io_size = params.pop('minimum_io_size', 4096)
    block_map_cache_size_mb = params.pop('block_map_cache_size_mb', 128)
    block_map_period = params.pop('block_map_period', 16380)

    # Clean up any existing device from previous failed runs
    run(f'dmsetup remove -f {dm_name}')

    device = BlockDevice(path=device_path)

    # No vdoformat -- the zeroed loop device triggers kernel-side formatting
    vdo_dev = VdoDevice.from_block_device(
        device=device,
        logical_size_sectors=logical_size_sectors,
        minimum_io_size=minimum_io_size,
        block_map_cache_size_mb=block_map_cache_size_mb,
        block_map_period=block_map_period,
        **params,
    )
    if not vdo_dev.create(dm_name):
        pytest.fail(f'Failed to create kernel-formatted VDO device {dm_name}')

    logger.info(f'Created kernel-formatted VDO device: {vdo_dev.dm_name}')
    yield vdo_dev

    # Cleanup: remove the device
    assert vdo_dev.remove(force=True)

zero_dm_device(request)

Create and clean up a zero DM device (returns zeros on read, no backing device).

Source code in sts_libs/src/sts/fixtures/dm_fixtures.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@pytest.fixture
def zero_dm_device(request: pytest.FixtureRequest) -> Generator[ZeroDevice, None, None]:
    """Create and clean up a zero DM device (returns zeros on read, no backing device)."""
    params = getattr(request, 'param', {})

    dm_name = params.get('dm_name', DEFAULT_ZERO_DM_NAME)
    size = params.get('size', DEFAULT_ZERO_SIZE_SECTORS)

    # Create zero device (start is always 0 for single-segment devices)
    zero_dev = ZeroDevice.create_config(start=0, size=size)
    if zero_dev.create(dm_name).failed:
        pytest.fail(f'Failed to create zero device {dm_name}')

    # Wait for udev to process device creation
    udevadm_settle()

    logger.debug(f'Created zero device: {zero_dev.dm_name}')
    yield zero_dev

    # Cleanup: remove the device
    zero_dev.remove(force=True).assert_ok()

Device Mapper Persistent Data

sts.fixtures.dmpd_fixtures

device-mapper-persistent-data (dmpd) test fixtures.

These fixtures build LVM thin-pool and cache-pool metadata into known states (including deliberately "broken" metadata) for exercising the dmpd tools (thin_dump, thin_check, thin_repair, thin_restore, thin_trim, cache_dump, cache_check, cache_repair, cache_restore).

CachePoolSplitState pydantic-model

Bases: StsBaseModel

Cache pool state flowing through pool/volume/split/swap chain (internal).

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Cache pool state flowing through pool/volume/split/swap chain (internal).",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "cache_origin_name": {
      "title": "Cache Origin Name",
      "type": "string"
    },
    "cache_pool_name": {
      "title": "Cache Pool Name",
      "type": "string"
    },
    "cached_lv_name": {
      "default": "",
      "title": "Cached Lv Name",
      "type": "string"
    },
    "cache_metadata_dev": {
      "default": "",
      "title": "Cache Metadata Dev",
      "type": "string"
    }
  },
  "required": [
    "vg_name",
    "cache_origin_name",
    "cache_pool_name"
  ],
  "title": "CachePoolSplitState",
  "type": "object"
}

Fields:

  • vg_name (str)
  • cache_origin_name (str)
  • cache_pool_name (str)
  • cached_lv_name (str)
  • cache_metadata_dev (str)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
 94
 95
 96
 97
 98
 99
100
101
class CachePoolSplitState(StsBaseModel):
    """Cache pool state flowing through pool/volume/split/swap chain (internal)."""

    vg_name: str
    cache_origin_name: str
    cache_pool_name: str
    cached_lv_name: str = ''
    cache_metadata_dev: str = ''

CacheTestContext pydantic-model

Bases: StsBaseModel

Terminal state for the cache-pool metadata fixture chain (dmpd testing).

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Terminal state for the cache-pool metadata fixture chain (dmpd testing).",
  "properties": {
    "cache_metadata_dev": {
      "title": "Cache Metadata Dev",
      "type": "string"
    },
    "cache_dump_path": {
      "format": "path",
      "title": "Cache Dump Path",
      "type": "string"
    },
    "cache_repair_path": {
      "format": "path",
      "title": "Cache Repair Path",
      "type": "string"
    }
  },
  "required": [
    "cache_metadata_dev",
    "cache_dump_path",
    "cache_repair_path"
  ],
  "title": "CacheTestContext",
  "type": "object"
}

Fields:

  • cache_metadata_dev (str)
  • cache_dump_path (Path)
  • cache_repair_path (Path)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
104
105
106
107
108
109
class CacheTestContext(StsBaseModel):
    """Terminal state for the cache-pool metadata fixture chain (dmpd testing)."""

    cache_metadata_dev: str
    cache_dump_path: Path
    cache_repair_path: Path

CacheVolumeState pydantic-model

Bases: StsBaseModel

Cache volume names for cache pool operations (internal chain state).

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Cache volume names for cache pool operations (internal chain state).",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "cache_meta_name": {
      "title": "Cache Meta Name",
      "type": "string"
    },
    "cache_origin_name": {
      "title": "Cache Origin Name",
      "type": "string"
    },
    "cache_data_name": {
      "title": "Cache Data Name",
      "type": "string"
    }
  },
  "required": [
    "vg_name",
    "cache_meta_name",
    "cache_origin_name",
    "cache_data_name"
  ],
  "title": "CacheVolumeState",
  "type": "object"
}

Fields:

  • vg_name (str)
  • cache_meta_name (str)
  • cache_origin_name (str)
  • cache_data_name (str)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
85
86
87
88
89
90
91
class CacheVolumeState(StsBaseModel):
    """Cache volume names for cache pool operations (internal chain state)."""

    vg_name: str
    cache_meta_name: str
    cache_origin_name: str
    cache_data_name: str

SwapVolumeInfo pydantic-model

Bases: StsBaseModel

Swap volume state for metadata operations (internal to fixture chain).

Show JSON schema:
{
  "$defs": {
    "LVReport": {
      "description": "Parsed LV data from 'lvs -o lv_all,seg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one logical volume's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').\nAll fields match lvs JSON key names and retain str | None typing,\nexcept for a handful of numeric fields (see below) that are coerced.",
      "properties": {
        "lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Uuid"
        },
        "lv_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Name"
        },
        "lv_full_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Name"
        },
        "lv_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Path"
        },
        "lv_dm_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Dm Path"
        },
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "lv_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Size"
        },
        "lv_metadata_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Metadata Size"
        },
        "seg_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Count"
        },
        "lv_layout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Layout"
        },
        "lv_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Role"
        },
        "lv_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Attr"
        },
        "lv_active": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active"
        },
        "lv_active_locally": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Locally"
        },
        "lv_active_remotely": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Remotely"
        },
        "lv_active_exclusively": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Exclusively"
        },
        "lv_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Permissions"
        },
        "lv_suspended": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Suspended"
        },
        "lv_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Major"
        },
        "lv_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Minor"
        },
        "lv_kernel_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Major"
        },
        "lv_kernel_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Minor"
        },
        "lv_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Read Ahead"
        },
        "lv_kernel_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Read Ahead"
        },
        "pool_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv"
        },
        "pool_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv Uuid"
        },
        "data_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv"
        },
        "data_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv Uuid"
        },
        "metadata_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv"
        },
        "metadata_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv Uuid"
        },
        "data_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Percent"
        },
        "metadata_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Percent"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "origin_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Uuid"
        },
        "origin_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Size"
        },
        "snap_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Percent"
        },
        "raid_mismatch_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Mismatch Count"
        },
        "raid_sync_action": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Sync Action"
        },
        "raid_write_behind": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Write Behind"
        },
        "raid_min_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Min Recovery Rate"
        },
        "raid_max_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Max Recovery Rate"
        },
        "cache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Total Blocks"
        },
        "cache_used_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Used Blocks"
        },
        "cache_dirty_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Dirty Blocks"
        },
        "cache_read_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Hits"
        },
        "cache_read_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Misses"
        },
        "cache_write_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Hits"
        },
        "cache_write_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Misses"
        },
        "kernel_cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Settings"
        },
        "kernel_cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Policy"
        },
        "vdo_operating_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Operating Mode"
        },
        "vdo_compression_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression State"
        },
        "vdo_index_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index State"
        },
        "vdo_used_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Used Size"
        },
        "vdo_saving_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Saving Percent"
        },
        "writecache_block_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Block Size"
        },
        "writecache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Total Blocks"
        },
        "writecache_free_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Free Blocks"
        },
        "writecache_writeback_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Writeback Blocks"
        },
        "writecache_error": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Error"
        },
        "lv_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Policy"
        },
        "lv_allocation_locked": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Locked"
        },
        "lv_autoactivation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Autoactivation"
        },
        "lv_when_full": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv When Full"
        },
        "lv_skip_activation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Skip Activation"
        },
        "lv_fixed_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Fixed Minor"
        },
        "lv_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time"
        },
        "lv_time_removed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time Removed"
        },
        "lv_host": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Host"
        },
        "lv_health_status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Health Status"
        },
        "lv_check_needed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Check Needed"
        },
        "lv_merge_failed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merge Failed"
        },
        "lv_snapshot_invalid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Snapshot Invalid"
        },
        "lv_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Tags"
        },
        "lv_profile": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Profile"
        },
        "lv_lockargs": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Lockargs"
        },
        "lv_modules": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Modules"
        },
        "lv_historical": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Historical"
        },
        "kernel_discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Discards"
        },
        "copy_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Copy Percent"
        },
        "sync_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Sync Percent"
        },
        "lv_live_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Live Table"
        },
        "lv_inactive_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Inactive Table"
        },
        "lv_device_open": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Device Open"
        },
        "lv_parent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Parent"
        },
        "lv_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Ancestors"
        },
        "lv_full_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Ancestors"
        },
        "lv_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Descendants"
        },
        "lv_full_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Descendants"
        },
        "lv_converting": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Converting"
        },
        "lv_merging": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merging"
        },
        "move_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv"
        },
        "move_pv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv Uuid"
        },
        "convert_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv"
        },
        "convert_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv Uuid"
        },
        "mirror_log": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log"
        },
        "mirror_log_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log Uuid"
        },
        "lv_initial_image_sync": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Initial Image Sync"
        },
        "lv_image_synced": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Image Synced"
        },
        "raidintegritymode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegritymode"
        },
        "raidintegrityblocksize": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegrityblocksize"
        },
        "integritymismatches": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integritymismatches"
        },
        "kernel_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Metadata Format"
        },
        "segtype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Segtype"
        },
        "stripes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripes"
        },
        "data_stripes": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Stripes"
        },
        "stripe_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripe Size"
        },
        "region_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Region Size"
        },
        "chunk_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Chunk Size"
        },
        "seg_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start"
        },
        "seg_start_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start Pe"
        },
        "seg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size"
        },
        "seg_size_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size Pe"
        },
        "seg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Tags"
        },
        "seg_pe_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Pe Ranges"
        },
        "seg_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Le Ranges"
        },
        "seg_metadata_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Metadata Le Ranges"
        },
        "devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Devices"
        },
        "metadata_devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Devices"
        },
        "seg_monitor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Monitor"
        },
        "reshape_len": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len"
        },
        "reshape_len_le": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len Le"
        },
        "data_copies": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Copies"
        },
        "data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Offset"
        },
        "new_data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "New Data Offset"
        },
        "parity_chunks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Parity Chunks"
        },
        "thin_count": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Count"
        },
        "discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Discards"
        },
        "cache_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Metadata Format"
        },
        "cache_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Mode"
        },
        "zero": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Zero"
        },
        "transaction_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Transaction Id"
        },
        "thin_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Id"
        },
        "cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Policy"
        },
        "cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Settings"
        },
        "integrity_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integrity Settings"
        },
        "vdo_compression": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression"
        },
        "vdo_deduplication": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Deduplication"
        },
        "vdo_minimum_io_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Minimum Io Size"
        },
        "vdo_block_map_cache_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Cache Size"
        },
        "vdo_block_map_era_length": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Era Length"
        },
        "vdo_use_sparse_index": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Sparse Index"
        },
        "vdo_index_memory_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index Memory Size"
        },
        "vdo_slab_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Slab Size"
        },
        "vdo_ack_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Ack Threads"
        },
        "vdo_bio_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Threads"
        },
        "vdo_bio_rotation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Rotation"
        },
        "vdo_cpu_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Cpu Threads"
        },
        "vdo_hash_zone_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Hash Zone Threads"
        },
        "vdo_logical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Logical Threads"
        },
        "vdo_physical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Physical Threads"
        },
        "vdo_max_discard": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Max Discard"
        },
        "vdo_header_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Header Size"
        },
        "vdo_use_metadata_hints": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Metadata Hints"
        },
        "vdo_write_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Write Policy"
        }
      },
      "title": "LVReport",
      "type": "object"
    },
    "LogicalVolume": {
      "additionalProperties": false,
      "description": "Logical Volume device.\n\nExample:\n    ```python\n    lv = LogicalVolume(name='lv0', vg='vg0')\n    lv.create(size='100M').assert_ok()\n    print(lv.report.lv_size)\n    ```",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "LogicalVolume",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "Swap volume state for metadata operations (internal to fixture chain).",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "swap_name": {
      "title": "Swap Name",
      "type": "string"
    },
    "swap_lv": {
      "$ref": "#/$defs/LogicalVolume"
    }
  },
  "required": [
    "vg_name",
    "swap_name",
    "swap_lv"
  ],
  "title": "SwapVolumeInfo",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
43
44
45
46
47
48
class SwapVolumeInfo(StsBaseModel):
    """Swap volume state for metadata operations (internal to fixture chain)."""

    vg_name: str
    swap_name: str
    swap_lv: LogicalVolume

ThinMetadataContext pydantic-model

Bases: StsBaseModel

Terminal state for the thin-pool metadata fixture chain (dmpd testing).

By the time tests consume this model through setup_thin_metadata_for_dmpd or restored_thin_pool, all fields are populated.

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Terminal state for the thin-pool metadata fixture chain (dmpd testing).\n\nBy the time tests consume this model through ``setup_thin_metadata_for_dmpd``\nor ``restored_thin_pool``, all fields are populated.",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "pool_name": {
      "title": "Pool Name",
      "type": "string"
    },
    "swap_name": {
      "title": "Swap Name",
      "type": "string"
    },
    "metadata_dev": {
      "title": "Metadata Dev",
      "type": "string"
    },
    "metadata_backup_path": {
      "format": "path",
      "title": "Metadata Backup Path",
      "type": "string"
    },
    "metadata_repair_path": {
      "format": "path",
      "title": "Metadata Repair Path",
      "type": "string"
    },
    "metadata_working_path": {
      "format": "path",
      "title": "Metadata Working Path",
      "type": "string"
    }
  },
  "required": [
    "vg_name",
    "pool_name",
    "swap_name",
    "metadata_dev",
    "metadata_backup_path",
    "metadata_repair_path",
    "metadata_working_path"
  ],
  "title": "ThinMetadataContext",
  "type": "object"
}

Fields:

  • vg_name (str)
  • pool_name (str)
  • swap_name (str)
  • metadata_dev (str)
  • metadata_backup_path (Path)
  • metadata_repair_path (Path)
  • metadata_working_path (Path)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class ThinMetadataContext(StsBaseModel):
    """Terminal state for the thin-pool metadata fixture chain (dmpd testing).

    By the time tests consume this model through ``setup_thin_metadata_for_dmpd``
    or ``restored_thin_pool``, all fields are populated.
    """

    vg_name: str
    pool_name: str
    swap_name: str
    metadata_dev: str
    metadata_backup_path: Path
    metadata_repair_path: Path
    metadata_working_path: Path

ThinPoolState pydantic-model

Bases: StsBaseModel

Thin pool state after volume creation and lifecycle (internal chain state).

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Thin pool state after volume creation and lifecycle (internal chain state).",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "pool_name": {
      "title": "Pool Name",
      "type": "string"
    },
    "thin_count": {
      "title": "Thin Count",
      "type": "integer"
    },
    "thin_base_name": {
      "title": "Thin Base Name",
      "type": "string"
    }
  },
  "required": [
    "vg_name",
    "pool_name",
    "thin_count",
    "thin_base_name"
  ],
  "title": "ThinPoolState",
  "type": "object"
}

Fields:

  • vg_name (str)
  • pool_name (str)
  • thin_count (int)
  • thin_base_name (str)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
51
52
53
54
55
56
57
class ThinPoolState(StsBaseModel):
    """Thin pool state after volume creation and lifecycle (internal chain state)."""

    vg_name: str
    pool_name: str
    thin_count: int
    thin_base_name: str

ThinSwapState pydantic-model

Bases: StsBaseModel

State after thin metadata swap to swap volume (internal chain state).

Show JSON schema:
{
  "additionalProperties": false,
  "description": "State after thin metadata swap to swap volume (internal chain state).",
  "properties": {
    "vg_name": {
      "title": "Vg Name",
      "type": "string"
    },
    "pool_name": {
      "title": "Pool Name",
      "type": "string"
    },
    "swap_name": {
      "title": "Swap Name",
      "type": "string"
    },
    "metadata_dev": {
      "title": "Metadata Dev",
      "type": "string"
    }
  },
  "required": [
    "vg_name",
    "pool_name",
    "swap_name",
    "metadata_dev"
  ],
  "title": "ThinSwapState",
  "type": "object"
}

Fields:

  • vg_name (str)
  • pool_name (str)
  • swap_name (str)
  • metadata_dev (str)
Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
60
61
62
63
64
65
66
class ThinSwapState(StsBaseModel):
    """State after thin metadata swap to swap volume (internal chain state)."""

    vg_name: str
    pool_name: str
    swap_name: str
    metadata_dev: str

binary_metadata_file()

Yield a pre-allocated 5MB binary file in /var/tmp for DMPD metadata operations.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
126
127
128
129
130
131
132
133
134
135
136
137
138
@pytest.fixture
def binary_metadata_file() -> Generator[Path, None, None]:
    """Yield a pre-allocated 5MB binary file in /var/tmp for DMPD metadata operations."""
    binary_file = Path('/var/tmp/thin_check_metadata.bin')
    assert fallocate(str(binary_file), length='5M'), f'Failed to allocate {binary_file}'
    logger.info(f'Allocated binary metadata file: {binary_file} (5MB)')

    yield binary_file

    # Cleanup
    if binary_file.exists():
        binary_file.unlink()
        logger.debug(f'Cleaned up binary metadata file: {binary_file}')

cache_metadata_backup(cache_metadata_swap)

Dump cache metadata and prepare a repair file for DMPD testing.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
@pytest.fixture
def cache_metadata_backup(cache_metadata_swap: CachePoolSplitState) -> Generator[CacheTestContext, None, None]:
    """Dump cache metadata and prepare a repair file for DMPD testing."""
    state = cache_metadata_swap
    cache_metadata_dev = state.cache_metadata_dev
    cache_dump_path = Path('/var/tmp/cache_dump')
    cache_repair_path = Path('/var/tmp/cache_repair')

    # Create cache metadata dump (to match testing expectations)
    dump_result = dmpd.cache_dump(cache_metadata_dev, output=str(cache_dump_path))
    assert dump_result.succeeded

    # Create empty repair file with proper allocation (5MB should be enough)
    assert fallocate(cache_repair_path, length='5M')

    yield CacheTestContext(
        cache_metadata_dev=cache_metadata_dev,
        cache_dump_path=cache_dump_path,
        cache_repair_path=cache_repair_path,
    )

    # Cleanup files
    run(f'rm -f {cache_dump_path} {cache_repair_path}')

cache_metadata_swap(cache_split, swap_volume)

Swap cache pool metadata into the swap volume via lvconvert.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
@pytest.fixture
def cache_metadata_swap(cache_split: CachePoolSplitState, swap_volume: SwapVolumeInfo) -> CachePoolSplitState:
    """Swap cache pool metadata into the swap volume via lvconvert."""
    state = cache_split
    swap_info = swap_volume

    # Ensure both fixtures reference the same VG
    assert state.vg_name == swap_info.vg_name, 'Cache and swap must be in same VG'

    vg_name = state.vg_name
    cache_pool_name = state.cache_pool_name
    swap_name = swap_info.swap_name

    # Deactivate volumes before metadata swap
    cache_pool_lv = LogicalVolume(name=cache_pool_name, vg=vg_name)
    cache_pool_lv.deactivate()  # Ignore errors
    swap_lv = LogicalVolume(name=swap_name, vg=vg_name)
    swap_lv.deactivate()
    udevadm_settle()

    # Swap cache metadata to swap volume (matching setup logic)
    convert_result = run(f'lvconvert -y --cachepool {vg_name}/{cache_pool_name} --poolmetadata {vg_name}/{swap_name}')
    assert convert_result.succeeded

    # Activate swap volume (now containing cache metadata)
    swap_lv = LogicalVolume(name=swap_name, vg=vg_name)
    swap_lv.activate().assert_ok()
    udevadm_settle()

    # Use swap LV as cache metadata device
    cache_metadata_dev = f'/dev/{vg_name}/{swap_name}'

    return state.model_copy(update={'cache_metadata_dev': cache_metadata_dev})

cache_pool(cache_volumes)

Merge cache data and metadata volumes into a cache pool via lvconvert.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
@pytest.fixture
def cache_pool(cache_volumes: CacheVolumeState) -> CachePoolSplitState:
    """Merge cache data and metadata volumes into a cache pool via lvconvert."""
    state = cache_volumes
    vg_name = state.vg_name
    cache_data_name = state.cache_data_name
    cache_meta_name = state.cache_meta_name

    # Use lvm convert to create cache pool (matching setup logic)
    convert_result = run(
        f'lvconvert -y --type cache-pool --cachemode writeback '
        f'--poolmetadata {vg_name}/{cache_meta_name} {vg_name}/{cache_data_name}'
    )
    assert convert_result.succeeded

    return CachePoolSplitState(
        vg_name=vg_name,
        cache_origin_name=state.cache_origin_name,
        cache_pool_name=cache_data_name,  # Pool takes the name of data LV
    )

cache_split(cache_volume)

Split the cached volume to separate cache pool and origin.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
495
496
497
498
499
500
501
502
503
504
505
506
@pytest.fixture
def cache_split(cache_volume: CachePoolSplitState) -> CachePoolSplitState:
    """Split the cached volume to separate cache pool and origin."""
    state = cache_volume
    vg_name = state.vg_name
    cached_lv_name = state.cached_lv_name

    # Split cache (matching setup logic)
    split_result = run(f'lvconvert -y --splitcache {vg_name}/{cached_lv_name}')
    assert split_result.succeeded

    return state

cache_volume(cache_pool)

Convert origin LV to a cached volume backed by the cache pool.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
@pytest.fixture
def cache_volume(cache_pool: CachePoolSplitState) -> CachePoolSplitState:
    """Convert origin LV to a cached volume backed by the cache pool."""
    state = cache_pool
    vg_name = state.vg_name
    cache_origin_name = state.cache_origin_name
    cache_pool_name = state.cache_pool_name

    # Convert origin LV to cached LV
    convert_result = run(
        f'lvconvert -y --type cache --cachepool {vg_name}/{cache_pool_name} {vg_name}/{cache_origin_name}'
    )
    assert convert_result.succeeded

    # Create ext4 filesystem on cached volume (matching setup logic)
    assert mkfs(f'/dev/{vg_name}/{cache_origin_name}', 'ext4', force=True)

    return state.model_copy(update={'cached_lv_name': cache_origin_name})

cache_volumes(setup_loopdev_vg)

Create cache metadata, origin, and data logical volumes for cache pool testing.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
@pytest.fixture
def cache_volumes(setup_loopdev_vg: VolumeGroup) -> Generator[CacheVolumeState, None, None]:
    """Create cache metadata, origin, and data logical volumes for cache pool testing."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    cache_meta_name = 'cache_meta'
    cache_origin_name = 'cache_origin'
    cache_data_name = 'cache_data'

    # Create cache metadata LV (12MB as per original setup)
    cache_meta_lv = LogicalVolume(name=cache_meta_name, vg=vg_name)
    cache_meta_lv.create(size='12M').assert_ok()

    # Create cache origin LV (300MB as per original setup)
    cache_origin_lv = LogicalVolume(name=cache_origin_name, vg=vg_name)
    cache_origin_lv.create(size='300M').assert_ok()

    # Create cache data LV (100MB as per original setup)
    cache_data_lv = LogicalVolume(name=cache_data_name, vg=vg_name)
    cache_data_lv.create(size='100M').assert_ok()

    yield CacheVolumeState(
        vg_name=vg_name,
        cache_meta_name=cache_meta_name,
        cache_origin_name=cache_origin_name,
        cache_data_name=cache_data_name,
    )

    # Cleanup. Deliberately tolerant: downstream fixtures (cache_pool/cache_volume/
    # cache_split/cache_metadata_swap) convert these into cache-pool sub-LVs and swap
    # their metadata, so by teardown time they may no longer exist under their original
    # names (LVM absorbs/renames cache components) or may be metadata-inconsistent, same
    # as the thin-pool metadata-swap chain above. The underlying loop device gets wiped
    # regardless by setup_loopdev_vg's own teardown, so a failure here is not a real leak.
    for lv in (cache_data_lv, cache_origin_lv, cache_meta_lv):
        if lv.remove().failed:
            logger.warning(f'Failed to remove {lv.name} (expected if absorbed into a cache pool)')

metadata_backup(metadata_swap)

Dump thin metadata to XML and prepare repair/working binary files.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
@pytest.fixture
def metadata_backup(metadata_swap: ThinSwapState) -> Generator[ThinMetadataContext, None, None]:
    """Dump thin metadata to XML and prepare repair/working binary files."""
    state = metadata_swap
    metadata_dev = state.metadata_dev
    metadata_backup_path = Path('/var/tmp/metadata')
    metadata_repair_path = Path('/var/tmp/metadata_repair')

    # Create metadata backup using thin_dump (matching backup_metadata from main.fmf)
    backup_cmd = f'thin_dump --format xml --repair {metadata_dev} --output {metadata_backup_path}'
    backup_result = run(backup_cmd)
    assert backup_result.succeeded

    # Create proper metadata files for testing
    # 1. Create empty repair file with proper allocation (5MB should be enough)
    assert fallocate(metadata_repair_path, length='5M')

    # 2. Create a working metadata file that thin_repair can actually repair
    metadata_working_path = Path('/var/tmp/metadata_working')
    assert fallocate(metadata_working_path, length='5M')

    # 3. Populate the working metadata file with valid data from backup
    restore_working_cmd = f'thin_restore -i {metadata_backup_path} -o {metadata_working_path}'
    restore_working_result = run(restore_working_cmd)
    assert restore_working_result.succeeded, f'Failed to create working metadata: {restore_working_result.stderr}'

    yield ThinMetadataContext(
        vg_name=state.vg_name,
        pool_name=state.pool_name,
        swap_name=state.swap_name,
        metadata_dev=metadata_dev,
        metadata_backup_path=metadata_backup_path,
        metadata_repair_path=metadata_repair_path,
        metadata_working_path=metadata_working_path,
    )

    # Cleanup files
    run(f'rm -f {metadata_backup_path} {metadata_repair_path} {metadata_working_path}')

metadata_snapshot(thin_volumes_with_lifecycle)

Create a metadata snapshot via suspend/message/resume, then deactivate thin volumes.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@pytest.fixture
def metadata_snapshot(thin_volumes_with_lifecycle: ThinPoolState) -> Generator[ThinPoolState, None, None]:
    """Create a metadata snapshot via suspend/message/resume, then deactivate thin volumes."""
    state = thin_volumes_with_lifecycle

    udevadm_settle()

    # Create metadata snapshot while pool is still active
    pool_device = f'/dev/mapper/{state.vg_name}-{state.pool_name}-tpool'

    # Suspend -> message -> resume sequence (matching metadata_snapshot from setup.py)
    suspend_result = run(f'dmsetup suspend {pool_device}')
    assert suspend_result.succeeded

    message_result = run(f'dmsetup message {pool_device} 0 reserve_metadata_snap')
    assert message_result.succeeded

    resume_result = run(f'dmsetup resume {pool_device}')
    assert resume_result.succeeded

    # Now deactivate thin volumes (matching deactivate_thinvols from setup)
    for i in range(state.thin_count):
        thin_name = f'{state.thin_base_name}{i}'
        thin_lv = LogicalVolume(name=thin_name, vg=state.vg_name)
        thin_lv.deactivate()

    udevadm_settle()

    yield state

    # Release metadata snapshot
    run(f'dmsetup message {pool_device} 0 release_metadata_snap')

metadata_swap(metadata_snapshot, swap_volume)

Swap thin pool metadata into the swap volume via lvconvert.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
@pytest.fixture
def metadata_swap(metadata_snapshot: ThinPoolState, swap_volume: SwapVolumeInfo) -> ThinSwapState:
    """Swap thin pool metadata into the swap volume via lvconvert."""
    pool_state = metadata_snapshot
    swap_info = swap_volume

    # Ensure both fixtures reference the same VG
    assert pool_state.vg_name == swap_info.vg_name, 'Pool and swap must be in same VG'

    vg_name = pool_state.vg_name
    pool_name = pool_state.pool_name
    swap_name = swap_info.swap_name

    # Deactivate pool and swap (matching swap_metadata logic from setup.py)
    pool_lv = LogicalVolume(name=pool_name, vg=vg_name)
    pool_lv.deactivate()
    swap_lv = LogicalVolume(name=swap_name, vg=vg_name)
    swap_lv.deactivate()

    logger.info(run('lvs').stdout)
    udevadm_settle()

    # Swap metadata using lv_convert --poolmetadata (exact logic from setup.py)
    # This converts the swap LV to hold the thin pool's metadata
    convert_cmd = f'lvconvert -y --thinpool {vg_name}/{pool_name} --poolmetadata {vg_name}/{swap_name}'
    convert_result = run(convert_cmd)
    assert convert_result.succeeded

    # Activate swap volume (now containing metadata)
    swap_lv = LogicalVolume(name=swap_name, vg=vg_name)
    swap_lv.activate().assert_ok()

    # Use swap LV as metadata device (it now contains the metadata)
    metadata_dev = f'/dev/{vg_name}/{swap_name}'

    return ThinSwapState(
        vg_name=vg_name,
        pool_name=pool_name,
        swap_name=swap_name,
        metadata_dev=metadata_dev,
    )

restored_thin_pool(metadata_backup)

Restore thin pool metadata and swap it back so the pool is activatable.

Warning: Use only for tests needing an active pool (e.g. thin_trim). Most DMPD tests should use setup_thin_metadata_for_dmpd which preserves the intentionally inconsistent metadata state.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
@pytest.fixture
def restored_thin_pool(metadata_backup: ThinMetadataContext) -> Generator[ThinMetadataContext, None, None]:
    """Restore thin pool metadata and swap it back so the pool is activatable.

    Warning: Use only for tests needing an active pool (e.g. thin_trim). Most DMPD
    tests should use ``setup_thin_metadata_for_dmpd`` which preserves the intentionally
    inconsistent metadata state.
    """
    ctx = metadata_backup

    # Step 1: Use thin_restore to repair the metadata in the swap device
    logger.info(f'Restoring metadata to repair inconsistencies in {ctx.metadata_dev}')
    restore_cmd = f'thin_restore -i {ctx.metadata_backup_path} -o {ctx.metadata_dev}'
    restore_result = run(restore_cmd)
    assert restore_result.succeeded, f'Failed to restore metadata: {restore_result.stderr}'

    # Step 2: Deactivate both volumes before swapping metadata back
    pool_lv = LogicalVolume(name=ctx.pool_name, vg=ctx.vg_name)
    pool_lv.deactivate()  # Pool might already be deactivated
    swap_lv = LogicalVolume(name=ctx.swap_name, vg=ctx.vg_name)
    swap_lv.deactivate()
    udevadm_settle()

    # Step 3: "Swap back metadata" - restore the fixed metadata to the pool
    # This matches the "Swapping back metadata" step from python-stqe cleanup
    swap_back_cmd = (
        f'lvconvert -y --thinpool {ctx.vg_name}/{ctx.pool_name} --poolmetadata {ctx.vg_name}/{ctx.swap_name}'
    )
    swap_back_result = run(swap_back_cmd)
    assert swap_back_result.succeeded, f'Failed to swap metadata back to pool: {swap_back_result.stderr}'

    # Step 4: Reactivate the swap volume and verify device accessibility
    swap_lv = LogicalVolume(name=ctx.swap_name, vg=ctx.vg_name)
    swap_lv.activate().assert_ok()
    udevadm_settle()

    # Verify the metadata device exists and update the path if needed
    metadata_dev_path = f'/dev/{ctx.vg_name}/{ctx.swap_name}'
    check_dev = run(f'ls -la {metadata_dev_path}')
    if not check_dev.succeeded:
        # Try alternative device path
        metadata_dev_path = f'/dev/mapper/{ctx.vg_name}-{ctx.swap_name}'
        check_dev_alt = run(f'ls -la {metadata_dev_path}')
        assert check_dev_alt.succeeded, f'Swap device not accessible at {metadata_dev_path}'

    yield ctx.model_copy(update={'metadata_dev': metadata_dev_path})

    # Leave pool in deactivated state for cleanup
    pool_lv = LogicalVolume(name=ctx.pool_name, vg=ctx.vg_name)
    pool_lv.deactivate()  # Ignore errors

setup_cache_metadata_for_dmpd(_install_dmpd, cache_metadata_backup)

Provide cache metadata for DMPD cache tool testing.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
569
570
571
572
573
@pytest.fixture
def setup_cache_metadata_for_dmpd(_install_dmpd: None, cache_metadata_backup: CacheTestContext) -> CacheTestContext:
    """Provide cache metadata for DMPD cache tool testing."""
    # Use cache_metadata_backup which provides working cache metadata for DMPD testing
    return cache_metadata_backup

setup_thin_metadata_for_dmpd(_install_dmpd, metadata_backup)

Provide intentionally inconsistent thin-pool metadata for DMPD tool testing.

The metadata swap leaves a transaction_id mismatch so DMPD tools can exercise their detection, analysis, and repair paths.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
397
398
399
400
401
402
403
404
405
@pytest.fixture
def setup_thin_metadata_for_dmpd(_install_dmpd: None, metadata_backup: ThinMetadataContext) -> ThinMetadataContext:
    """Provide intentionally inconsistent thin-pool metadata for DMPD tool testing.

    The metadata swap leaves a transaction_id mismatch so DMPD tools can exercise
    their detection, analysis, and repair paths.
    """
    # Use metadata_backup which preserves the "broken" metadata state for DMPD testing
    return metadata_backup

swap_volume(setup_loopdev_vg)

Create a 75MB swap logical volume for metadata swapping.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
@pytest.fixture
def swap_volume(setup_loopdev_vg: VolumeGroup) -> Generator[SwapVolumeInfo, None, None]:
    """Create a 75MB swap logical volume for metadata swapping."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    swap_name = 'swapvol'

    # Create swap LV (75MB as per original setup)
    swap_lv = LogicalVolume(name=swap_name, vg=vg_name)
    swap_lv.create(size='75M').assert_ok()

    yield SwapVolumeInfo(vg_name=vg_name, swap_name=swap_name, swap_lv=swap_lv)

    # Cleanup
    swap_lv.remove().assert_ok()

thin_volumes_with_lifecycle(setup_loopdev_vg)

Create a 3GB thin pool with 10 thin volumes and generate metadata activity.

Each thin volume gets a filesystem created, mounted, written to, unmounted, and deactivated to produce realistic metadata for DMPD testing.

Source code in sts_libs/src/sts/fixtures/dmpd_fixtures.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
@pytest.fixture
def thin_volumes_with_lifecycle(setup_loopdev_vg: VolumeGroup) -> Generator[ThinPoolState, None, None]:
    """Create a 3GB thin pool with 10 thin volumes and generate metadata activity.

    Each thin volume gets a filesystem created, mounted, written to, unmounted,
    and deactivated to produce realistic metadata for DMPD testing.
    """
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    pool_name = 'thinpool'

    # Create thin pool (3GB to accommodate 10x300MB thin volumes with filesystem support)
    pool = ThinPool.create_thin_pool(pool_name, vg_name, size='3G')

    thin_base_name = 'thinvol'
    run(f'vgchange {vg_name} --setautoactivation n')
    # Create 10 thin volumes of 300MB each (minimum size for filesystem support)
    thin_lvs: list[LogicalVolume] = []
    for i in range(10):
        thin_name = f'{thin_base_name}{i}'
        thin_lv = LogicalVolume(name=thin_name, vg=vg_name)
        thin_lv.create(type='thin', thinpool=pool_name, virtualsize='300M').assert_ok()
        thin_lvs.append(thin_lv)
    for thin_lv in thin_lvs:
        # Create filesystem and mount/unmount to generate metadata activity
        # This matches the mount_lv/umount_lv logic from setup.py
        thin_path = f'/dev/{vg_name}/{thin_lv.name}'
        mount_point = f'/mnt/{thin_lv.name}'

        mnt_dir = Directory(path=Path(mount_point), create=True)
        assert mkfs(device=thin_path, fs_type='xfs')
        assert mount(device=thin_path, mountpoint=mount_point)
        random_data_file = f'{mount_point}/random_data.txt'
        assert write_data(target=random_data_file, source='/dev/urandom', count=10, bs='1M'), (
            f'Failed to write random data to {random_data_file}'
        )
        logger.info(f'Wrote 10MB of random data to {random_data_file}')
        udevadm_settle()
        assert umount(mountpoint=mount_point)
        mnt_dir.remove_dir()

        # Deactivate thin LV with verification
        thin_lv.deactivate()

    yield ThinPoolState(
        vg_name=vg_name,
        pool_name=pool_name,
        thin_count=10,
        thin_base_name=thin_base_name,
    )

    # Cleanup thin volumes and pool. Deliberately tolerant: consumers of this fixture
    # (the metadata-swap chain) leave the pool's metadata intentionally inconsistent
    # (see setup_thin_metadata_for_dmpd), so a normal lvremove can genuinely fail here
    # (e.g. "transaction_id is 0, while expected N") even though nothing is actually
    # wrong with the fixture. The underlying loop device gets wiped regardless by
    # setup_loopdev_vg's own teardown, so a failure here is not a real leak.
    try:
        pool.remove_with_thin_volumes()
    except STSError as e:
        logger.warning(f'Failed to remove pool {pool_name} with thin volumes (expected if metadata was swapped): {e}')

Loop Devices

Provided by Common Fixtures — see loop_devices.

Multipath

sts.fixtures.multipath_fixtures

Multipath test fixtures for service management and device creation.

MultipathActivePathsContext pydantic-model

Bases: StsBaseModel

State for get_multipath_active_paths fixture.

Show JSON schema:
{
  "$defs": {
    "BlockdevInfo": {
      "description": "Parsed ``blockdev --report`` output.",
      "properties": {
        "ro": {
          "default": false,
          "title": "Ro",
          "type": "boolean"
        },
        "ra": {
          "default": 0,
          "title": "Ra",
          "type": "integer"
        },
        "log-sec": {
          "default": 0,
          "title": "Log-Sec",
          "type": "integer"
        },
        "phy-sec": {
          "default": 0,
          "title": "Phy-Sec",
          "type": "integer"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        },
        "size": {
          "default": 0,
          "title": "Size",
          "type": "integer"
        }
      },
      "title": "BlockdevInfo",
      "type": "object"
    },
    "DmDevice": {
      "additionalProperties": false,
      "description": "Base class for all Device Mapper devices.\n\nBefore ``create()``, the device is just a target configuration\n(start, size_sectors, args). After ``create()``, it becomes a full\nblock device with dm_name, path, table, and all BlockDevice functionality.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "blockdev_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/BlockdevInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "lsblk_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/LsblkInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        },
        "size_sectors": {
          "default": 0,
          "title": "Size Sectors",
          "type": "integer"
        },
        "args": {
          "default": "",
          "title": "Args",
          "type": "string"
        },
        "dm_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Dm Name"
        },
        "target_type": {
          "default": "",
          "title": "Target Type",
          "type": "string"
        },
        "table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Table"
        },
        "is_created": {
          "default": false,
          "title": "Is Created",
          "type": "boolean"
        }
      },
      "title": "DmDevice",
      "type": "object"
    },
    "LsblkInfo": {
      "description": "Parsed ``lsblk -JOb`` output.",
      "properties": {
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "rm": {
          "default": false,
          "title": "Rm",
          "type": "boolean"
        },
        "hctl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Hctl"
        },
        "state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "State"
        },
        "pttype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pttype"
        },
        "wwn": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Wwn"
        },
        "fstype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Fstype"
        },
        "mountpoint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mountpoint"
        },
        "type": {
          "default": "disk",
          "title": "Type",
          "type": "string"
        },
        "tran": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Tran"
        },
        "maj:min": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Maj:Min"
        },
        "pkname": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pkname"
        },
        "start": {
          "default": 0,
          "title": "Start",
          "type": "integer"
        }
      },
      "title": "LsblkInfo",
      "type": "object"
    },
    "MultipathDevice": {
      "additionalProperties": false,
      "description": "Multipath device managed by multipathd.\n\nFor creating DM multipath targets directly (without multipathd), see\n`sts.dm.multipath.MultipathTarget`.\n\nThe ``dm`` attribute provides access to a `DmDevice` for low-level\nDM operations (table, size). ``size`` is derived from the DM device.\n\nExample:\n    ```python\n    device = MultipathDevice().discover()  # Uses first available device\n    device = MultipathDevice(name='mpatha').discover()\n    device.dm.table  # Access DM table\n    ```",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "blockdev_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/BlockdevInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "lsblk_info": {
          "anyOf": [
            {
              "$ref": "#/$defs/LsblkInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "dm_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Dm Name"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "wwid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Wwid"
        },
        "vendor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vendor"
        },
        "n_paths": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "N Paths"
        },
        "size_str": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size Str"
        },
        "features": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Features"
        },
        "hwhandler": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Hwhandler"
        },
        "failback": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Failback"
        },
        "dm_st": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Dm St"
        },
        "path_groups": {
          "items": {
            "$ref": "#/$defs/PathGroup"
          },
          "title": "Path Groups",
          "type": "array"
        },
        "dm": {
          "anyOf": [
            {
              "$ref": "#/$defs/DmDevice"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "MultipathDevice",
      "type": "object"
    },
    "PathGroup": {
      "description": "A path group within a multipath device's 'path_groups' report.\n\nThe inner `paths` entries keep their raw dict shape (`chk_st`, `dev`,\n`dm_st`, etc.) since the exact keys reported by multipathd vary by version.",
      "properties": {
        "group": {
          "default": 0,
          "title": "Group",
          "type": "integer"
        },
        "status": {
          "default": "",
          "title": "Status",
          "type": "string"
        },
        "paths": {
          "items": {
            "additionalProperties": true,
            "type": "object"
          },
          "title": "Paths",
          "type": "array"
        }
      },
      "title": "PathGroup",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for get_multipath_active_paths fixture.",
  "properties": {
    "device": {
      "$ref": "#/$defs/MultipathDevice"
    },
    "active_paths": {
      "items": {
        "additionalProperties": true,
        "type": "object"
      },
      "title": "Active Paths",
      "type": "array"
    }
  },
  "required": [
    "device"
  ],
  "title": "MultipathActivePathsContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
106
107
108
109
110
class MultipathActivePathsContext(StsBaseModel):
    """State for get_multipath_active_paths fixture."""

    device: MultipathDevice
    active_paths: list[dict[str, Any]] = Field(default_factory=list)

get_multipath_active_paths()

Yield the first multipath device that has active paths, or skip.

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@pytest.fixture(scope='class')
def get_multipath_active_paths() -> Generator[MultipathActivePathsContext, None, None]:
    """Yield the first multipath device that has active paths, or skip."""
    mpath_devices = MultipathDevice.get_all()
    if not mpath_devices:
        pytest.skip('No multipath devices found')

    for device in mpath_devices:
        active_paths: list[dict[str, Any]] = [path for path in device.paths if path.get('dm_st') == 'active']
        if active_paths:
            device_path = Path(device.path) if device.path else None
            if device_path and device_path.exists():
                yield MultipathActivePathsContext(device=device, active_paths=active_paths)
                break
    else:
        pytest.skip('No multipath device with active paths found')

multipath_device(request, with_target_service)

Create a multipath device via LIO loopback targets with fileio backstore.

Defaults to 4 paths, 100M. Parametrize with [{'num_paths': 2, 'size': '500M'}].

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
@pytest.fixture(scope='class')
def multipath_device(
    request: pytest.FixtureRequest,
    with_target_service: None,
) -> Generator[MultipathDevice, None, None]:
    """Create a multipath device via LIO loopback targets with fileio backstore.

    Defaults to 4 paths, 100M. Parametrize with ``[{'num_paths': 2, 'size': '500M'}]``.
    """
    _ = with_target_service
    # Parse configuration from parametrize
    raw_param = getattr(request, 'param', {})
    if isinstance(raw_param, dict):
        params = cast('dict[str, Any]', raw_param)
        num_paths: int = params.get('num_paths', DEFAULT_NUM_PATHS)
        size: str = params.get('size', DEFAULT_DEVICE_SIZE)
    else:
        num_paths = DEFAULT_NUM_PATHS
        size = DEFAULT_DEVICE_SIZE

    # Setup multipath service
    mpath_service = MultipathService()
    service_running = mpath_service.is_running()
    if not service_running and not mpath_service.start():
        pytest.skip('Failed to start multipath service')

    # Create multipath device using loopback
    yield from _create_multipath_loopback(num_paths, size)

    # Stop multipath service if it was not running originally
    if not service_running:
        mpath_service.stop()

with_multipath_disabled()

Fixture to temporarily disable multipath service.

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
@pytest.fixture(scope='class')
def with_multipath_disabled() -> Generator[None, None, None]:
    """Fixture to temporarily disable multipath service."""
    ensure_installed('device-mapper-multipath')
    mpath_service = MultipathService()
    was_running = mpath_service.is_running()

    if was_running:
        logger.info('Temporarily disabling multipath service...')
        mpath_service.stop()

        # Flush devices after service is stopped
        if MultipathDevice.get_all():
            logger.warning('Flushing existing multipath devices...')
            if not mpath_service.flush():
                pytest.skip('Failed to flush multipath devices')

    yield

    if was_running:
        logger.info('Restoring multipath service...')
        mpath_service.start()

with_multipath_enabled()

Fixture to temporarily enable multipath service.

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@pytest.fixture(scope='class')
def with_multipath_enabled() -> Generator[None, None, None]:
    """Fixture to temporarily enable multipath service."""
    ensure_installed('device-mapper-multipath')
    mpath_service = MultipathService()
    was_stopped = not mpath_service.is_running()

    if was_stopped:
        logger.info('Starting multipath service...')
        if not mpath_service.start():
            pytest.skip('Failed to start multipath service')

    yield

    # Cleanup only if we started the service
    if was_stopped:
        logger.info('Stopping multipath service...')
        mpath_service.stop()

with_target_service()

Ensure the target service is running, restoring original state on teardown.

Source code in sts_libs/src/sts/fixtures/multipath_fixtures.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@pytest.fixture(scope='class')
def with_target_service() -> Generator[None, None, None]:
    """Ensure the target service is running, restoring original state on teardown."""
    system = SystemManager()
    service_name = 'target'

    # Ensure targetcli package is installed
    if not system.package_manager.install('targetcli'):
        pytest.skip('Failed to install targetcli package')

    was_stopped = not system.is_service_running(service_name)

    if was_stopped:
        logger.info('Starting target service...')
        if not system.service_start(service_name):
            pytest.skip('Failed to start target service')

    yield

    # Cleanup only if we started the service
    if was_stopped:
        logger.info('Stopping target service...')
        system.service_stop(service_name)

Logical Volume Management

sts.fixtures.lvm_fixtures

LVM test fixtures.

Note

dmpd fixtures live in dmpd_fixtures.py and VDO fixtures in vdo_fixtures.py.

MountedThinAndRegularVolumeContext pydantic-model

Bases: StsBaseModel

ThinAndRegularVolumeContext plus both volumes formatted and mounted.

Show JSON schema:
{
  "$defs": {
    "Directory": {
      "additionalProperties": false,
      "description": "Directory wrapper with optional auto-creation.\n\nAttributes:\n    create: If True, create the directory (with parents) on construction.",
      "properties": {
        "path": {
          "format": "path",
          "title": "Path",
          "type": "string"
        },
        "create": {
          "default": false,
          "title": "Create",
          "type": "boolean"
        },
        "mode": {
          "default": 493,
          "title": "Mode",
          "type": "integer"
        }
      },
      "title": "Directory",
      "type": "object"
    },
    "LVReport": {
      "description": "Parsed LV data from 'lvs -o lv_all,seg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one logical volume's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').\nAll fields match lvs JSON key names and retain str | None typing,\nexcept for a handful of numeric fields (see below) that are coerced.",
      "properties": {
        "lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Uuid"
        },
        "lv_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Name"
        },
        "lv_full_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Name"
        },
        "lv_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Path"
        },
        "lv_dm_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Dm Path"
        },
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "lv_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Size"
        },
        "lv_metadata_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Metadata Size"
        },
        "seg_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Count"
        },
        "lv_layout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Layout"
        },
        "lv_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Role"
        },
        "lv_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Attr"
        },
        "lv_active": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active"
        },
        "lv_active_locally": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Locally"
        },
        "lv_active_remotely": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Remotely"
        },
        "lv_active_exclusively": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Exclusively"
        },
        "lv_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Permissions"
        },
        "lv_suspended": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Suspended"
        },
        "lv_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Major"
        },
        "lv_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Minor"
        },
        "lv_kernel_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Major"
        },
        "lv_kernel_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Minor"
        },
        "lv_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Read Ahead"
        },
        "lv_kernel_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Read Ahead"
        },
        "pool_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv"
        },
        "pool_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv Uuid"
        },
        "data_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv"
        },
        "data_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv Uuid"
        },
        "metadata_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv"
        },
        "metadata_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv Uuid"
        },
        "data_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Percent"
        },
        "metadata_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Percent"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "origin_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Uuid"
        },
        "origin_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Size"
        },
        "snap_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Percent"
        },
        "raid_mismatch_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Mismatch Count"
        },
        "raid_sync_action": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Sync Action"
        },
        "raid_write_behind": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Write Behind"
        },
        "raid_min_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Min Recovery Rate"
        },
        "raid_max_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Max Recovery Rate"
        },
        "cache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Total Blocks"
        },
        "cache_used_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Used Blocks"
        },
        "cache_dirty_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Dirty Blocks"
        },
        "cache_read_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Hits"
        },
        "cache_read_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Misses"
        },
        "cache_write_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Hits"
        },
        "cache_write_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Misses"
        },
        "kernel_cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Settings"
        },
        "kernel_cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Policy"
        },
        "vdo_operating_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Operating Mode"
        },
        "vdo_compression_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression State"
        },
        "vdo_index_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index State"
        },
        "vdo_used_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Used Size"
        },
        "vdo_saving_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Saving Percent"
        },
        "writecache_block_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Block Size"
        },
        "writecache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Total Blocks"
        },
        "writecache_free_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Free Blocks"
        },
        "writecache_writeback_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Writeback Blocks"
        },
        "writecache_error": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Error"
        },
        "lv_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Policy"
        },
        "lv_allocation_locked": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Locked"
        },
        "lv_autoactivation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Autoactivation"
        },
        "lv_when_full": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv When Full"
        },
        "lv_skip_activation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Skip Activation"
        },
        "lv_fixed_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Fixed Minor"
        },
        "lv_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time"
        },
        "lv_time_removed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time Removed"
        },
        "lv_host": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Host"
        },
        "lv_health_status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Health Status"
        },
        "lv_check_needed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Check Needed"
        },
        "lv_merge_failed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merge Failed"
        },
        "lv_snapshot_invalid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Snapshot Invalid"
        },
        "lv_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Tags"
        },
        "lv_profile": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Profile"
        },
        "lv_lockargs": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Lockargs"
        },
        "lv_modules": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Modules"
        },
        "lv_historical": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Historical"
        },
        "kernel_discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Discards"
        },
        "copy_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Copy Percent"
        },
        "sync_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Sync Percent"
        },
        "lv_live_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Live Table"
        },
        "lv_inactive_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Inactive Table"
        },
        "lv_device_open": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Device Open"
        },
        "lv_parent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Parent"
        },
        "lv_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Ancestors"
        },
        "lv_full_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Ancestors"
        },
        "lv_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Descendants"
        },
        "lv_full_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Descendants"
        },
        "lv_converting": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Converting"
        },
        "lv_merging": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merging"
        },
        "move_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv"
        },
        "move_pv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv Uuid"
        },
        "convert_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv"
        },
        "convert_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv Uuid"
        },
        "mirror_log": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log"
        },
        "mirror_log_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log Uuid"
        },
        "lv_initial_image_sync": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Initial Image Sync"
        },
        "lv_image_synced": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Image Synced"
        },
        "raidintegritymode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegritymode"
        },
        "raidintegrityblocksize": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegrityblocksize"
        },
        "integritymismatches": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integritymismatches"
        },
        "kernel_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Metadata Format"
        },
        "segtype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Segtype"
        },
        "stripes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripes"
        },
        "data_stripes": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Stripes"
        },
        "stripe_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripe Size"
        },
        "region_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Region Size"
        },
        "chunk_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Chunk Size"
        },
        "seg_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start"
        },
        "seg_start_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start Pe"
        },
        "seg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size"
        },
        "seg_size_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size Pe"
        },
        "seg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Tags"
        },
        "seg_pe_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Pe Ranges"
        },
        "seg_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Le Ranges"
        },
        "seg_metadata_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Metadata Le Ranges"
        },
        "devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Devices"
        },
        "metadata_devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Devices"
        },
        "seg_monitor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Monitor"
        },
        "reshape_len": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len"
        },
        "reshape_len_le": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len Le"
        },
        "data_copies": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Copies"
        },
        "data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Offset"
        },
        "new_data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "New Data Offset"
        },
        "parity_chunks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Parity Chunks"
        },
        "thin_count": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Count"
        },
        "discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Discards"
        },
        "cache_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Metadata Format"
        },
        "cache_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Mode"
        },
        "zero": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Zero"
        },
        "transaction_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Transaction Id"
        },
        "thin_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Id"
        },
        "cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Policy"
        },
        "cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Settings"
        },
        "integrity_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integrity Settings"
        },
        "vdo_compression": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression"
        },
        "vdo_deduplication": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Deduplication"
        },
        "vdo_minimum_io_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Minimum Io Size"
        },
        "vdo_block_map_cache_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Cache Size"
        },
        "vdo_block_map_era_length": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Era Length"
        },
        "vdo_use_sparse_index": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Sparse Index"
        },
        "vdo_index_memory_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index Memory Size"
        },
        "vdo_slab_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Slab Size"
        },
        "vdo_ack_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Ack Threads"
        },
        "vdo_bio_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Threads"
        },
        "vdo_bio_rotation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Rotation"
        },
        "vdo_cpu_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Cpu Threads"
        },
        "vdo_hash_zone_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Hash Zone Threads"
        },
        "vdo_logical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Logical Threads"
        },
        "vdo_physical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Physical Threads"
        },
        "vdo_max_discard": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Max Discard"
        },
        "vdo_header_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Header Size"
        },
        "vdo_use_metadata_hints": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Metadata Hints"
        },
        "vdo_write_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Write Policy"
        }
      },
      "title": "LVReport",
      "type": "object"
    },
    "LogicalVolume": {
      "additionalProperties": false,
      "description": "Logical Volume device.\n\nExample:\n    ```python\n    lv = LogicalVolume(name='lv0', vg='vg0')\n    lv.create(size='100M').assert_ok()\n    print(lv.report.lv_size)\n    ```",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "LogicalVolume",
      "type": "object"
    },
    "ThinAndRegularVolumeContext": {
      "additionalProperties": false,
      "description": "State for a thin volume and a regular volume created side-by-side (perf comparison).",
      "properties": {
        "vg": {
          "$ref": "#/$defs/VolumeGroup"
        },
        "pool": {
          "$ref": "#/$defs/ThinPool"
        },
        "thin_device": {
          "title": "Thin Device",
          "type": "string"
        },
        "regular_lv": {
          "$ref": "#/$defs/LogicalVolume"
        },
        "regular_device": {
          "title": "Regular Device",
          "type": "string"
        }
      },
      "required": [
        "vg",
        "pool",
        "thin_device",
        "regular_lv",
        "regular_device"
      ],
      "title": "ThinAndRegularVolumeContext",
      "type": "object"
    },
    "ThinPool": {
      "additionalProperties": false,
      "description": "Thin Pool logical volume.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        },
        "thin_volumes": {
          "items": {
            "$ref": "#/$defs/LogicalVolume"
          },
          "title": "Thin Volumes",
          "type": "array"
        },
        "tdata": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "tmeta": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "ThinPool",
      "type": "object"
    },
    "VGReport": {
      "description": "Parsed VG data from 'vgs -o vg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one volume group's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').",
      "properties": {
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "vg_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Uuid"
        },
        "vg_fmt": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Fmt"
        },
        "vg_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Attr"
        },
        "vg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Size"
        },
        "vg_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free"
        },
        "vg_extent_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Size"
        },
        "vg_extent_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Count"
        },
        "vg_free_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free Count"
        },
        "pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pv Count"
        },
        "lv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Count"
        },
        "snap_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Count"
        },
        "vg_seqno": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Seqno"
        },
        "vg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Tags"
        },
        "vg_mda_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Count"
        },
        "vg_mda_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Free"
        },
        "max_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Lv"
        },
        "max_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Pv"
        },
        "vg_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Permissions"
        },
        "vg_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Allocation Policy"
        },
        "vg_clustered": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Clustered"
        },
        "vg_exported": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Exported"
        },
        "vg_partial": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Partial"
        },
        "vg_missing_pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Missing Pv Count"
        }
      },
      "title": "VGReport",
      "type": "object"
    },
    "VolumeGroup": {
      "additionalProperties": false,
      "description": "Volume Group device.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "pvs": {
          "items": {
            "type": "string"
          },
          "title": "Pvs",
          "type": "array"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/VGReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "VolumeGroup",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "ThinAndRegularVolumeContext plus both volumes formatted and mounted.",
  "properties": {
    "base": {
      "$ref": "#/$defs/ThinAndRegularVolumeContext"
    },
    "thin_lv_mnt": {
      "$ref": "#/$defs/Directory"
    },
    "regular_lv_mnt": {
      "$ref": "#/$defs/Directory"
    },
    "filesystem": {
      "title": "Filesystem",
      "type": "string"
    }
  },
  "required": [
    "base",
    "thin_lv_mnt",
    "regular_lv_mnt",
    "filesystem"
  ],
  "title": "MountedThinAndRegularVolumeContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
67
68
69
70
71
72
73
class MountedThinAndRegularVolumeContext(StsBaseModel):
    """ThinAndRegularVolumeContext plus both volumes formatted and mounted."""

    base: ThinAndRegularVolumeContext
    thin_lv_mnt: Directory
    regular_lv_mnt: Directory
    filesystem: str

TempMountContext pydantic-model

Bases: StsBaseModel

State for temp_mount_fixture.

Show JSON schema:
{
  "$defs": {
    "Directory": {
      "additionalProperties": false,
      "description": "Directory wrapper with optional auto-creation.\n\nAttributes:\n    create: If True, create the directory (with parents) on construction.",
      "properties": {
        "path": {
          "format": "path",
          "title": "Path",
          "type": "string"
        },
        "create": {
          "default": false,
          "title": "Create",
          "type": "boolean"
        },
        "mode": {
          "default": 493,
          "title": "Mode",
          "type": "integer"
        }
      },
      "title": "Directory",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for temp_mount_fixture.",
  "properties": {
    "mount_point": {
      "format": "path",
      "title": "Mount Point",
      "type": "string"
    },
    "mount_dir": {
      "$ref": "#/$defs/Directory"
    },
    "temp_files": {
      "items": {
        "format": "path",
        "type": "string"
      },
      "title": "Temp Files",
      "type": "array"
    }
  },
  "required": [
    "mount_point",
    "mount_dir"
  ],
  "title": "TempMountContext",
  "type": "object"
}

Fields:

  • mount_point (Path)
  • mount_dir (Directory)
  • temp_files (list[Path])
Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
647
648
649
650
651
652
class TempMountContext(StsBaseModel):
    """State for temp_mount_fixture."""

    mount_point: Path
    mount_dir: Directory
    temp_files: list[Path] = Field(default_factory=list)

ThinAndRegularVolumeContext pydantic-model

Bases: StsBaseModel

State for a thin volume and a regular volume created side-by-side (perf comparison).

Show JSON schema:
{
  "$defs": {
    "LVReport": {
      "description": "Parsed LV data from 'lvs -o lv_all,seg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one logical volume's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').\nAll fields match lvs JSON key names and retain str | None typing,\nexcept for a handful of numeric fields (see below) that are coerced.",
      "properties": {
        "lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Uuid"
        },
        "lv_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Name"
        },
        "lv_full_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Name"
        },
        "lv_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Path"
        },
        "lv_dm_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Dm Path"
        },
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "lv_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Size"
        },
        "lv_metadata_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Metadata Size"
        },
        "seg_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Count"
        },
        "lv_layout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Layout"
        },
        "lv_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Role"
        },
        "lv_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Attr"
        },
        "lv_active": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active"
        },
        "lv_active_locally": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Locally"
        },
        "lv_active_remotely": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Remotely"
        },
        "lv_active_exclusively": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Exclusively"
        },
        "lv_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Permissions"
        },
        "lv_suspended": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Suspended"
        },
        "lv_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Major"
        },
        "lv_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Minor"
        },
        "lv_kernel_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Major"
        },
        "lv_kernel_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Minor"
        },
        "lv_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Read Ahead"
        },
        "lv_kernel_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Read Ahead"
        },
        "pool_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv"
        },
        "pool_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv Uuid"
        },
        "data_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv"
        },
        "data_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv Uuid"
        },
        "metadata_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv"
        },
        "metadata_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv Uuid"
        },
        "data_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Percent"
        },
        "metadata_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Percent"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "origin_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Uuid"
        },
        "origin_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Size"
        },
        "snap_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Percent"
        },
        "raid_mismatch_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Mismatch Count"
        },
        "raid_sync_action": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Sync Action"
        },
        "raid_write_behind": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Write Behind"
        },
        "raid_min_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Min Recovery Rate"
        },
        "raid_max_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Max Recovery Rate"
        },
        "cache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Total Blocks"
        },
        "cache_used_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Used Blocks"
        },
        "cache_dirty_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Dirty Blocks"
        },
        "cache_read_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Hits"
        },
        "cache_read_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Misses"
        },
        "cache_write_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Hits"
        },
        "cache_write_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Misses"
        },
        "kernel_cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Settings"
        },
        "kernel_cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Policy"
        },
        "vdo_operating_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Operating Mode"
        },
        "vdo_compression_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression State"
        },
        "vdo_index_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index State"
        },
        "vdo_used_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Used Size"
        },
        "vdo_saving_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Saving Percent"
        },
        "writecache_block_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Block Size"
        },
        "writecache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Total Blocks"
        },
        "writecache_free_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Free Blocks"
        },
        "writecache_writeback_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Writeback Blocks"
        },
        "writecache_error": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Error"
        },
        "lv_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Policy"
        },
        "lv_allocation_locked": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Locked"
        },
        "lv_autoactivation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Autoactivation"
        },
        "lv_when_full": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv When Full"
        },
        "lv_skip_activation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Skip Activation"
        },
        "lv_fixed_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Fixed Minor"
        },
        "lv_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time"
        },
        "lv_time_removed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time Removed"
        },
        "lv_host": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Host"
        },
        "lv_health_status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Health Status"
        },
        "lv_check_needed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Check Needed"
        },
        "lv_merge_failed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merge Failed"
        },
        "lv_snapshot_invalid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Snapshot Invalid"
        },
        "lv_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Tags"
        },
        "lv_profile": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Profile"
        },
        "lv_lockargs": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Lockargs"
        },
        "lv_modules": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Modules"
        },
        "lv_historical": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Historical"
        },
        "kernel_discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Discards"
        },
        "copy_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Copy Percent"
        },
        "sync_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Sync Percent"
        },
        "lv_live_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Live Table"
        },
        "lv_inactive_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Inactive Table"
        },
        "lv_device_open": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Device Open"
        },
        "lv_parent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Parent"
        },
        "lv_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Ancestors"
        },
        "lv_full_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Ancestors"
        },
        "lv_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Descendants"
        },
        "lv_full_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Descendants"
        },
        "lv_converting": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Converting"
        },
        "lv_merging": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merging"
        },
        "move_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv"
        },
        "move_pv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv Uuid"
        },
        "convert_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv"
        },
        "convert_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv Uuid"
        },
        "mirror_log": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log"
        },
        "mirror_log_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log Uuid"
        },
        "lv_initial_image_sync": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Initial Image Sync"
        },
        "lv_image_synced": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Image Synced"
        },
        "raidintegritymode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegritymode"
        },
        "raidintegrityblocksize": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegrityblocksize"
        },
        "integritymismatches": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integritymismatches"
        },
        "kernel_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Metadata Format"
        },
        "segtype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Segtype"
        },
        "stripes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripes"
        },
        "data_stripes": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Stripes"
        },
        "stripe_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripe Size"
        },
        "region_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Region Size"
        },
        "chunk_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Chunk Size"
        },
        "seg_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start"
        },
        "seg_start_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start Pe"
        },
        "seg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size"
        },
        "seg_size_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size Pe"
        },
        "seg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Tags"
        },
        "seg_pe_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Pe Ranges"
        },
        "seg_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Le Ranges"
        },
        "seg_metadata_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Metadata Le Ranges"
        },
        "devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Devices"
        },
        "metadata_devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Devices"
        },
        "seg_monitor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Monitor"
        },
        "reshape_len": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len"
        },
        "reshape_len_le": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len Le"
        },
        "data_copies": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Copies"
        },
        "data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Offset"
        },
        "new_data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "New Data Offset"
        },
        "parity_chunks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Parity Chunks"
        },
        "thin_count": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Count"
        },
        "discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Discards"
        },
        "cache_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Metadata Format"
        },
        "cache_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Mode"
        },
        "zero": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Zero"
        },
        "transaction_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Transaction Id"
        },
        "thin_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Id"
        },
        "cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Policy"
        },
        "cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Settings"
        },
        "integrity_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integrity Settings"
        },
        "vdo_compression": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression"
        },
        "vdo_deduplication": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Deduplication"
        },
        "vdo_minimum_io_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Minimum Io Size"
        },
        "vdo_block_map_cache_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Cache Size"
        },
        "vdo_block_map_era_length": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Era Length"
        },
        "vdo_use_sparse_index": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Sparse Index"
        },
        "vdo_index_memory_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index Memory Size"
        },
        "vdo_slab_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Slab Size"
        },
        "vdo_ack_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Ack Threads"
        },
        "vdo_bio_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Threads"
        },
        "vdo_bio_rotation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Rotation"
        },
        "vdo_cpu_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Cpu Threads"
        },
        "vdo_hash_zone_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Hash Zone Threads"
        },
        "vdo_logical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Logical Threads"
        },
        "vdo_physical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Physical Threads"
        },
        "vdo_max_discard": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Max Discard"
        },
        "vdo_header_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Header Size"
        },
        "vdo_use_metadata_hints": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Metadata Hints"
        },
        "vdo_write_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Write Policy"
        }
      },
      "title": "LVReport",
      "type": "object"
    },
    "LogicalVolume": {
      "additionalProperties": false,
      "description": "Logical Volume device.\n\nExample:\n    ```python\n    lv = LogicalVolume(name='lv0', vg='vg0')\n    lv.create(size='100M').assert_ok()\n    print(lv.report.lv_size)\n    ```",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "LogicalVolume",
      "type": "object"
    },
    "ThinPool": {
      "additionalProperties": false,
      "description": "Thin Pool logical volume.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        },
        "thin_volumes": {
          "items": {
            "$ref": "#/$defs/LogicalVolume"
          },
          "title": "Thin Volumes",
          "type": "array"
        },
        "tdata": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "tmeta": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "ThinPool",
      "type": "object"
    },
    "VGReport": {
      "description": "Parsed VG data from 'vgs -o vg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one volume group's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').",
      "properties": {
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "vg_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Uuid"
        },
        "vg_fmt": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Fmt"
        },
        "vg_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Attr"
        },
        "vg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Size"
        },
        "vg_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free"
        },
        "vg_extent_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Size"
        },
        "vg_extent_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Count"
        },
        "vg_free_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free Count"
        },
        "pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pv Count"
        },
        "lv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Count"
        },
        "snap_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Count"
        },
        "vg_seqno": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Seqno"
        },
        "vg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Tags"
        },
        "vg_mda_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Count"
        },
        "vg_mda_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Free"
        },
        "max_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Lv"
        },
        "max_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Pv"
        },
        "vg_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Permissions"
        },
        "vg_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Allocation Policy"
        },
        "vg_clustered": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Clustered"
        },
        "vg_exported": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Exported"
        },
        "vg_partial": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Partial"
        },
        "vg_missing_pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Missing Pv Count"
        }
      },
      "title": "VGReport",
      "type": "object"
    },
    "VolumeGroup": {
      "additionalProperties": false,
      "description": "Volume Group device.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "pvs": {
          "items": {
            "type": "string"
          },
          "title": "Pvs",
          "type": "array"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/VGReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "VolumeGroup",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a thin volume and a regular volume created side-by-side (perf comparison).",
  "properties": {
    "vg": {
      "$ref": "#/$defs/VolumeGroup"
    },
    "pool": {
      "$ref": "#/$defs/ThinPool"
    },
    "thin_device": {
      "title": "Thin Device",
      "type": "string"
    },
    "regular_lv": {
      "$ref": "#/$defs/LogicalVolume"
    },
    "regular_device": {
      "title": "Regular Device",
      "type": "string"
    }
  },
  "required": [
    "vg",
    "pool",
    "thin_device",
    "regular_lv",
    "regular_device"
  ],
  "title": "ThinAndRegularVolumeContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
57
58
59
60
61
62
63
64
class ThinAndRegularVolumeContext(StsBaseModel):
    """State for a thin volume and a regular volume created side-by-side (perf comparison)."""

    vg: VolumeGroup
    pool: ThinPool
    thin_device: str
    regular_lv: LogicalVolume
    regular_device: str

ThinPoolVolumeContext pydantic-model

Bases: StsBaseModel

State for a thin pool with one thin volume created in it.

Show JSON schema:
{
  "$defs": {
    "LVReport": {
      "description": "Parsed LV data from 'lvs -o lv_all,seg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one logical volume's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').\nAll fields match lvs JSON key names and retain str | None typing,\nexcept for a handful of numeric fields (see below) that are coerced.",
      "properties": {
        "lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Uuid"
        },
        "lv_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Name"
        },
        "lv_full_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Name"
        },
        "lv_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Path"
        },
        "lv_dm_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Dm Path"
        },
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "lv_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Size"
        },
        "lv_metadata_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Metadata Size"
        },
        "seg_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Count"
        },
        "lv_layout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Layout"
        },
        "lv_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Role"
        },
        "lv_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Attr"
        },
        "lv_active": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active"
        },
        "lv_active_locally": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Locally"
        },
        "lv_active_remotely": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Remotely"
        },
        "lv_active_exclusively": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Active Exclusively"
        },
        "lv_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Permissions"
        },
        "lv_suspended": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Suspended"
        },
        "lv_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Major"
        },
        "lv_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Minor"
        },
        "lv_kernel_major": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Major"
        },
        "lv_kernel_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Minor"
        },
        "lv_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Read Ahead"
        },
        "lv_kernel_read_ahead": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Kernel Read Ahead"
        },
        "pool_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv"
        },
        "pool_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Lv Uuid"
        },
        "data_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv"
        },
        "data_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Lv Uuid"
        },
        "metadata_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv"
        },
        "metadata_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Lv Uuid"
        },
        "data_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Percent"
        },
        "metadata_percent": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Percent"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "origin_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Uuid"
        },
        "origin_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin Size"
        },
        "snap_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Percent"
        },
        "raid_mismatch_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Mismatch Count"
        },
        "raid_sync_action": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Sync Action"
        },
        "raid_write_behind": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Write Behind"
        },
        "raid_min_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Min Recovery Rate"
        },
        "raid_max_recovery_rate": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raid Max Recovery Rate"
        },
        "cache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Total Blocks"
        },
        "cache_used_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Used Blocks"
        },
        "cache_dirty_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Dirty Blocks"
        },
        "cache_read_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Hits"
        },
        "cache_read_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Read Misses"
        },
        "cache_write_hits": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Hits"
        },
        "cache_write_misses": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Write Misses"
        },
        "kernel_cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Settings"
        },
        "kernel_cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Cache Policy"
        },
        "vdo_operating_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Operating Mode"
        },
        "vdo_compression_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression State"
        },
        "vdo_index_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index State"
        },
        "vdo_used_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Used Size"
        },
        "vdo_saving_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Saving Percent"
        },
        "writecache_block_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Block Size"
        },
        "writecache_total_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Total Blocks"
        },
        "writecache_free_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Free Blocks"
        },
        "writecache_writeback_blocks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Writeback Blocks"
        },
        "writecache_error": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Writecache Error"
        },
        "lv_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Policy"
        },
        "lv_allocation_locked": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Allocation Locked"
        },
        "lv_autoactivation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Autoactivation"
        },
        "lv_when_full": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv When Full"
        },
        "lv_skip_activation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Skip Activation"
        },
        "lv_fixed_minor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Fixed Minor"
        },
        "lv_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time"
        },
        "lv_time_removed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Time Removed"
        },
        "lv_host": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Host"
        },
        "lv_health_status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Health Status"
        },
        "lv_check_needed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Check Needed"
        },
        "lv_merge_failed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merge Failed"
        },
        "lv_snapshot_invalid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Snapshot Invalid"
        },
        "lv_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Tags"
        },
        "lv_profile": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Profile"
        },
        "lv_lockargs": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Lockargs"
        },
        "lv_modules": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Modules"
        },
        "lv_historical": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Historical"
        },
        "kernel_discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Discards"
        },
        "copy_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Copy Percent"
        },
        "sync_percent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Sync Percent"
        },
        "lv_live_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Live Table"
        },
        "lv_inactive_table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Inactive Table"
        },
        "lv_device_open": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Device Open"
        },
        "lv_parent": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Parent"
        },
        "lv_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Ancestors"
        },
        "lv_full_ancestors": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Ancestors"
        },
        "lv_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Descendants"
        },
        "lv_full_descendants": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Full Descendants"
        },
        "lv_converting": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Converting"
        },
        "lv_merging": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Merging"
        },
        "move_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv"
        },
        "move_pv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Move Pv Uuid"
        },
        "convert_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv"
        },
        "convert_lv_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Convert Lv Uuid"
        },
        "mirror_log": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log"
        },
        "mirror_log_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mirror Log Uuid"
        },
        "lv_initial_image_sync": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Initial Image Sync"
        },
        "lv_image_synced": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Image Synced"
        },
        "raidintegritymode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegritymode"
        },
        "raidintegrityblocksize": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Raidintegrityblocksize"
        },
        "integritymismatches": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integritymismatches"
        },
        "kernel_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Kernel Metadata Format"
        },
        "segtype": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Segtype"
        },
        "stripes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripes"
        },
        "data_stripes": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Stripes"
        },
        "stripe_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Stripe Size"
        },
        "region_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Region Size"
        },
        "chunk_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Chunk Size"
        },
        "seg_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start"
        },
        "seg_start_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Start Pe"
        },
        "seg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size"
        },
        "seg_size_pe": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Size Pe"
        },
        "seg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Tags"
        },
        "seg_pe_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Pe Ranges"
        },
        "seg_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Le Ranges"
        },
        "seg_metadata_le_ranges": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Metadata Le Ranges"
        },
        "devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Devices"
        },
        "metadata_devices": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Metadata Devices"
        },
        "seg_monitor": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Seg Monitor"
        },
        "reshape_len": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len"
        },
        "reshape_len_le": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reshape Len Le"
        },
        "data_copies": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Copies"
        },
        "data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Data Offset"
        },
        "new_data_offset": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "New Data Offset"
        },
        "parity_chunks": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Parity Chunks"
        },
        "thin_count": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Count"
        },
        "discards": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Discards"
        },
        "cache_metadata_format": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Metadata Format"
        },
        "cache_mode": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Mode"
        },
        "zero": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Zero"
        },
        "transaction_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Transaction Id"
        },
        "thin_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Thin Id"
        },
        "cache_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Policy"
        },
        "cache_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Cache Settings"
        },
        "integrity_settings": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Integrity Settings"
        },
        "vdo_compression": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Compression"
        },
        "vdo_deduplication": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Deduplication"
        },
        "vdo_minimum_io_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Minimum Io Size"
        },
        "vdo_block_map_cache_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Cache Size"
        },
        "vdo_block_map_era_length": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Block Map Era Length"
        },
        "vdo_use_sparse_index": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Sparse Index"
        },
        "vdo_index_memory_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Index Memory Size"
        },
        "vdo_slab_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Slab Size"
        },
        "vdo_ack_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Ack Threads"
        },
        "vdo_bio_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Threads"
        },
        "vdo_bio_rotation": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Bio Rotation"
        },
        "vdo_cpu_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Cpu Threads"
        },
        "vdo_hash_zone_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Hash Zone Threads"
        },
        "vdo_logical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Logical Threads"
        },
        "vdo_physical_threads": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Physical Threads"
        },
        "vdo_max_discard": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Max Discard"
        },
        "vdo_header_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Header Size"
        },
        "vdo_use_metadata_hints": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Use Metadata Hints"
        },
        "vdo_write_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vdo Write Policy"
        }
      },
      "title": "LVReport",
      "type": "object"
    },
    "LogicalVolume": {
      "additionalProperties": false,
      "description": "Logical Volume device.\n\nExample:\n    ```python\n    lv = LogicalVolume(name='lv0', vg='vg0')\n    lv.create(size='100M').assert_ok()\n    print(lv.report.lv_size)\n    ```",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "LogicalVolume",
      "type": "object"
    },
    "ThinPool": {
      "additionalProperties": false,
      "description": "Thin Pool logical volume.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "vg": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/LVReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        },
        "thin_volumes": {
          "items": {
            "$ref": "#/$defs/LogicalVolume"
          },
          "title": "Thin Volumes",
          "type": "array"
        },
        "tdata": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "tmeta": {
          "anyOf": [
            {
              "$ref": "#/$defs/LogicalVolume"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "ThinPool",
      "type": "object"
    },
    "VGReport": {
      "description": "Parsed VG data from 'vgs -o vg_all --reportformat json'.\n\nFrozen (immutable) snapshot of one volume group's report fields.\nUnknown JSON keys are silently ignored (extra='ignore').",
      "properties": {
        "vg_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Name"
        },
        "vg_uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Uuid"
        },
        "vg_fmt": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Fmt"
        },
        "vg_attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Attr"
        },
        "vg_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Size"
        },
        "vg_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free"
        },
        "vg_extent_size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Size"
        },
        "vg_extent_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Extent Count"
        },
        "vg_free_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Free Count"
        },
        "pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pv Count"
        },
        "lv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Lv Count"
        },
        "snap_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snap Count"
        },
        "vg_seqno": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Seqno"
        },
        "vg_tags": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Tags"
        },
        "vg_mda_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Count"
        },
        "vg_mda_free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Mda Free"
        },
        "max_lv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Lv"
        },
        "max_pv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Pv"
        },
        "vg_permissions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Permissions"
        },
        "vg_allocation_policy": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Allocation Policy"
        },
        "vg_clustered": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Clustered"
        },
        "vg_exported": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Exported"
        },
        "vg_partial": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Partial"
        },
        "vg_missing_pv_count": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Vg Missing Pv Count"
        }
      },
      "title": "VGReport",
      "type": "object"
    },
    "VolumeGroup": {
      "additionalProperties": false,
      "description": "Volume Group device.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "format": "path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "model": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Model"
        },
        "pvs": {
          "items": {
            "type": "string"
          },
          "title": "Pvs",
          "type": "array"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/VGReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "VolumeGroup",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a thin pool with one thin volume created in it.",
  "properties": {
    "vg": {
      "$ref": "#/$defs/VolumeGroup"
    },
    "pool": {
      "$ref": "#/$defs/ThinPool"
    },
    "thin_lv": {
      "$ref": "#/$defs/LogicalVolume"
    }
  },
  "required": [
    "vg",
    "pool",
    "thin_lv"
  ],
  "title": "ThinPoolVolumeContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
49
50
51
52
53
54
class ThinPoolVolumeContext(StsBaseModel):
    """State for a thin pool with one thin volume created in it."""

    vg: VolumeGroup
    pool: ThinPool
    thin_lv: LogicalVolume

lv_fixture(_lvm_test, setup_vg, request)

Create a COW or thin LV with automatic cleanup.

Defaults to COW at 25%vg. Parametrize with {'lv_type': 'thin'} for thin volumes.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
@pytest.fixture
def lv_fixture(
    _lvm_test: None, setup_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[LogicalVolume, None, None]:
    """Create a COW or thin LV with automatic cleanup.

    Defaults to COW at 25%vg. Parametrize with ``{'lv_type': 'thin'}`` for thin volumes.
    """
    params = getattr(request, 'param', {})
    lv_type = params.get('lv_type', 'cow')
    vg = setup_vg

    # Set defaults based on lv_type
    if lv_type == 'thin':
        default_lv_name = 'ststhin25vglv1'
        default_pool_name = 'stspool1_25vg'
    else:
        default_lv_name = 'stscow25vglv1'
        default_pool_name = None

    lv_name = params.get('lv_name', getenv('LV_NAME', default_lv_name))
    extents = params.get('extents', '25%vg')
    pool_name = params.get('pool_name', getenv('THIN_POOL_NAME', default_pool_name)) if lv_type == 'thin' else None
    virtualsize = params.get('virtualsize', '512M')

    lv = _create_lv(vg, lv_type=lv_type, lv_name=lv_name, extents=extents, virtualsize=virtualsize, pool_name=pool_name)

    yield lv

    # Cleanup — remove_with_thin_volumes raises STSError on failure
    if lv_type == 'thin' and pool_name:
        with contextlib.suppress(STSError):
            ThinPool(name=pool_name, vg=vg.name).remove_with_thin_volumes()
    else:
        LogicalVolume(name=lv_name, vg=vg.name).remove()

lvm2_version()

Return the installed LVM2 version as VersionInfo (or 0.0.0 if not found).

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@pytest.fixture(scope='class')
def lvm2_version() -> VersionInfo:
    """Return the installed LVM2 version as VersionInfo (or 0.0.0 if not found)."""
    result = run('rpm -q lvm2 --queryformat "%{VERSION}-%{RELEASE}"')
    if result.rc == 0:
        return VersionInfo.from_string(result.stdout.strip())

    # Fallback to lvm version command
    result = run('lvm version')
    if result.rc == 0:
        lines = result.stdout.strip().split('\n')
        for line in lines:
            if 'LVM version:' in line:
                return VersionInfo.from_string(line.split(':')[1].strip())

    return VersionInfo(0, 0, 0)

lvm_config()

Return an LvmConfig instance for reading/modifying lvm.conf settings.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
508
509
510
511
@pytest.fixture
def lvm_config() -> LvmConfig:
    """Return an LvmConfig instance for reading/modifying lvm.conf settings."""
    return LvmConfig()

lvm_config_restore(lvm_config)

Yield LvmConfig, restoring thin pool configuration values on teardown.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
@pytest.fixture
def lvm_config_restore(lvm_config: LvmConfig) -> Generator[LvmConfig, None, None]:
    """Yield LvmConfig, restoring thin pool configuration values on teardown."""
    # Save original values
    original_values = {
        LvmConfig.THIN_POOL_METADATA_REQUIRE_SEPARATE_PVS: lvm_config.get_thin_pool_metadata_require_separate_pvs(),
        LvmConfig.THIN_POOL_AUTOEXTEND_THRESHOLD: lvm_config.get_thin_pool_autoextend_threshold(),
        LvmConfig.THIN_POOL_AUTOEXTEND_PERCENT: lvm_config.get_thin_pool_autoextend_percent(),
    }

    yield lvm_config

    # Restore original values
    for key, value in original_values.items():
        if value is not None:
            lvm_config.set(key, value)

mount_lv_fixture(_lvm_test, setup_vg, request)

Create an LV, format it, and mount it, with automatic cleanup.

Defaults to COW, xfs. Parametrize lv_type, fs_type, etc. via indirect.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@pytest.fixture
def mount_lv_fixture(
    _lvm_test: None, setup_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[Directory, None, None]:
    """Create an LV, format it, and mount it, with automatic cleanup.

    Defaults to COW, xfs. Parametrize ``lv_type``, ``fs_type``, etc. via indirect.
    """
    params = getattr(request, 'param', {})
    lv_type = params.get('lv_type', 'cow')
    vg = setup_vg

    # Set defaults based on lv_type
    if lv_type == 'thin':
        default_lv_name = 'ststhinmntlv1'
        default_pool_name = 'stspool1_mnt'
        default_mount_point = '/mnt/thinlvmntdir'
    else:
        default_lv_name = 'stscowmntlv1'
        default_pool_name = None
        default_mount_point = '/mnt/lvcowmntdir'

    lv_name = params.get('lv_name', default_lv_name)
    extents = params.get('extents', '25%vg')
    pool_name = params.get('pool_name', default_pool_name) if lv_type == 'thin' else None
    virtualsize = params.get('virtualsize', '512M')
    fs_type = params.get('fs_type', 'xfs')
    mount_point = params.get('mount_point', default_mount_point)

    _create_lv(vg, lv_type=lv_type, lv_name=lv_name, extents=extents, virtualsize=virtualsize, pool_name=pool_name)

    dev_path = f'/dev/{vg.name}/{lv_name}'

    # Create filesystem
    assert mkfs(device=dev_path, fs_type=fs_type)

    # Create mount point directory
    mnt_dir = Directory(path=Path(mount_point), create=True)
    assert mnt_dir.exists, f'Failed to create mount point directory {mount_point}'

    # Mount the LV
    assert mount(device=dev_path, mountpoint=mount_point)

    yield mnt_dir

    # Cleanup
    try:
        umount(mountpoint=mount_point)
    except Exception:  # noqa: BLE001
        logger.warning('Failed to unmount %s during teardown', mount_point)
    mnt_dir.remove_dir()

    if lv_type == 'thin' and pool_name:
        with contextlib.suppress(STSError):
            ThinPool(name=pool_name, vg=vg.name).remove_with_thin_volumes()
    else:
        LogicalVolume(name=lv_name, vg=vg.name).remove()

mount_lv_ramdisk_fixture(_lvm_test, setup_ramdisk_vg, request)

Create a mounted LV on a ramdisk-backed VG for fast I/O testing.

Defaults to thin, xfs, 400M virtualsize. Smaller sizes to conserve memory.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
@pytest.fixture
def mount_lv_ramdisk_fixture(
    _lvm_test: None, setup_ramdisk_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[Directory, None, None]:
    """Create a mounted LV on a ramdisk-backed VG for fast I/O testing.

    Defaults to thin, xfs, 400M virtualsize. Smaller sizes to conserve memory.
    """
    params = getattr(request, 'param', {})
    lv_type = params.get('lv_type', 'thin')  # Default to thin for ramdisk
    vg = setup_ramdisk_vg

    # Set defaults based on lv_type (smaller sizes for ramdisk)
    if lv_type == 'thin':
        default_lv_name = 'stsrdthinlv1'
        default_pool_name = 'stsrdpool1'
        default_mount_point = '/mnt/rdthinmnt'
    else:
        default_lv_name = 'stsrdcowlv1'
        default_pool_name = None
        default_mount_point = '/mnt/rdcowmnt'

    lv_name = params.get('lv_name', default_lv_name)
    extents = params.get('extents', '50%vg')  # Use more of the smaller VG
    pool_name = params.get('pool_name', default_pool_name) if lv_type == 'thin' else None
    virtualsize = params.get('virtualsize', '400M')  # Must be >300MB for XFS
    fs_type = params.get('fs_type', 'xfs')
    mount_point = params.get('mount_point', default_mount_point)

    _create_lv(vg, lv_type=lv_type, lv_name=lv_name, extents=extents, virtualsize=virtualsize, pool_name=pool_name)

    dev_path = f'/dev/{vg.name}/{lv_name}'

    # Create filesystem
    assert mkfs(device=dev_path, fs_type=fs_type)

    # Create mount point directory
    mnt_dir = Directory(path=Path(mount_point), create=True)
    assert mnt_dir.exists, f'Failed to create mount point directory {mount_point}'

    # Mount the LV
    assert mount(device=dev_path, mountpoint=mount_point)

    yield mnt_dir

    # Cleanup
    try:
        umount(mountpoint=mount_point)
    except Exception:  # noqa: BLE001
        logger.warning('Failed to unmount %s during teardown', mount_point)
    mnt_dir.remove_dir()

    if lv_type == 'thin' and pool_name:
        with contextlib.suppress(STSError):
            ThinPool(name=pool_name, vg=vg.name).remove_with_thin_volumes()
    else:
        LogicalVolume(name=lv_name, vg=vg.name).remove()

mounted_thin_and_regular_lvs(thin_and_regular_lvs)

Format and mount both thin and regular LVs with ext4 for I/O comparison.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
@pytest.fixture
def mounted_thin_and_regular_lvs(
    thin_and_regular_lvs: ThinAndRegularVolumeContext,
) -> Generator[MountedThinAndRegularVolumeContext, None, None]:
    """Format and mount both thin and regular LVs with ext4 for I/O comparison."""
    thin_device = thin_and_regular_lvs.thin_device
    regular_device = thin_and_regular_lvs.regular_device

    # Create mount point directories
    thin_lv_mnt = Directory(path=Path('/mnt/thin_lv'), create=True)
    regular_lv_mnt = Directory(path=Path('/mnt/regular_lv'), create=True)

    filesystem = 'ext4'  # Use ext4 for consistent performance testing
    assert mkfs(thin_device, filesystem, force=True)
    assert mkfs(regular_device, filesystem, force=True)

    assert mount(thin_device, thin_lv_mnt.path)
    assert mount(regular_device, regular_lv_mnt.path)

    yield MountedThinAndRegularVolumeContext(
        base=thin_and_regular_lvs,
        thin_lv_mnt=thin_lv_mnt,
        regular_lv_mnt=regular_lv_mnt,
        filesystem=filesystem,
    )

    # Cleanup
    umount(thin_lv_mnt.path)
    umount(regular_lv_mnt.path)
    thin_lv_mnt.remove_dir()
    regular_lv_mnt.remove_dir()

multiple_mntpoints_fixture(_lvm_test, setup_vg, request)

Create multiple mounted LVs. Defaults to 6 COW volumes with xfs.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
473
474
475
476
477
478
479
480
481
482
483
@pytest.fixture
def multiple_mntpoints_fixture(
    _lvm_test: None, setup_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[list[Directory], None, None]:
    """Create multiple mounted LVs. Defaults to 6 COW volumes with xfs."""
    params = getattr(request, 'param', {})

    yield from _create_multiple_lv_mntpoints(
        vg=setup_vg,
        **params,
    )

multiple_mntpoints_ramdisk_fixture(_lvm_test, setup_ramdisk_vg, request)

Create multiple mounted LVs on a ramdisk-backed VG. Defaults to 2 thin volumes.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
@pytest.fixture
def multiple_mntpoints_ramdisk_fixture(
    _lvm_test: None, setup_ramdisk_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[list[Directory], None, None]:
    """Create multiple mounted LVs on a ramdisk-backed VG. Defaults to 2 thin volumes."""
    params = getattr(request, 'param', {})

    # Override defaults for ramdisk (smaller sizes, fewer mount points)
    ramdisk_defaults: dict[str, Any] = {
        'lv_type': 'thin',
        'num_of_mntpoints': 2,
        'virtualsize': '400M',  # Must be >300MB for XFS
        'percentage_of_vg_to_use': 80,
    }
    ramdisk_defaults.update(params)

    yield from _create_multiple_lv_mntpoints(
        vg=setup_ramdisk_vg,
        **ramdisk_defaults,
    )

multiple_thin_pools(setup_loopdev_vg)

Create 3 thin pools, each with one thin volume, for concurrent testing.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
@pytest.fixture
def multiple_thin_pools(setup_loopdev_vg: VolumeGroup) -> Generator[list[ThinPool], None, None]:
    """Create 3 thin pools, each with one thin volume, for concurrent testing."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    pools: list[ThinPool] = []

    # Create 3 thin pools with thin volumes
    for i in range(1, 4):
        pool_name = f'pool{i}'
        lv_name = f'lv{i}'

        pool = ThinPool.create_thin_pool(pool_name, vg_name, size='30M')
        pool.create_thin_volume(lv_name, virtualsize='50M')
        pools.append(pool)

    yield pools

    # Cleanup
    for pool in pools:
        pool.remove_with_thin_volumes()

regular_lv_fixture(setup_loopdev_vg, request)

Create a regular (non-thin) LV (default 50M) with automatic cleanup.

Set skip_cleanup=True when the LV will be converted to a thin pool and cleanup is handled separately.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
@pytest.fixture
def regular_lv_fixture(
    setup_loopdev_vg: VolumeGroup, request: pytest.FixtureRequest
) -> Generator[LogicalVolume, None, None]:
    """Create a regular (non-thin) LV (default 50M) with automatic cleanup.

    Set ``skip_cleanup=True`` when the LV will be converted to a thin pool
    and cleanup is handled separately.
    """
    vg = setup_loopdev_vg
    params = getattr(request, 'param', {})

    lv_name = params.get('lv_name', 'lv')
    size = params.get('size', '50M')
    extents = params.get('extents')
    inactive = params.get('inactive', False)
    zero = params.get('zero')
    skip_cleanup = params.get('skip_cleanup', False)

    lv = LogicalVolume(name=lv_name, vg=vg.name)

    # Build create options
    create_args: list[str] = []
    if inactive:
        create_args.append('-an')

    if extents:
        if zero:
            lv.create(*create_args, extents=extents, zero=zero).assert_ok()
        else:
            lv.create(*create_args, extents=extents).assert_ok()
    elif zero:
        lv.create(*create_args, size=size, zero=zero).assert_ok()
    else:
        lv.create(*create_args, size=size).assert_ok()

    logger.debug(f'Created regular LV {lv_name} in VG {vg.name}')

    yield lv

    # Cleanup - skip if LV was converted to pool (conversion tests handle cleanup)
    # force=True: the LV is commonly formatted/mounted by consumers before teardown runs
    if not skip_cleanup:
        lv.remove(force=True)
        logger.debug(f'Cleaned up regular LV {lv_name}')

setup_loopdev_vg(_lvm_test, loop_devices)

Create a VG from loop_devices, cleaned up on teardown.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
149
150
151
152
153
154
155
156
157
158
159
@pytest.fixture
def setup_loopdev_vg(_lvm_test: None, loop_devices: list[LoopDevice]) -> Generator[VolumeGroup, None, None]:
    """Create a VG from loop_devices, cleaned up on teardown."""
    vg_name = getenv('STS_VG_NAME', 'stsvg0')
    devices = [str(dev.path) for dev in loop_devices]

    vg = _make_vg(devices, vg_name)
    try:
        yield vg
    finally:
        _teardown_vg(vg, devices)

setup_ramdisk_vg(_lvm_test, ramdisk_loop_devices)

Create a VG from ramdisk-backed loop devices for fast I/O testing.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
162
163
164
165
166
167
168
169
170
171
@pytest.fixture
def setup_ramdisk_vg(_lvm_test: None, ramdisk_loop_devices: list[str]) -> Generator[VolumeGroup, None, None]:
    """Create a VG from ramdisk-backed loop devices for fast I/O testing."""
    vg_name = getenv('STS_VG_NAME', 'stsvg0')

    vg = _make_vg(ramdisk_loop_devices, vg_name)
    try:
        yield vg
    finally:
        _teardown_vg(vg, ramdisk_loop_devices)

setup_vg(_lvm_test, ensure_minimum_devices_with_same_block_sizes)

Create a VG from ensure_minimum_devices_with_same_block_sizes, cleaned up on teardown.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
134
135
136
137
138
139
140
141
142
143
144
145
146
@pytest.fixture
def setup_vg(
    _lvm_test: None, ensure_minimum_devices_with_same_block_sizes: list[BlockDevice]
) -> Generator[VolumeGroup, None, None]:
    """Create a VG from ensure_minimum_devices_with_same_block_sizes, cleaned up on teardown."""
    vg_name = getenv('STS_VG_NAME', 'stsvg0')
    devices = [str(device.path) for device in ensure_minimum_devices_with_same_block_sizes]

    vg = _make_vg(devices, vg_name)
    try:
        yield vg
    finally:
        _teardown_vg(vg, devices)

temp_mount_fixture(request)

Create a temporary mount point directory with automatic cleanup.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
@pytest.fixture
def temp_mount_fixture(request: pytest.FixtureRequest) -> Generator[TempMountContext, None, None]:
    """Create a temporary mount point directory with automatic cleanup."""
    params = getattr(request, 'param', {})
    mount_point = Path(params.get('mount_point', '/mnt/test'))

    mount_dir = Directory(path=mount_point, create=True)

    mount_info = TempMountContext(
        mount_point=mount_point,
        mount_dir=mount_dir,
        temp_files=[],
    )

    yield mount_info

    # Cleanup - unmount if mounted, remove dir, clean temp files
    umount(mount_point)

    if mount_dir.exists:
        mount_dir.remove_dir()

    for temp_file in mount_info.temp_files:
        run(f'rm -f {temp_file}')

thin_and_regular_lvs(setup_loopdev_vg)

Create a thin volume (900M) and a regular LV (900M) side-by-side for comparison.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
@pytest.fixture
def thin_and_regular_lvs(setup_loopdev_vg: VolumeGroup) -> Generator[ThinAndRegularVolumeContext, None, None]:
    """Create a thin volume (900M) and a regular LV (900M) side-by-side for comparison."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None

    # Create thin pool and volumes
    pool = ThinPool.create_thin_pool('pool', vg_name, size='1G')
    pool.create_thin_volume('thin_lv', virtualsize='900M')
    regular_lv = LogicalVolume(name='regular_lv', vg=vg_name)
    regular_lv.create(size='900M').assert_ok()

    yield ThinAndRegularVolumeContext(
        vg=vg,
        pool=pool,
        regular_lv=regular_lv,
        thin_device=f'/dev/mapper/{vg_name}-thin_lv',
        regular_device=f'/dev/mapper/{vg_name}-regular_lv',
    )

    # Cleanup
    regular_lv.remove()
    with contextlib.suppress(STSError):
        pool.remove_with_thin_volumes()

thin_pool_with_volume(setup_loopdev_vg)

Create a 100M thin pool with one 50M thin volume.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
@pytest.fixture
def thin_pool_with_volume(setup_loopdev_vg: VolumeGroup) -> Generator[ThinPoolVolumeContext, None, None]:
    """Create a 100M thin pool with one 50M thin volume."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    pool_name = 'pool'
    lv_name = 'lv1'

    # Create thin pool
    pool = ThinPool.create_thin_pool(pool_name, vg_name, size='100M')

    # Create thin volume
    thin_lv = pool.create_thin_volume(lv_name, virtualsize='50M')

    yield ThinPoolVolumeContext(vg=vg, pool=pool, thin_lv=thin_lv)

    # Cleanup
    pool.remove_with_thin_volumes()

thinpool_fixture(setup_loopdev_vg, request)

Create a thin pool (default 500M) with automatic cleanup of all thin volumes.

Source code in sts_libs/src/sts/fixtures/lvm_fixtures.py
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
@pytest.fixture
def thinpool_fixture(setup_loopdev_vg: VolumeGroup, request: pytest.FixtureRequest) -> Generator[ThinPool, None, None]:
    """Create a thin pool (default 500M) with automatic cleanup of all thin volumes."""
    vg = setup_loopdev_vg
    vg_name = vg.name
    assert vg_name is not None
    params = getattr(request, 'param', {})

    pool_name = params.get('pool_name', 'pool')
    size = params.get('size')
    extents = params.get('extents')
    discards = params.get('discards')
    stripes = params.get('stripes')
    stripesize = params.get('stripesize')
    chunksize = params.get('chunksize')
    poolmetadatasize = params.get('poolmetadatasize')
    poolmetadataspare = params.get('poolmetadataspare')
    create_thin_volume = params.get('create_thin_volume', False)
    thin_volume_name = params.get('thin_volume_name', 'lv1')
    thin_volume_size = params.get('thin_volume_size', '50M')

    # Default size if neither size nor extents specified
    if not size and not extents:
        size = '500M'

    # Build options dict, filtering out None values
    pool_options: LvCreateOptions = {}
    if size:
        pool_options['size'] = size
    if extents:
        pool_options['extents'] = extents
    if discards:
        pool_options['discards'] = discards
    if stripes:
        pool_options['stripes'] = stripes
    if stripesize:
        pool_options['stripesize'] = stripesize
    if chunksize:
        pool_options['chunksize'] = chunksize
    if poolmetadatasize:
        pool_options['poolmetadatasize'] = poolmetadatasize
    if poolmetadataspare:
        pool_options['poolmetadataspare'] = poolmetadataspare

    pool = ThinPool.create_thin_pool(
        pool_name,
        vg_name,
        **pool_options,
    )
    logger.debug(f'Created thin pool {pool_name} with size {size or extents} in VG {vg_name}')

    if create_thin_volume:
        pool.create_thin_volume(thin_volume_name, virtualsize=thin_volume_size)
        logger.debug(f'Created thin volume {thin_volume_name} with size {thin_volume_size}')

    yield pool

    # Cleanup - remove all thin volumes and the pool. Tolerant: some tests remove
    # the pool (or its thin volumes) themselves as part of what they're testing, in
    # which case this is a no-op cleanup attempt against an already-gone pool, not
    # a real failure.
    try:
        pool.remove_with_thin_volumes()
    except STSError as e:
        logger.warning(f'Failed to remove pool {pool_name} with thin volumes (may already be removed): {e}')
    logger.debug(f'Cleaned up thin pool {pool_name} and all its volumes')

Network Storage

iSCSI

sts.fixtures.iscsi_fixtures

iSCSI test fixtures.

generate_test_iqns(test_name)

Generate (base_iqn, target_iqn, initiator_iqn) from test name.

Source code in sts_libs/src/sts/fixtures/iscsi_fixtures.py
28
29
30
31
32
33
def generate_test_iqns(test_name: str) -> tuple[str, str, str]:
    """Generate (base_iqn, target_iqn, initiator_iqn) from test name."""
    test_name = test_name.split('[', maxsplit=1)[0]
    test_name = test_name.replace('_', '-')
    base_iqn = f'iqn.2024-01.sts.{test_name}'
    return base_iqn, f'{base_iqn}:target', f'{base_iqn}:initiator'

get_test_device()

Return a callable that finds test device paths (multipath first, then SCSI vendor).

Source code in sts_libs/src/sts/fixtures/iscsi_fixtures.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@pytest.fixture
def get_test_device() -> Callable[[str], list[Path]]:
    """Return a callable that finds test device paths (multipath first, then SCSI vendor)."""

    def _get_test_device(vendor: str = 'LIO-ORG') -> list[Path]:
        def _extract_paths(devices: Sequence[StorageDevice]) -> list[Path]:
            return [Path(str(d.path)) for d in devices if d.path]

        mp_service = MultipathService()
        if mp_service.is_running():
            devices = MultipathDevice.get_all()
            if devices:
                paths = _extract_paths(devices)
                if paths:
                    return paths

        devices = ScsiDevice.get_by_vendor(vendor)
        assert devices, f'No {vendor} devices found'

        paths = _extract_paths(devices)
        assert paths, f'No valid device paths found for {vendor} devices'
        return paths

    return _get_test_device

iscsi_localhost_test(request, _iscsi_test)

Set up and tear down a local iSCSI target environment. Yields target IQN.

Source code in sts_libs/src/sts/fixtures/iscsi_fixtures.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@pytest.fixture(scope='class')
def iscsi_localhost_test(request: pytest.FixtureRequest, _iscsi_test: None) -> Generator[str, None, None]:
    """Set up and tear down a local iSCSI target environment. Yields target IQN."""
    assert ensure_installed('targetcli')

    test_name = str(request.node.name)  # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
    _, target_iqn, _ = generate_test_iqns(test_name)

    target = Iscsi(target_wwn=target_iqn)
    target.delete_target()

    yield target_iqn

    target.delete_target()

iscsi_target(request, iscsi_localhost_test)

Create iSCSI target with optional LUNs, log in, yield IscsiNode, log out on exit.

Source code in sts_libs/src/sts/fixtures/iscsi_fixtures.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@pytest.fixture
def iscsi_target(request: pytest.FixtureRequest, iscsi_localhost_test: None) -> Generator[IscsiNode, None, None]:  # noqa: ARG001
    """Create iSCSI target with optional LUNs, log in, yield IscsiNode, log out on exit."""
    node_name = str(request.node.name)  # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
    _, target_iqn, initiator_iqn = generate_test_iqns(node_name)
    params: dict[str, Any] = request.param if hasattr(request, 'param') else {}
    size: str = params.get('size', '1G')
    n_luns: int = params.get('n_luns', 1)

    assert create_basic_iscsi_target(
        target_wwn=target_iqn,
        initiator_wwn=initiator_iqn,
        size=size,
    ), 'Failed to create target'

    if n_luns > 1:
        test_name = node_name.split('[', maxsplit=1)[0]
        for i in range(1, n_luns):
            backstore_name = f'{test_name}_lun{i}'
            backstore = BackstoreFileio(name=backstore_name)
            backstore.create_backstore(size=size, file_or_dev=f'{backstore_name}_file')
            IscsiLUN(target_wwn=target_iqn).create_lun(storage_object=backstore.path)

    node = setup_and_login(
        portal='127.0.0.1:3260',
        initiator_iqn=initiator_iqn,
        target_iqn=target_iqn,
    )
    try:
        yield node
    finally:
        node.logout()

NVMe

sts.fixtures.nvme_fixtures

NVMe test fixtures.

ensure_nvme_disks()

Install nvme-cli and skip if no NVMe devices are present.

Source code in sts_libs/src/sts/fixtures/nvme_fixtures.py
12
13
14
15
16
17
18
19
20
21
@pytest.fixture(scope='class')
def ensure_nvme_disks() -> None:
    """Install nvme-cli and skip if no NVMe devices are present."""
    # Ensure nvme-cli package is installed
    if not ensure_installed('nvme-cli'):
        pytest.skip('Failed to install nvme-cli package')

    # Check if NVMe devices are available
    if not NvmeDevice.has_nvme():
        pytest.skip('No NVMe disks detected.')

Fibre Channel

sts.fixtures.fc_fixtures

FC test fixtures.

get_fc_device()

Return the first online Fibre Channel device, or skip.

Source code in sts_libs/src/sts/fixtures/fc_fixtures.py
22
23
24
25
26
27
28
29
30
@pytest.fixture(scope='class')
def get_fc_device() -> FcDevice:
    """Return the first online Fibre Channel device, or skip."""
    devices = FcDevice.get_by_attribute('transport', 'fc:')
    # Break down complex assertion
    online_devices = [dev for dev in devices if dev.state == 'running']
    if not online_devices:
        pytest.skip("No online FC devices found with transport 'fc:'")
    return online_devices[0]

get_fc_paths(get_multipath_active_paths)

Return FC devices corresponding to active multipath paths.

Source code in sts_libs/src/sts/fixtures/fc_fixtures.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@pytest.fixture
def get_fc_paths(get_multipath_active_paths: MultipathActivePathsContext) -> list[FcDevice]:
    """Return FC devices corresponding to active multipath paths."""
    active_paths = get_multipath_active_paths.active_paths
    fc_devices: list[FcDevice] = []

    for path in active_paths:
        dev_name = path.get('dev')
        if not dev_name:
            continue
        try:
            fc_dev = FcDevice(name=dev_name).discover()
            if fc_dev.path and Path(fc_dev.path).exists():
                fc_devices.append(fc_dev)
        except (ValueError, OSError) as e:
            logger.warning(f'Failed to get FC device for {dev_name}: {e}')

    if not fc_devices:
        pytest.skip('No valid FC paths found')

    return fc_devices

multipath_device_setup(get_multipath_active_paths, get_fc_paths)

Return a verified multipath device with at least MIN_PATHS FC paths.

Source code in sts_libs/src/sts/fixtures/fc_fixtures.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@pytest.fixture
def multipath_device_setup(
    get_multipath_active_paths: MultipathActivePathsContext, get_fc_paths: list[FcDevice]
) -> MultipathDevice:
    """Return a verified multipath device with at least MIN_PATHS FC paths."""
    mpath_device = get_multipath_active_paths.device
    fc_paths = get_fc_paths

    # Verify minimum paths requirement
    if len(fc_paths) < MIN_PATHS:
        pytest.skip(f'Need at least {MIN_PATHS} paths, found {len(fc_paths)}')

    # Verify device accessibility
    if not mpath_device.path or not Path(mpath_device.path).exists():
        pytest.skip(f'Multipath device {mpath_device.name} not accessible')

    return mpath_device

SCSI Target

sts.fixtures.target_fixtures

Target (targetcli/LIO) test fixtures for backstores, iSCSI targets, ACLs, and loopback devices.

backstore_block_setup(_target_test, request)

Create a block backstore on a loop device. Requires name and size params.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
@pytest.fixture
def backstore_block_setup(_target_test: None, request: pytest.FixtureRequest) -> Generator[BackstoreBlock, None, None]:
    """Create a block backstore on a loop device. Requires ``name`` and ``size`` params."""
    loop_dev = None
    backstore = None
    try:
        # Create loop device
        loop_dev = LoopDevice.create(
            name=request.param['name'],
            size_mb=request.param['size'] // (1024 * 1024),
        )
        if not loop_dev:
            pytest.skip('Failed to create loop device')

        # Create backstore
        backstore = BackstoreBlock(name=request.param['name'])
        backstore.create_backstore(dev=str(loop_dev.path))
        yield backstore

    except Exception:
        logger.exception('Failed to set up block backstore')
        raise

    finally:
        # Clean up
        if backstore:
            backstore.delete_backstore()
        if loop_dev:
            loop_dev.remove()

backstore_fileio_setup(_target_test, request)

Create a fileio backstore. Requires name and size params.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@pytest.fixture
def backstore_fileio_setup(
    _target_test: None, request: pytest.FixtureRequest
) -> Generator[BackstoreFileio, None, None]:
    """Create a fileio backstore. Requires ``name`` and ``size`` params."""
    backstore = None
    try:
        backstore = BackstoreFileio(name=request.param['name'])
        backstore.create_backstore(
            size=str(request.param['size']),
            file_or_dev=request.param.get('file_or_dev') or f'{request.param["name"]}_file',
        )
        yield backstore

    except Exception:
        logger.exception('Failed to set up fileio backstore')
        raise

    finally:
        if backstore:
            backstore.delete_backstore()

backstore_ramdisk_setup(_target_test, request)

Create a ramdisk backstore. Requires name and size params.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@pytest.fixture
def backstore_ramdisk_setup(
    _target_test: None, request: pytest.FixtureRequest
) -> Generator[BackstoreRamdisk, None, None]:
    """Create a ramdisk backstore. Requires ``name`` and ``size`` params."""
    backstore = None
    try:
        backstore = BackstoreRamdisk(name=request.param['name'])
        backstore.create_backstore(size=str(request.param['size']))
        yield backstore

    except Exception:
        logger.exception('Failed to set up ramdisk backstore')
        raise

    finally:
        if backstore:
            backstore.delete_backstore()

configure_auth(request)

Create an iSCSI target with CHAP authentication configured via parametrize.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
@pytest.fixture
def configure_auth(request: pytest.FixtureRequest) -> Generator[Iscsi, None, None]:
    """Create an iSCSI target with CHAP authentication configured via parametrize."""
    target_wwn = request.param['t_iqn']
    target = Iscsi(target_wwn=target_wwn)

    try:
        # Create target
        target.create_target()

        # Add backstore
        backstore = BackstoreFileio(name='auth_test')
        backstore.create_backstore(size='1M', file_or_dev='auth_test_file')
        luns = IscsiLUN(target_wwn=target_wwn)
        luns.create_lun(storage_object=backstore.path)

        # Configure auth
        if request.param['tpg_or_acl'] == 'acl':
            acl = ACL(target_wwn=target_wwn, initiator_wwn=request.param['i_iqn'])
            acl.create_acl()
            acl.set_auth(
                userid=request.param['chap_username'],
                password=request.param['chap_password'],
                mutual_userid=request.param.get('chap_target_username', ''),
                mutual_password=request.param.get('chap_target_password', ''),
            )

        yield target

    finally:
        target.delete_target()

iscsi_target_setup(_target_test, request)

Create an iSCSI target with optional ACLs and LUNs via parametrize.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
191
192
193
194
195
196
197
198
199
200
201
@pytest.fixture(scope='class')
def iscsi_target_setup(_target_test: None, request: pytest.FixtureRequest) -> Generator[Iscsi, None, None]:
    """Create an iSCSI target with optional ACLs and LUNs via parametrize."""
    params = request.param
    with target_setup(
        t_iqn=params.get('t_iqn'),
        i_iqn=params.get('i_iqn'),
        n_luns=params.get('n_luns', 0),
        back_size=params.get('back_size'),
    ) as target:
        yield target

loopback_devices(request)

Create loopback devices with a given block size.

Reads block_size and device_count from fixture params or test parametrize. Defaults to 2 devices, 512-byte blocks.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
@pytest.fixture
def loopback_devices(request: pytest.FixtureRequest) -> Generator[list[BlockDevice], None, None]:
    """Create loopback devices with a given block size.

    Reads ``block_size`` and ``device_count`` from fixture params or test parametrize.
    Defaults to 2 devices, 512-byte blocks.
    """
    # Get parameters with defaults
    if hasattr(request, 'param') and isinstance(request.param, dict):
        # Fixture is parametrized with dict
        param_dict = cast('dict[str, Any]', request.param)
        device_count: int = param_dict.get('device_count', 2)
        block_size: int = param_dict.get('block_size', 512)
    else:
        # Get from test parametrization or use defaults
        callspec: Any = getattr(request.node, 'callspec', None)  # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
        callspec_params: dict[str, Any] = getattr(callspec, 'params', {}) if callspec else {}
        device_count = callspec_params.get('device_count', 2)
        block_size = callspec_params.get('block_size', 512)

    # Create devices
    ensure_installed('targetcli')
    devices: list[BlockDevice] = create_loopback_devices(device_count, block_size=block_size)

    try:
        yield devices
    finally:
        # Clean up
        cleanup_loopback_devices(devices)

ramdisk_loopback_devices(_target_test, request)

Create ramdisk-backed loopback devices via targetcli for fast I/O testing.

Defaults to 1 device, 512 MB. Parametrize with [{'count': 2, 'size_mb': 256}].

Warning

Total device size should not exceed available RAM.

Source code in sts_libs/src/sts/fixtures/target_fixtures.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@pytest.fixture(scope='class')
def ramdisk_loopback_devices(_target_test: None, request: pytest.FixtureRequest) -> Generator[list[str], None, None]:
    """Create ramdisk-backed loopback devices via targetcli for fast I/O testing.

    Defaults to 1 device, 512 MB. Parametrize with ``[{'count': 2, 'size_mb': 256}]``.

    Warning:
        Total device size should not exceed available RAM.
    """
    # Handle different parameter formats
    raw_param = getattr(request, 'param', {})
    if isinstance(raw_param, dict):
        params = cast('dict[str, Any]', raw_param)
        count: int = params.get('count', 1)
        size_mb: int = params.get('size_mb', 512)
    else:
        count = 1
        size_mb = 512

    # Use a unique WWN prefix for these devices
    lun_prefix = 'sts-ramdisk-'

    # Initialize loopback target
    loopback = Loopback()
    result = loopback.create_target()
    if not result.succeeded:
        pytest.skip(f'Failed to create loopback target: {result.stderr}')

    wwn = loopback.target_wwn
    if not wwn:
        pytest.skip('Failed to get loopback target WWN')

    lun = LoopbackLUN(target_wwn=wwn)
    backstores: list[BackstoreRamdisk] = []

    try:
        # Create ramdisk backstores and LUNs
        for n in range(count):
            backstore = BackstoreRamdisk(name=f'{lun_prefix}{n}')
            size_bytes: int = size_mb * 1024 * 1024
            result = backstore.create_backstore(size=str(size_bytes))
            if not result.succeeded:
                pytest.skip(f'Failed to create ramdisk backstore {n}: {result.stderr}')
            backstores.append(backstore)

            result = lun.create_lun(storage_object=backstore.path)
            if not result.succeeded:
                pytest.skip(f'Failed to create LUN {n}: {result.stderr}')

        # Find the created devices
        devices = [f'/dev/{dev.name}' for dev in get_free_disks() if dev.model and lun_prefix in dev.model]

        if len(devices) < count:
            pytest.skip(f'Expected {count} ramdisk devices, found {len(devices)}')

        logger.info(f'Created {count} ramdisk loopback device(s): {devices}')
        yield devices[:count]

    finally:
        # Clean up LUNs
        for n in range(len(backstores)):
            lun.delete_lun(n)

        # Clean up backstores
        for backstore in backstores:
            backstore.delete_backstore()

        # Clean up target
        loopback.delete_target()

target_setup(*, t_iqn=None, i_iqn=None, n_luns=0, back_size=None)

Context manager that creates an iSCSI target with optional ACLs and LUNs.

Parameters:

Name Type Description Default
t_iqn str | None

Target IQN

None
i_iqn str | None

Initiator IQN (creates ACL if set)

None
n_luns int

Number of LUNs to create

0
back_size int | None

Backstore size in bytes (required if n_luns > 0)

None
Source code in sts_libs/src/sts/fixtures/target_fixtures.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@contextmanager
def target_setup(
    *,
    t_iqn: str | None = None,
    i_iqn: str | None = None,
    n_luns: int = 0,
    back_size: int | None = None,
) -> Generator[Iscsi, None, None]:
    """Context manager that creates an iSCSI target with optional ACLs and LUNs.

    Args:
        t_iqn: Target IQN
        i_iqn: Initiator IQN (creates ACL if set)
        n_luns: Number of LUNs to create
        back_size: Backstore size in bytes (required if n_luns > 0)
    """
    target_wwn = t_iqn or DEFAULT_TARGET_IQN
    target = Iscsi(target_wwn=target_wwn)

    try:
        # Create target
        target.create_target()

        # Add ACL if needed
        if i_iqn:
            acl = ACL(target_wwn=target_wwn, initiator_wwn=i_iqn)
            acl.create_acl()

        # Add LUNs if needed
        if back_size and n_luns > 0:
            luns = IscsiLUN(target_wwn=target_wwn)
            for n in range(n_luns):
                name = f'backstore{n}'
                backstore = BackstoreFileio(name=name)
                backstore.create_backstore(size=str(back_size), file_or_dev=f'{name}_file')
                luns.create_lun(storage_object=backstore.path)

        yield target

    finally:
        target.delete_target()
        # Clean up backstore files
        for n in range(n_luns):
            Path(f'backstore{n}_file').unlink(missing_ok=True)

Filesystem / Pool Managers

Stratis

sts.fixtures.stratis_fixtures

Stratis test fixtures for pool, filesystem, encryption, and failure testing.

KeyContext pydantic-model

Bases: StsBaseModel

State for a registered test encryption key.

Show JSON schema:
{
  "$defs": {
    "Key": {
      "additionalProperties": false,
      "description": "Stratis encryption key management (kernel keyring).",
      "properties": {
        "config": {
          "$ref": "#/$defs/StratisConfig"
        }
      },
      "title": "Key",
      "type": "object"
    },
    "StratisConfig": {
      "additionalProperties": false,
      "description": "Stratis configuration controlling global CLI options.",
      "properties": {
        "unhyphenated_uuids": {
          "default": false,
          "title": "Unhyphenated Uuids",
          "type": "boolean"
        }
      },
      "title": "StratisConfig",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a registered test encryption key.",
  "properties": {
    "key": {
      "$ref": "#/$defs/Key"
    },
    "key_desc": {
      "title": "Key Desc",
      "type": "string"
    }
  },
  "required": [
    "key",
    "key_desc"
  ],
  "title": "KeyContext",
  "type": "object"
}

Fields:

  • key (Key)
  • key_desc (str)
Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
46
47
48
49
50
class KeyContext(StsBaseModel):
    """State for a registered test encryption key."""

    key: Key
    key_desc: str

MountedFsContext pydantic-model

Bases: StsBaseModel

State for a pool with a filesystem mounted for I/O testing.

Show JSON schema:
{
  "$defs": {
    "BlockDevInfo": {
      "description": "Block device information from stratis report.",
      "properties": {
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Path"
        },
        "size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "in_use": {
          "default": false,
          "title": "In Use",
          "type": "boolean"
        },
        "blksizes": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Blksizes"
        },
        "clevis_config": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Clevis Config"
        },
        "clevis_pin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Clevis Pin"
        },
        "key_description": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Key Description"
        }
      },
      "title": "BlockDevInfo",
      "type": "object"
    },
    "BlockDevs": {
      "description": "Block devices in a Stratis pool.\n\nPools have two storage tiers: ``datadevs`` for primary data storage and ``cachedevs`` for an\noptional fast cache layer (typically SSDs accelerating HDDs).",
      "properties": {
        "datadevs": {
          "items": {
            "$ref": "#/$defs/BlockDevInfo"
          },
          "title": "Datadevs",
          "type": "array"
        },
        "cachedevs": {
          "items": {
            "$ref": "#/$defs/BlockDevInfo"
          },
          "title": "Cachedevs",
          "type": "array"
        }
      },
      "title": "BlockDevs",
      "type": "object"
    },
    "EncryptionInfo": {
      "additionalProperties": false,
      "description": "Encryption metadata for a pool (key descriptions and Clevis bindings).\n\nUses `StsBaseModel` rather than `ReportModel`: `PoolReport` and `StratisPool`\nare mutable and reassign `self.encryption` wholesale (see\n`_parse_pool_interface`), which a frozen `ReportModel` would reject.\n\nv1 pools have at most one binding (singular ``KeyDescription``/``ClevisInfo``\nin the D-Bus interface); v2 pools support multiple (plural\n``KeyDescriptions``/``ClevisInfos``). Both are normalized to the plural\nfield names here \u2014 see `PoolReport._parse_pool_interface`.",
      "properties": {
        "key_descriptions": {
          "default": null,
          "title": "Key Descriptions"
        },
        "clevis_infos": {
          "default": null,
          "title": "Clevis Infos"
        }
      },
      "title": "EncryptionInfo",
      "type": "object"
    },
    "PoolReport": {
      "additionalProperties": false,
      "description": "Pool report data.\n\nMutable model that fetches and holds pool metadata from stratisd.\nCall ``refresh()`` after construction to populate from the system.",
      "properties": {
        "config": {
          "$ref": "#/$defs/StratisConfig"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "blockdevs": {
          "$ref": "#/$defs/BlockDevs"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "encryption": {
          "$ref": "#/$defs/EncryptionInfo"
        },
        "encrypted": {
          "default": false,
          "title": "Encrypted",
          "type": "boolean"
        },
        "last_reencrypted_timestamp": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Last Reencrypted Timestamp"
        },
        "fs_limit": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Fs Limit"
        },
        "available_actions": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Available Actions"
        },
        "filesystems": {
          "items": {
            "type": "string"
          },
          "title": "Filesystems",
          "type": "array"
        },
        "raw_data": {
          "additionalProperties": true,
          "title": "Raw Data",
          "type": "object"
        },
        "total_size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Total Size"
        },
        "used_size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Used Size"
        },
        "object_path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Object Path"
        },
        "prevent_update": {
          "default": false,
          "title": "Prevent Update",
          "type": "boolean"
        }
      },
      "title": "PoolReport",
      "type": "object"
    },
    "StratisConfig": {
      "additionalProperties": false,
      "description": "Stratis configuration controlling global CLI options.",
      "properties": {
        "unhyphenated_uuids": {
          "default": false,
          "title": "Unhyphenated Uuids",
          "type": "boolean"
        }
      },
      "title": "StratisConfig",
      "type": "object"
    },
    "StratisFilesystem": {
      "additionalProperties": false,
      "description": "Stratis filesystem representation.\n\nManages filesystems with thin provisioning, snapshots, and size\nmanagement. Call ``update_from_managed_objects()`` after construction\nto populate size/used/limit from stratisd.\n\nExample:\n    ```python\n    fs = StratisFilesystem(name='fs1', pool_name='pool1')\n    fs.create()\n    ```",
      "properties": {
        "config": {
          "$ref": "#/$defs/StratisConfig"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "size_limit": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size Limit"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "used": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Used"
        }
      },
      "title": "StratisFilesystem",
      "type": "object"
    },
    "StratisPool": {
      "additionalProperties": false,
      "description": "Stratis pool representation.\n\nManages Stratis pools including creation, encryption, and cache.\nCall ``refresh_report()`` after construction to populate report data.\n\nExample:\n    ```python\n    pool = StratisPool(name='pool1', blockdevs=['/dev/sda'])\n    pool.create()  # create() calls refresh_report() internally\n    ```",
      "properties": {
        "config": {
          "$ref": "#/$defs/StratisConfig"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "encryption": {
          "$ref": "#/$defs/EncryptionInfo"
        },
        "blockdevs": {
          "items": {
            "type": "string"
          },
          "title": "Blockdevs",
          "type": "array"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/PoolReport"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "prevent_report_updates": {
          "default": false,
          "title": "Prevent Report Updates",
          "type": "boolean"
        }
      },
      "title": "StratisPool",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a pool with a filesystem mounted for I/O testing.",
  "properties": {
    "pool": {
      "$ref": "#/$defs/StratisPool"
    },
    "filesystem": {
      "$ref": "#/$defs/StratisFilesystem"
    },
    "mount_point": {
      "title": "Mount Point",
      "type": "string"
    }
  },
  "required": [
    "pool",
    "filesystem",
    "mount_point"
  ],
  "title": "MountedFsContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
38
39
40
41
42
43
class MountedFsContext(StsBaseModel):
    """State for a pool with a filesystem mounted for I/O testing."""

    pool: StratisPool
    filesystem: StratisFilesystem
    mount_point: str

SnapshotContext pydantic-model

Bases: StsBaseModel

State for a filesystem with a snapshot.

Show JSON schema:
{
  "$defs": {
    "StratisConfig": {
      "additionalProperties": false,
      "description": "Stratis configuration controlling global CLI options.",
      "properties": {
        "unhyphenated_uuids": {
          "default": false,
          "title": "Unhyphenated Uuids",
          "type": "boolean"
        }
      },
      "title": "StratisConfig",
      "type": "object"
    },
    "StratisFilesystem": {
      "additionalProperties": false,
      "description": "Stratis filesystem representation.\n\nManages filesystems with thin provisioning, snapshots, and size\nmanagement. Call ``update_from_managed_objects()`` after construction\nto populate size/used/limit from stratisd.\n\nExample:\n    ```python\n    fs = StratisFilesystem(name='fs1', pool_name='pool1')\n    fs.create()\n    ```",
      "properties": {
        "config": {
          "$ref": "#/$defs/StratisConfig"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "pool_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pool Name"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "size": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "size_limit": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size Limit"
        },
        "origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "used": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Used"
        }
      },
      "title": "StratisFilesystem",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a filesystem with a snapshot.",
  "properties": {
    "filesystem": {
      "$ref": "#/$defs/StratisFilesystem"
    },
    "snapshot": {
      "$ref": "#/$defs/StratisFilesystem"
    }
  },
  "required": [
    "filesystem",
    "snapshot"
  ],
  "title": "SnapshotContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
31
32
33
34
35
class SnapshotContext(StsBaseModel):
    """State for a filesystem with a snapshot."""

    filesystem: StratisFilesystem
    snapshot: StratisFilesystem

setup_stratis_key()

Register a Stratis encryption key, yielding its description. Cleaned up on teardown.

Configurable via STRATIS_KEY_DESC, STRATIS_KEY_PATH, and STRATIS_KEY env vars.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@pytest.fixture
def setup_stratis_key() -> Generator[str, None, None]:
    """Register a Stratis encryption key, yielding its description. Cleaned up on teardown.

    Configurable via STRATIS_KEY_DESC, STRATIS_KEY_PATH, and STRATIS_KEY env vars.
    """
    stratis_key = Key()
    keydesc = getenv('STRATIS_KEY_DESC', 'sts-stratis-test-key')
    keypath = getenv('STRATIS_KEY_PATH', '/tmp/sts-stratis-test-key')
    key = getenv('STRATIS_KEY', 'Stra123tisKey45')

    # Create key file
    keyp = Path(keypath)
    keyp.write_text(key)
    assert keyp.is_file()

    # Register key with Stratis
    assert stratis_key.set(keydesc=keydesc, keyfile_path=keypath).succeeded

    yield keydesc

    # Clean up
    assert stratis_key.unset(keydesc).succeeded
    keyp.unlink()
    assert not keyp.is_file()

stratis_clevis_test()

Start a Tang server and yield {'thumbprint': ..., 'url': ...} for Clevis encryption.

Installs tang, curl, jose, jq. Stops Tang service on teardown.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
@pytest.fixture
def stratis_clevis_test() -> Generator[dict[str, str], None, None]:
    """Start a Tang server and yield ``{'thumbprint': ..., 'url': ...}`` for Clevis encryption.

    Installs tang, curl, jose, jq. Stops Tang service on teardown.
    """
    system = SystemManager()
    system_info = SystemInfo()
    tang_service = 'tangd.socket'

    # Install required packages
    required_packages = ['tang', 'curl', 'jose', 'jq', 'coreutils']
    assert ensure_installed(*required_packages), 'Failed to install required packages'

    # Start Tang service if not running
    if not system.is_service_running(tang_service):
        assert system.service_start(tang_service), f'Failed to start {tang_service}'

    # Get server thumbprint
    cmd = (
        f'curl -s {system_info.hostname}/adv | '
        f'jq -r .payload | '
        f'base64 -d | '
        f'jose jwk use -i- -r -u verify -o- | '
        f'jose jwk thp -i-'
    )
    result = run(cmd=cmd)
    assert result.succeeded, 'Failed to get Tang server thumbprint'
    assert result.stdout.strip(), 'Empty thumbprint received'

    # Prepare server information
    clevis_info = {'thumbprint': result.stdout.strip(), 'url': f'http://{system_info.hostname}'}

    yield clevis_info

    # Clean up
    if system.is_service_running(tang_service):
        assert system.service_stop(tang_service), f'Failed to stop {tang_service}'

stratis_encrypted_pool(loop_devices, setup_stratis_key)

Create a key-encrypted Stratis pool from loop devices.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
@pytest.fixture
def stratis_encrypted_pool(
    loop_devices: list[LoopDevice], setup_stratis_key: str
) -> Generator[StratisPool, None, None]:
    """Create a key-encrypted Stratis pool from loop devices."""
    pool = StratisPool(name='sts-stratis-test-pool', blockdevs=[str(dev.path) for dev in loop_devices])

    # Create encrypted pool
    config = PoolCreateConfig(key_desc=setup_stratis_key)
    if pool.create(config).failed:
        pytest.skip('Failed to create encrypted test pool')

    yield pool

    # Clean up
    pool.destroy()

stratis_extend_lvm(_lvm_test, loop_devices)

Create a 70%vg LV on the 3rd and 4th loop devices for stratis extend-data testing.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
@pytest.fixture
def stratis_extend_lvm(
    _lvm_test: None,
    loop_devices: list[LoopDevice],
) -> Generator[LogicalVolume, None, None]:
    """Create a 70%vg LV on the 3rd and 4th loop devices for stratis extend-data testing."""
    vg_name = getenv('STRATIS_VG_NAME', 'sts-stratis-volume-group')
    lv_name = getenv('STRATIS_LV_NAME', 'sts-stratis-logical-volume')
    pvs: list[PhysicalVolume] = []
    assert len(loop_devices) > 4, 'Not enough loop devices is available'
    devices = [str(dev.path) for dev in loop_devices[2:4]]
    try:
        # Create PVs
        for device in devices:
            pv = PhysicalVolume(name=device, path=device)
            pv.create().assert_ok(f'Failed to create PV on device {device}')
            pvs.append(pv)

        # Create VG
        vg = VolumeGroup(name=vg_name, pvs=devices)
        vg.create().assert_ok(f'Failed to create VG {vg_name}')
        lv = LogicalVolume(name=lv_name, vg=vg_name)
        lv.create(extents='70%vg').assert_ok()
        yield lv

    finally:
        # Cleanup in reverse order
        vg = VolumeGroup(name=vg_name)
        if vg.remove().failed:
            logger.warning(f'Failed to remove VG {vg_name}')

        for pv in pvs:
            if pv.remove().failed:
                logger.warning(f'Failed to remove PV {pv.path}')

stratis_failing_pool(scsi_debug_devices)

Create a Stratis pool on an SCSI debug device with failure injection enabled.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
@pytest.fixture
def stratis_failing_pool(scsi_debug_devices: list[str]) -> Generator[StratisPool, None, None]:
    """Create a Stratis pool on an SCSI debug device with failure injection enabled."""
    # Get first device for injection
    device = ScsiDebugDevice(path=scsi_debug_devices[0])

    # Inject failures (every operation fails with noisy error)
    device.inject_failure(every_nth=1, opts=1)

    # Create pool
    pool = StratisPool(name='sts-stratis-test-pool', blockdevs=[scsi_debug_devices[0]])  # Only use first device

    if pool.create().failed:
        pytest.skip('Failed to create test pool')

    yield pool

    # Clean up
    pool.destroy()

stratis_filesystem(stratis_no_enc_pool)

Create a test filesystem on a non-encrypted pool, destroyed on teardown.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
331
332
333
334
335
336
337
@pytest.fixture
def stratis_filesystem(stratis_no_enc_pool: StratisPool) -> Generator[StratisFilesystem, None, None]:
    """Create a test filesystem on a non-encrypted pool, destroyed on teardown."""
    fs = StratisFilesystem(name='sts-stratis-test-fs', pool_name=stratis_no_enc_pool.name)
    fs.create().assert_ok('Failed to create test filesystem')
    yield fs
    fs.destroy()

stratis_filesystem_with_snapshot(stratis_filesystem)

Create a snapshot of the stratis_filesystem, cleaned up on teardown.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
340
341
342
343
344
345
346
347
348
349
350
@pytest.fixture
def stratis_filesystem_with_snapshot(
    stratis_filesystem: StratisFilesystem,
) -> Generator[SnapshotContext, None, None]:
    """Create a snapshot of the stratis_filesystem, cleaned up on teardown."""
    snap = stratis_filesystem.snapshot('sts-stratis-test-fs-snap')
    assert snap is not None, 'Failed to create test snapshot'
    yield SnapshotContext(filesystem=stratis_filesystem, snapshot=snap)
    # Cancel any scheduled revert before destroying (no-op if not scheduled)
    snap.cancel_revert()
    snap.destroy()

stratis_inplace_key_desc_pool(loop_devices, setup_stratis_key)

Create an unencrypted pool, then encrypt it in-place with keyring.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
@pytest.fixture
def stratis_inplace_key_desc_pool(
    loop_devices: list[LoopDevice], setup_stratis_key: str
) -> Generator[StratisPool, None, None]:
    """Create an unencrypted pool, then encrypt it in-place with keyring."""
    pool = StratisPool(
        name='sts-stratis-test-pool',
        blockdevs=[str(dev.path) for dev in loop_devices[:2]],
    )

    if pool.version < ENCRYPTION_ON_OFF_MIN_VERSION:
        pytest.skip(f'Requires stratis >= {ENCRYPTION_ON_OFF_MIN_VERSION}')

    pool_created = False
    try:
        pool.create().assert_ok()
        pool_created = True
        pool.encryption_on(key_desc=setup_stratis_key, in_place=True).assert_ok()

        for _ in range(120):
            time.sleep(5)
            pool.refresh_report()
            if pool.report and pool.report.encrypted:
                break
        else:
            pytest.fail('Pool encryption did not complete within timeout')

        yield pool
    finally:
        if pool_created:
            _teardown_pool(pool)

stratis_key_desc_pool(loop_devices, setup_stratis_key)

Create a pool with keyring encryption.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
199
200
201
202
203
204
205
206
207
208
209
@pytest.fixture
def stratis_key_desc_pool(loop_devices: list[LoopDevice], setup_stratis_key: str) -> Generator[StratisPool, None, None]:
    """Create a pool with keyring encryption."""
    pool = StratisPool(
        name='sts-stratis-test-pool',
        blockdevs=[str(dev.path) for dev in loop_devices[:2]],  # Use first two devices initially
    )
    config = PoolCreateConfig(key_desc=setup_stratis_key)
    pool.create(config).assert_ok()
    yield pool
    _teardown_pool(pool)

stratis_mounted_fs(stratis_no_enc_pool)

Create a filesystem on the non-encrypted pool and mount it at /mnt/sts-stratis-test-fs.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
@pytest.fixture
def stratis_mounted_fs(
    stratis_no_enc_pool: StratisPool,
) -> Generator[MountedFsContext, None, None]:
    """Create a filesystem on the non-encrypted pool and mount it at /mnt/sts-stratis-test-fs."""
    pool = stratis_no_enc_pool
    mount_point = '/mnt/sts-stratis-test-fs'

    fs = StratisFilesystem(name='sts-stratis-test-fs', pool_name=pool.name)
    fs.create().assert_ok('Failed to create test filesystem')

    assert run(f'mkdir -p {mount_point}').succeeded, f'Failed to create mount point {mount_point}'
    dev_path = f'/dev/stratis/{pool.name}/{fs.name}'
    result = run(f'mount {dev_path} {mount_point}')
    assert result.succeeded, f'Failed to mount filesystem: {result.stderr}'

    yield MountedFsContext(pool=pool, filesystem=fs, mount_point=mount_point)

    run(f'umount {mount_point}')
    fs.destroy()
    run(f'rmdir {mount_point}')

stratis_no_enc_pool(loop_devices)

Create a pool without encryption.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
265
266
267
268
269
270
271
272
273
274
@pytest.fixture
def stratis_no_enc_pool(loop_devices: list[LoopDevice]) -> Generator[StratisPool, None, None]:
    """Create a pool without encryption."""
    pool = StratisPool(
        name='sts-stratis-test-pool',
        blockdevs=[str(dev.path) for dev in loop_devices[:2]],  # Use first two devices initially
    )
    pool.create().assert_ok()
    yield pool
    _teardown_pool(pool)

stratis_tang_pool(loop_devices, stratis_clevis_test)

Create a pool with Tang encryption.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
@pytest.fixture
def stratis_tang_pool(
    loop_devices: list[LoopDevice], stratis_clevis_test: dict[str, str]
) -> Generator[StratisPool, None, None]:
    """Create a pool with Tang encryption."""
    pool = StratisPool(
        name='sts-stratis-test-pool',
        blockdevs=[str(dev.path) for dev in loop_devices[:2]],  # Use first two devices initially
    )
    config = PoolCreateConfig(
        clevis='tang', tang_url=stratis_clevis_test['url'], thumbprint=stratis_clevis_test['thumbprint']
    )
    pool.create(config).assert_ok()
    yield pool
    _teardown_pool(pool)

stratis_test_key(tmp_path)

Register a test encryption key, unset on teardown.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
376
377
378
379
380
381
382
383
384
385
386
387
@pytest.fixture
def stratis_test_key(tmp_path: Path) -> Generator[KeyContext, None, None]:
    """Register a test encryption key, unset on teardown."""
    key = Key()
    keydesc = 'sts-stratis-test-key-fixture'
    keyfile = tmp_path / 'keyfile'
    keyfile.write_text('FixtureTestKey123')
    result = key.set(keydesc=keydesc, keyfile_path=str(keyfile))
    assert result.succeeded, f'Failed to set test key: {result.stderr}'
    yield KeyContext(key=key, key_desc=keydesc)
    if key.exists(keydesc):
        key.unset(keydesc)

stratis_test_pool(loop_devices)

Create a non-encrypted Stratis pool from loop devices.

Source code in sts_libs/src/sts/fixtures/stratis_fixtures.py
166
167
168
169
170
171
172
173
174
175
176
177
178
@pytest.fixture
def stratis_test_pool(loop_devices: list[LoopDevice]) -> Generator[StratisPool, None, None]:
    """Create a non-encrypted Stratis pool from loop devices."""
    pool = StratisPool(name='sts-stratis-test-pool', blockdevs=[str(dev.path) for dev in loop_devices])

    # Create pool
    if pool.create().failed:
        pytest.skip('Failed to create test pool')

    yield pool

    # Clean up
    pool.destroy()

VDO

sts.fixtures.vdo_fixtures

VDO (Virtual Data Optimizer) test fixtures for module loading and lifecycle.

load_vdo_module(_lvm_test)

Install VDO and load the appropriate kernel module (dm-vdo or kvdo).

Returns the module name. Uses dm-vdo for kernel 6.9+, kvdo for older kernels.

Source code in sts_libs/src/sts/fixtures/vdo_fixtures.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@pytest.fixture(scope='class')
def load_vdo_module(_lvm_test: None) -> str:
    """Install VDO and load the appropriate kernel module (dm-vdo or kvdo).

    Returns the module name. Uses dm-vdo for kernel 6.9+, kvdo for older kernels.
    """
    module = 'dm_vdo'
    system = SystemManager()
    assert system.package_manager.install(VDO_PACKAGE_NAME)
    log_package_versions(VDO_PACKAGE_NAME)
    try:
        k_version = system.info.kernel
        if k_version:
            k_version = k_version.split('.')
            # dm-vdo is available from kernel 6.9, for older version it's available
            # from kmod-kvdo package
            if int(k_version[0]) < 6 or (int(k_version[0]) == 6 and int(k_version[1]) <= 8):
                logger.debug('Using kmod-kvdo')
                assert system.package_manager.install('kmod-kvdo')
                log_package_versions('kmod-kvdo')
                module = 'kvdo'
    except (ValueError, IndexError):
        # if we can't get kernel version, just try to load dm-vdo
        logger.warning('Unable to parse kernel version; defaulting to dm-vdo')

    kmod = ModuleManager()
    ret = kmod.load(name=module)
    if not ret:
        stdout = run('find /lib/modules -name kvdo.ko')
        logger.debug(f'Modules found: {stdout.stdout}')
        modules = stdout.stdout.strip().splitlines()
        if modules:
            out = run(f'insmod {modules[0]}')
            logger.debug(f'Insmod output: {out.stdout}')
            logger.debug(f'Insmod error: {out.stderr}')
            ret = out.succeeded
    assert ret
    logger.debug(f'Successfully loaded {module} module')

    return module

Boom

sts.fixtures.boom_fixtures

Boom (boom-boot) test fixtures.

default_boom_entry(profile_from_host)

Yield a cloned BoomEntry from the default boot entry, deleted on teardown.

Source code in sts_libs/src/sts/fixtures/boom_fixtures.py
57
58
59
60
61
62
63
64
65
66
67
68
69
@pytest.fixture
def default_boom_entry(profile_from_host: BoomProfile) -> Generator[BoomEntry, None, None]:
    """Yield a cloned BoomEntry from the default boot entry, deleted on teardown."""
    _ = profile_from_host
    entry = None
    entries = BoomEntry().get_all()
    if entries:
        entry = entries[0].clone()
    if not entry:
        pytest.fail('Unable to create a clone of the default Boom entry for tests')
    yield entry

    entry.delete()

default_host_profile(profile_from_host)

Yield a BoomHost created from the profile_from_host fixture, deleted on teardown.

Source code in sts_libs/src/sts/fixtures/boom_fixtures.py
44
45
46
47
48
49
50
51
52
53
54
@pytest.fixture
def default_host_profile(profile_from_host: BoomProfile) -> Generator[BoomHost, None, None]:
    """Yield a BoomHost created from the profile_from_host fixture, deleted on teardown."""
    os_profile = profile_from_host
    host = BoomHost()
    host.create(profile_id=os_profile.os_id).assert_ok()
    assert host.host_id is not None

    yield host

    host.delete()

profile_from_host()

Yield a BoomProfile created from host data, deleted on teardown.

Source code in sts_libs/src/sts/fixtures/boom_fixtures.py
32
33
34
35
36
37
38
39
40
41
@pytest.fixture
def profile_from_host() -> Generator[BoomProfile, None, None]:
    """Yield a BoomProfile created from host data, deleted on teardown."""
    profile = BoomProfile()
    if profile.create(from_host=True).failed:
        pytest.fail('Unable to create profile from host!')

    yield profile

    profile.delete()

Snapm

sts.fixtures.snapm_fixtures

Snapm (Snapshot Manager) test fixtures.

SnapsetPairContext pydantic-model

Bases: StsBaseModel

State for a pair of snapsets created for diff testing.

Show JSON schema:
{
  "$defs": {
    "Snapset": {
      "additionalProperties": false,
      "description": "Snapset management.\n\nA Snapset is a collection of snapshots across multiple filesystems.",
      "properties": {
        "debugopts": {
          "anyOf": [
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Debugopts"
        },
        "verbose": {
          "default": false,
          "title": "Verbose",
          "type": "boolean"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "uuid": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "report": {
          "anyOf": [
            {
              "$ref": "#/$defs/SnapsetInfo"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "title": "Snapset",
      "type": "object"
    },
    "SnapsetInfo": {
      "description": "Snapset information parsed from ``snapm snapset show`` output.",
      "properties": {
        "SnapsetName": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snapsetname"
        },
        "Sources": {
          "items": {
            "type": "string"
          },
          "title": "Sources",
          "type": "array"
        },
        "MountPoints": {
          "items": {
            "type": "string"
          },
          "title": "Mountpoints",
          "type": "array"
        },
        "Devices": {
          "items": {
            "type": "string"
          },
          "title": "Devices",
          "type": "array"
        },
        "NrSnapshots": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Nrsnapshots"
        },
        "Timestamp": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Timestamp"
        },
        "Time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Time"
        },
        "UUID": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "Status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Status"
        },
        "Autoactivate": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Autoactivate"
        },
        "Bootable": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Bootable"
        },
        "BootEntries": {
          "additionalProperties": {
            "type": "string"
          },
          "title": "Bootentries",
          "type": "object"
        },
        "Snapshots": {
          "items": {
            "$ref": "#/$defs/SnapshotInfo"
          },
          "title": "Snapshots",
          "type": "array"
        }
      },
      "title": "SnapsetInfo",
      "type": "object"
    },
    "SnapshotInfo": {
      "description": "Snapshot information parsed from ``snapm snapshot show`` output.",
      "properties": {
        "Name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "SnapsetName": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Snapsetname"
        },
        "Origin": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Origin"
        },
        "Timestamp": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Timestamp"
        },
        "Time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Time"
        },
        "Source": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Source"
        },
        "MountPoint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Mountpoint"
        },
        "Provider": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Provider"
        },
        "UUID": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Uuid"
        },
        "Status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Status"
        },
        "Size": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        },
        "Free": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Free"
        },
        "SizeBytes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Sizebytes"
        },
        "FreeBytes": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Freebytes"
        },
        "Autoactivate": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Autoactivate"
        },
        "DevicePath": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Devicepath"
        }
      },
      "title": "SnapshotInfo",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "State for a pair of snapsets created for diff testing.",
  "properties": {
    "snapset1": {
      "$ref": "#/$defs/Snapset"
    },
    "snapset2": {
      "$ref": "#/$defs/Snapset"
    },
    "mount_point": {
      "title": "Mount Point",
      "type": "string"
    }
  },
  "required": [
    "snapset1",
    "snapset2",
    "mount_point"
  ],
  "title": "SnapsetPairContext",
  "type": "object"
}

Fields:

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
34
35
36
37
38
39
class SnapsetPairContext(StsBaseModel):
    """State for a pair of snapsets created for diff testing."""

    snapset1: Snapset
    snapset2: Snapset
    mount_point: str

mounted_snapset(_snapm_test, mount_lv_fixture, request)

Create a snapset, mount it, and clean up after the test.

Handles version checking, snapset creation, mounting (skips if mount is unsupported), and full cleanup (umount + delete).

The snapset name is derived from the test name so each test gets a unique snapset. The mount root is at /run/snapm/mounts/<snapset.name>.

Example
@LVM_THIN_FIXTURE
def test_exec(mounted_snapset):
    snapset = mounted_snapset
    result = snapset.exec_cmd('cat', '/etc/os-release')
    assert result.succeeded

Yields:

Name Type Description
Snapset Snapset

A mounted snapset instance

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
@pytest.fixture
def mounted_snapset(
    _snapm_test: None,
    mount_lv_fixture: Directory,
    request: pytest.FixtureRequest,
) -> Generator[Snapset, None, None]:
    """Create a snapset, mount it, and clean up after the test.

    Handles version checking, snapset creation, mounting (skips if
    mount is unsupported), and full cleanup (umount + delete).

    The snapset name is derived from the test name so each test gets
    a unique snapset. The mount root is at
    ``/run/snapm/mounts/<snapset.name>``.

    Example:
        ```python
        @LVM_THIN_FIXTURE
        def test_exec(mounted_snapset):
            snapset = mounted_snapset
            result = snapset.exec_cmd('cat', '/etc/os-release')
            assert result.succeeded
        ```

    Yields:
        Snapset: A mounted snapset instance
    """
    snapset = Snapset()
    if snapset.version < VersionInfo.from_string('0.6.0'):
        pytest.skip('requires snapm-0.6.0 or higher for mount manager support')

    name = _snapset_name_from_test(request)
    snapset.create(
        snapset_name=name,
        sources=[str(mount_lv_fixture.path)],
    )

    mount_result = snapset.mount()
    if not mount_result.succeeded:
        snapset.delete()
        pytest.skip(f'snapset mount not supported: {mount_result.stderr}')

    yield snapset

    snapset.umount()
    snapset.delete()

mounted_snapset_setup(_snapm_test, request)

Create and mount a snapset, with automatic unmount and cleanup.

Skips if mount is not supported by the snapm version.

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@pytest.fixture
def mounted_snapset_setup(
    _snapm_test: None,
    request: pytest.FixtureRequest,
) -> Generator[Snapset, None, None]:
    """Create and mount a snapset, with automatic unmount and cleanup.

    Skips if mount is not supported by the snapm version.
    """
    # Get parameters from request
    params: dict[str, Any] = getattr(request, 'param', {})

    snapset_name = params.get('snapset_name', 'sts-mounted-snapset')
    sources = params.get('sources')
    sources_fixture = params.get('sources_fixture')
    size_policy = params.get('size_policy')

    # Get sources from fixture if sources_fixture is specified
    if not sources and sources_fixture:
        fixture_value: Any = request.getfixturevalue(sources_fixture)
        if isinstance(fixture_value, list):
            sources = [str(mp.path) for mp in cast('list[Any]', fixture_value)]
        else:
            sources = [str(fixture_value.path)]

    # Create snapset
    snapset = Snapset()
    snapset.create(
        snapset_name=snapset_name,
        sources=sources,
        size_policy=size_policy,
    )

    # Mount the snapset
    mount_result = snapset.mount()
    if not mount_result.succeeded:
        # Cleanup and skip if mount not supported
        snapset.delete()
        pytest.skip(f'snapset mount not supported: {mount_result.stderr}')

    yield snapset

    # Cleanup - unmount then delete
    snapset.umount()
    snapset.delete()

schedule_setup(_snapm_test, request)

Create a snapm schedule with automatic cleanup.

Requires snapm >= 0.5.1. Parametrize sources or sources_fixture, policy_type, calendarspec, and keep_* options via indirect.

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
@pytest.fixture
def schedule_setup(
    _snapm_test: None,
    request: pytest.FixtureRequest,
) -> Generator[Schedule, None, None]:
    """Create a snapm schedule with automatic cleanup.

    Requires snapm >= 0.5.1. Parametrize ``sources`` or ``sources_fixture``,
    ``policy_type``, ``calendarspec``, and ``keep_*`` options via indirect.
    """
    # Check version
    schedule = Schedule()
    if schedule.version < VersionInfo.from_string('0.5.1'):
        pytest.skip('requires snapm-0.5.1 or higher for schedule support')

    # Get parameters from request
    params: dict[str, Any] = getattr(request, 'param', {})

    schedule_name = params.get('schedule_name', getenv('STS_SCHEDULE_NAME', DEFAULT_SCHEDULE_NAME))
    sources = params.get('sources')
    sources_fixture = params.get('sources_fixture')
    policy_type = params.get('policy_type', 'ALL')
    calendarspec = params.get('calendarspec', 'daily')
    size_policy = params.get('size_policy')
    bootable = params.get('bootable', False)
    revert = params.get('revert', False)

    # Extract keep_* options
    keep_options = {k: v for k, v in params.items() if k.startswith('keep_')}

    # Get sources from fixture if sources_fixture is specified
    if not sources and sources_fixture:
        fixture_value: Any = request.getfixturevalue(sources_fixture)
        if isinstance(fixture_value, list):
            sources = [str(mp.path) for mp in cast('list[Any]', fixture_value)]
        else:
            sources = [str(fixture_value.path)]

    # Create schedule
    schedule.create(
        schedule_name=schedule_name,
        sources=sources,
        policy_type=policy_type,
        calendarspec=calendarspec,
        size_policy=size_policy,
        bootable=bootable,
        revert=revert,
        **keep_options,
    )

    yield schedule

    # Cleanup
    _cleanup_schedule(schedule)

snapset_pair(_snapm_test, mount_lv_fixture, request)

Create a pair of snapsets for diff testing with automatic cleanup.

This fixture creates two snapsets from the LVM mount point. Uses the test name to generate unique snapset names.

The mount_point field should be used as start_path in diff commands to limit comparison scope.

Example
@LVM_THIN_FIXTURE
def test_diff(snapset_pair):
    ctx = snapset_pair
    result = Snapset().diff(ctx.snapset1.name, ctx.snapset2.name, start_path=ctx.mount_point)
    # Cleanup is automatic

Yields:

Name Type Description
SnapsetPairContext SnapsetPairContext

The two snapsets and the mount point path

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
@pytest.fixture
def snapset_pair(
    _snapm_test: None,
    mount_lv_fixture: Directory,
    request: pytest.FixtureRequest,
) -> Generator[SnapsetPairContext, None, None]:
    """Create a pair of snapsets for diff testing with automatic cleanup.

    This fixture creates two snapsets from the LVM mount point.
    Uses the test name to generate unique snapset names.

    The mount_point field should be used as start_path in diff commands
    to limit comparison scope.

    Example:
        ```python
        @LVM_THIN_FIXTURE
        def test_diff(snapset_pair):
            ctx = snapset_pair
            result = Snapset().diff(ctx.snapset1.name, ctx.snapset2.name, start_path=ctx.mount_point)
            # Cleanup is automatic
        ```

    Yields:
        SnapsetPairContext: The two snapsets and the mount point path
    """
    mount_point = mount_lv_fixture
    name_prefix = _snapset_name_from_test(request)

    snapset1 = Snapset()
    snapset2 = Snapset()

    snapset1.create(
        snapset_name=f'{name_prefix}-1',
        sources=[str(mount_point.path)],
    )

    # Ensure distinct timestamps so snapsets are ordered deterministically
    sleep(0.5)

    snapset2.create(
        snapset_name=f'{name_prefix}-2',
        sources=[str(mount_point.path)],
    )

    yield SnapsetPairContext(snapset1=snapset1, snapset2=snapset2, mount_point=str(mount_point.path))

    snapset2.delete()
    snapset1.delete()

snapset_pair_setup(_snapm_test, request)

Create two snapsets from the same sources for diff testing.

Optionally writes a file change between creations (create_change=True, the default).

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
@pytest.fixture
def snapset_pair_setup(
    _snapm_test: None,
    request: pytest.FixtureRequest,
) -> Generator[tuple[Snapset, Snapset], None, None]:
    """Create two snapsets from the same sources for diff testing.

    Optionally writes a file change between creations (``create_change=True``, the default).
    """
    # Get parameters from request
    params: dict[str, Any] = getattr(request, 'param', {})

    snapset1_name = params.get('snapset1_name', 'sts-diff-snap1')
    snapset2_name = params.get('snapset2_name', 'sts-diff-snap2')
    sources = params.get('sources')
    sources_fixture = params.get('sources_fixture')
    size_policy = params.get('size_policy')
    create_change = params.get('create_change', True)

    # Get sources from fixture if sources_fixture is specified
    mount_point: Any = None
    if not sources and sources_fixture:
        fixture_value: Any = request.getfixturevalue(sources_fixture)
        if isinstance(fixture_value, list):
            typed_list = cast('list[Any]', fixture_value)
            sources = [str(mp.path) for mp in typed_list]
            mount_point = typed_list[0]  # Use first for file changes
        else:
            mount_point = fixture_value
            sources = [str(mount_point.path)]

    # Create first snapset
    snapset1 = Snapset()
    snapset1.create(
        snapset_name=snapset1_name,
        sources=sources,
        size_policy=size_policy,
    )

    # Create a change in the source if requested
    test_file = None
    if create_change and mount_point:
        test_file = Path(mount_point.path) / 'diff_test_marker.txt'
        test_file.write_text('Content added between snapsets for diff testing')

    # Create second snapset
    snapset2 = Snapset()
    snapset2.create(
        snapset_name=snapset2_name,
        sources=sources,
        size_policy=size_policy,
    )

    yield snapset1, snapset2

    # Cleanup
    if test_file and test_file.exists():
        test_file.unlink()

    snapset2.umount()
    snapset2.delete()
    snapset1.umount()
    snapset1.delete()

snapset_pair_with_change(_snapm_test, mount_lv_fixture, request)

Create a pair of snapsets with a file change between them.

Like snapset_pair, but writes a marker file before the first snapset and modifies it before the second, guaranteeing that the Difference Engine has real content changes to report.

Example
@LVM_THIN_FIXTURE
def test_diff_format(snapset_pair_with_change):
    ctx = snapset_pair_with_change
    result = Snapset().diff(
        ctx.snapset1.name,
        ctx.snapset2.name,
        start_path=ctx.mount_point,
        output_format='json',
    )

Yields:

Name Type Description
SnapsetPairContext SnapsetPairContext

The two snapsets and the mount point path

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
@pytest.fixture
def snapset_pair_with_change(
    _snapm_test: None,
    mount_lv_fixture: Directory,
    request: pytest.FixtureRequest,
) -> Generator[SnapsetPairContext, None, None]:
    """Create a pair of snapsets with a file change between them.

    Like ``snapset_pair``, but writes a marker file before the first
    snapset and modifies it before the second, guaranteeing that the
    Difference Engine has real content changes to report.

    Example:
        ```python
        @LVM_THIN_FIXTURE
        def test_diff_format(snapset_pair_with_change):
            ctx = snapset_pair_with_change
            result = Snapset().diff(
                ctx.snapset1.name,
                ctx.snapset2.name,
                start_path=ctx.mount_point,
                output_format='json',
            )
        ```

    Yields:
        SnapsetPairContext: The two snapsets and the mount point path
    """
    mount_point = mount_lv_fixture
    name_prefix = _snapset_name_from_test(request)

    marker = mount_point.path / 'diff_marker.txt'
    marker.write_text('initial content before first snapshot')

    snapset1 = Snapset()
    snapset1.create(
        snapset_name=f'{name_prefix}-1',
        sources=[str(mount_point.path)],
    )

    marker.write_text('modified content after first snapshot')
    # Ensure distinct timestamps so snapsets are ordered deterministically
    sleep(0.5)

    snapset2 = Snapset()
    snapset2.create(
        snapset_name=f'{name_prefix}-2',
        sources=[str(mount_point.path)],
    )

    yield SnapsetPairContext(snapset1=snapset1, snapset2=snapset2, mount_point=str(mount_point.path))

    if marker.exists():
        marker.unlink()
    snapset2.delete()
    snapset1.delete()

snapset_setup(_snapm_test, request)

Create a snapm snapset with automatic unmount and cleanup on teardown.

Source code in sts_libs/src/sts/fixtures/snapm_fixtures.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@pytest.fixture
def snapset_setup(
    _snapm_test: None,
    request: pytest.FixtureRequest,
) -> Generator[Snapset, None, None]:
    """Create a snapm snapset with automatic unmount and cleanup on teardown."""
    # Get parameters from request
    params: dict[str, Any] = getattr(request, 'param', {})

    snapset_name = params.get('snapset_name', getenv('STS_SNAPSET_NAME', 'sts-test-snapset'))
    sources = params.get('sources')
    sources_fixture = params.get('sources_fixture')
    size_policy = params.get('size_policy')
    bootable = params.get('bootable', False)
    revert = params.get('revert', False)
    autoindex = params.get('autoindex', False)

    # Get sources from fixture if sources_fixture is specified
    if not sources and sources_fixture:
        fixture_value: Any = request.getfixturevalue(sources_fixture)
        if isinstance(fixture_value, list):
            sources = [str(mp.path) for mp in cast('list[Any]', fixture_value)]
        else:
            sources = [str(fixture_value.path)]

    # Create snapset
    snapset = Snapset()
    snapset.create(
        snapset_name=snapset_name,
        sources=sources,
        size_policy=size_policy,
        bootable=bootable,
        revert=revert,
        autoindex=autoindex,
    )

    yield snapset

    # Cleanup - unmount if mounted, then delete
    snapset.umount()
    snapset.delete()

Hardware & Protocols

RDMA

sts.fixtures.rdma_fixtures

RDMA test fixtures.

rdma_device()

Return a factory function that creates an RdmaDevice by HCA ID.

Source code in sts_libs/src/sts/fixtures/rdma_fixtures.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@pytest.fixture(scope='class')
def rdma_device() -> Callable[[str], RdmaDeviceType]:
    """Return a factory function that creates an RdmaDevice by HCA ID."""

    def _device_factory(hca_id: str) -> RdmaDeviceType:
        """Create and validate an RDMA device instance.

        Args:
            hca_id: HCA ID (e.g. 'mlx5_0', 'mlx4_1')

        Returns:
            RDMA device instance
        """
        assert exists_device(hca_id), f'No RDMA device found: {hca_id}'
        return RdmaDevice(ibdev=hca_id).discover()

    return _device_factory

SG3 Utils

sts.fixtures.sg3_utils_fixtures

sg3_utils test fixtures for scsi_debug devices and direct I/O management.

ScsiDebugContext pydantic-model

Bases: StsBaseModel

Container for scsi_debug device paths discovered via sg_map.

Show JSON schema:
{
  "additionalProperties": false,
  "description": "Container for scsi_debug device paths discovered via sg_map.",
  "properties": {
    "sg_device": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Sg Device"
    },
    "sd_device": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Sd Device"
    }
  },
  "title": "ScsiDebugContext",
  "type": "object"
}

Fields:

  • sg_device (str | None)
  • sd_device (str | None)
Source code in sts_libs/src/sts/fixtures/sg3_utils_fixtures.py
31
32
33
34
35
class ScsiDebugContext(StsBaseModel):
    """Container for scsi_debug device paths discovered via sg_map."""

    sg_device: str | None = None
    sd_device: str | None = None

allow_dio()

Enable direct I/O via /proc/scsi/sg/allow_dio, restored on teardown.

Source code in sts_libs/src/sts/fixtures/sg3_utils_fixtures.py
76
77
78
79
80
81
82
83
84
@pytest.fixture
def allow_dio() -> Generator[None, None, None]:
    """Enable direct I/O via /proc/scsi/sg/allow_dio, restored on teardown."""
    if not ALLOW_DIO_PATH.exists():
        pytest.skip('allow_dio not available')
    original = ALLOW_DIO_PATH.read_text().strip()
    ALLOW_DIO_PATH.write_text('1')
    yield
    ALLOW_DIO_PATH.write_text(original)

scsi_debug(request)

Load scsi_debug and discover sg/sd device paths via sg_map.

Parametrize with [{'options': 'scsi_level=2'}] for custom module options.

Source code in sts_libs/src/sts/fixtures/sg3_utils_fixtures.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@pytest.fixture(scope='class')
def scsi_debug(request: pytest.FixtureRequest) -> Generator[ScsiDebugContext, None, None]:
    """Load scsi_debug and discover sg/sd device paths via sg_map.

    Parametrize with ``[{'options': 'scsi_level=2'}]`` for custom module options.
    """
    raw_params = getattr(request, 'param', {})
    options: str = cast('dict[str, Any]', raw_params).get('options', '') if isinstance(raw_params, dict) else ''

    ensure_installed('sg3_utils')

    _unload_scsi_debug()

    device = ScsiDebugDevice.create(options=options)
    if not device:
        pytest.skip('Failed to create SCSI debug device')

    ctx = ScsiDebugContext()
    map_result = run('sg_map -sd -i')
    if map_result.succeeded:
        for line in map_result.stdout.splitlines():
            if 'scsi_debug' in line:
                parts = line.split()
                if len(parts) >= 2:
                    ctx.sg_device = parts[0]
                    ctx.sd_device = parts[1]
                    break

    if not ctx.sg_device:
        device.remove()
        pytest.skip('No scsi_debug device discovered via sg_map')

    yield ctx

    run('udevadm settle')
    device.remove()

scsi_debug_devices(request)

Create multiple scsi_debug devices, settling udev before unload to avoid ModuleInUseError.

Source code in sts_libs/src/sts/fixtures/sg3_utils_fixtures.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@pytest.fixture(scope='class')
def scsi_debug_devices(request: pytest.FixtureRequest) -> Generator[list[str], None, None]:
    """Create multiple scsi_debug devices, settling udev before unload to avoid ModuleInUseError."""
    count = getattr(request, 'param', 1)
    # num_tgts * add_host: count targets per host, count hosts
    total = count**2

    _unload_scsi_debug()

    device = ScsiDebugDevice.create(
        size=1024 * 1024 * 1024,
        options=f'num_tgts={count} add_host={count}',
    )
    if not device:
        pytest.skip('Failed to create SCSI debug device')

    devices = ScsiDebugDevice.get_devices()
    if not devices or len(devices) < total:
        device.remove()
        pytest.skip(f'Expected {total} SCSI debug devices, got {len(devices or [])}')

    yield [f'/dev/{dev}' for dev in devices[:total]]

    run('udevadm settle')
    device.remove()

System

Service Management

sts.fixtures.service_fixtures

Service management fixtures for testing.

managed_service(request)

Fixture that captures and restores service state.

This fixture must be used with indirect parametrization.

Example
@pytest.mark.parametrize('managed_service', ['sshd'], indirect=True)
def test_my_service(managed_service):
    # Test code here
    pass
Source code in sts_libs/src/sts/fixtures/service_fixtures.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@pytest.fixture
def managed_service(request: pytest.FixtureRequest) -> Generator[None, None, None]:
    """Fixture that captures and restores service state.

    This fixture must be used with indirect parametrization.

    Example:
        ```python
        @pytest.mark.parametrize('managed_service', ['sshd'], indirect=True)
        def test_my_service(managed_service):
            # Test code here
            pass
        ```
    """
    service_name = request.param
    system = SystemManager()

    if not system.service_exists(service_name):
        pytest.skip(f'Service {service_name} not found')

    # Special handling for multipathd
    if service_name == 'multipathd':
        result = run('mpathconf --enable')
        if result.failed:
            pytest.skip(f'Failed to enable multipath configuration: {result.stderr}')

    # Capture initial state
    initial_active = system.is_service_running(service_name)
    initial_enabled = system.is_service_enabled(service_name)
    logger = logging.getLogger(__name__)

    try:
        yield
    finally:
        # Restore state
        logger.info(f'Restoring {service_name} to original state')

        if initial_enabled != system.is_service_enabled(service_name):
            if initial_enabled:
                if system.service_enable(service_name):
                    logger.info(f'Enabled {service_name} (restored to original state)')
                else:
                    logger.warning(f'Failed to enable {service_name}')
            elif system.service_disable(service_name):
                logger.info(f'Disabled {service_name} (restored to original state)')
            else:
                logger.warning(f'Failed to disable {service_name}')

        if initial_active != system.is_service_running(service_name):
            if initial_active:
                if system.service_start(service_name):
                    logger.info(f'Started {service_name} (restored to original state)')
                else:
                    logger.warning(f'Failed to start {service_name}')
            elif system.service_stop(service_name):
                logger.info(f'Stopped {service_name} (restored to original state)')
            else:
                logger.warning(f'Failed to stop {service_name}')

Kernel Module Management

sts.fixtures.module_fixtures

Fixtures related to kernel module management.

managed_module(request)

Load a kernel module before the test and unload it after (if it was not loaded initially).

Parametrize with @pytest.mark.parametrize('managed_module', ['qedi'], indirect=True).

Source code in sts_libs/src/sts/fixtures/module_fixtures.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@pytest.fixture
def managed_module(request: pytest.FixtureRequest) -> Generator[ModuleInfo, None, None]:
    """Load a kernel module before the test and unload it after (if it was not loaded initially).

    Parametrize with ``@pytest.mark.parametrize('managed_module', ['qedi'], indirect=True)``.
    """
    module_name = getattr(request, 'param', None)
    if not module_name:
        pytest.skip('Module name not provided to managed_module fixture')

    logger.info(f'Setting up managed module: {module_name}')
    mm = ModuleManager()
    module_info = ModuleInfo(name=module_name).discover()

    if not module_info.exists:
        pytest.skip(f'Module {module_name} does not exist')

    # Store initial state
    was_initially_loaded = module_info.loaded

    # Setup: Load the module
    if not module_info.loaded:
        logger.info(f'Module {module_name} not loaded, attempting load.')
        if not mm.load(module_name):
            pytest.skip(f'Failed to load required module {module_name}')
        # Re-check info after load attempt
        module_info = ModuleInfo(name=module_name).discover()
        if not module_info.loaded:
            pytest.skip(f'Module {module_name} still not loaded after load attempt')
        logger.info(f'Module {module_name} loaded successfully.')
    else:
        logger.debug(f'Module {module_name} was already loaded.')

    yield module_info

    # Teardown: Only unload if it wasn't loaded initially
    if not was_initially_loaded:
        logger.info(f'Unloading module {module_name} to restore initial state')
        try:
            # Re-fetch info in case state changed during test
            module_info_teardown = ModuleInfo(name=module_name).discover()
            if module_info_teardown.loaded:
                if not mm.unload(module_name):
                    logger.warning(f'Failed to unload module {module_name} during fixture teardown.')
                else:
                    logger.info(f'Successfully unloaded module {module_name} during fixture teardown.')
        except Exception:
            logger.exception(f'Error unloading module {module_name} during fixture teardown.')
    else:
        logger.debug(f'Keeping module {module_name} loaded as it was initially loaded')

    logger.debug(f'Finished teardown for managed_module: {module_name}')