Skip to content

LVM

This section documents the Logical Volume Management (LVM) functionality.

sts.lvm

LVM device management.

This module provides functionality for managing LVM devices: - Physical Volume (PV) operations - Volume Group (VG) operations - Logical Volume (LV) operations

LVM (Logical Volume Management) provides flexible disk space management: 1. Physical Volumes (PVs): Physical disks or partitions 2. Volume Groups (VGs): Pool of space from PVs 3. Logical Volumes (LVs): Virtual partitions from VG space

Key benefits: - Resize filesystems online - Snapshot and mirror volumes - Stripe across multiple disks - Move data between disks

LogicalVolume dataclass

Bases: LvmDevice

Logical Volume device.

A Logical Volume (LV) is a virtual partition created from VG space. LVs appear as block devices that can be formatted and mounted.

Key features: - Flexible sizing - Online resizing - Snapshots - Striping and mirroring - Thin provisioning

Parameters:

Name Type Description Default
name str | None

Device name (optional)

None
path Path | str | None

Device path (optional, defaults to /dev//)

None
size int | None

Device size in bytes (optional, discovered from device)

None
model str | None

Device model (optional)

None
yes bool

Automatically answer yes to prompts

True
force bool

Force operations without confirmation

False
vg str | None

Volume group name (optional, discovered from device)

None
Example
lv = LogicalVolume(name='lv0')  # Discovers other values
lv = LogicalVolume.create('lv0', 'vg0', size='1G')  # Creates new LV
Source code in sts_libs/src/sts/lvm.py
472
473
474
475
476
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
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
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
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
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
@dataclass
class LogicalVolume(LvmDevice):
    """Logical Volume device.

    A Logical Volume (LV) is a virtual partition created from VG space.
    LVs appear as block devices that can be formatted and mounted.

    Key features:
    - Flexible sizing
    - Online resizing
    - Snapshots
    - Striping and mirroring
    - Thin provisioning

    Args:
        name: Device name (optional)
        path: Device path (optional, defaults to /dev/<vg>/<name>)
        size: Device size in bytes (optional, discovered from device)
        model: Device model (optional)
        yes: Automatically answer yes to prompts
        force: Force operations without confirmation
        vg: Volume group name (optional, discovered from device)

    Example:
        ```python
        lv = LogicalVolume(name='lv0')  # Discovers other values
        lv = LogicalVolume.create('lv0', 'vg0', size='1G')  # Creates new LV
        ```
    """

    # Optional parameters for this class
    vg: str | None = None  # Parent VG
    pool_name: str | None = None

    # Available LV commands
    COMMANDS: ClassVar[list[str]] = [
        'lvchange',  # Change LV attributes
        'lvcreate',  # Create LV
        'lvconvert',  # Convert LV type
        'lvdisplay',  # Show LV details
        'lvextend',  # Increase LV size
        'lvreduce',  # Reduce LV size
        'lvremove',  # Remove LV
        'lvrename',  # Rename LV
        'lvresize',  # Change LV size
        'lvs',  # List LVs
        'lvscan',  # Scan for LVs
    ]

    def __post_init__(self) -> None:
        """Initialize Logical Volume.

        - Sets device path from name and VG
        - Discovers VG membership
        """
        # Set path based on name and vg if not provided
        if not self.path and self.name and self.vg:
            self.path = f'/dev/{self.vg}/{self.name}'

    def discover_vg(self) -> str | None:
        """Discover VG if name is available."""
        if self.name and not self.vg:
            result = run(f'lvs {self.name} -o vg_name --noheadings')
            if result.succeeded:
                self.vg = result.stdout.strip()
                return self.vg
        return None

    def create(self, **options: str) -> bool:
        """Create Logical Volume.

        Creates a new LV in the specified VG:
        - Allocates space from VG
        - Creates device mapper device
        - Initializes LV metadata

        Args:
            **options: LV options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            lv = LogicalVolume(name='lv0', vg='vg0')
            lv.create(size='1G')
            True
            ```
        """
        if not self.name:
            logging.error('Logical volume name required')
            return False
        if not self.vg:
            logging.error('Volume group required')
            return False

        result = self._run('lvcreate', '-n', self.name, self.vg, **options)
        return result.succeeded

    def remove(self, **options: str) -> bool:
        """Remove Logical Volume.

        Removes LV and its data:
        - Data is permanently lost
        - Space is returned to VG
        - Device mapper device is removed

        Args:
            **options: LV options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            lv = LogicalVolume(name='lv0', vg='vg0')
            lv.remove()
            True
            ```
        """
        if not self.name:
            logging.error('Logical volume name required')
            return False
        if not self.vg:
            logging.error('Volume group required')
            return False

        result = self._run('lvremove', f'{self.vg}/{self.name}', **options)
        return result.succeeded

    def change(self, *args: str, **options: str) -> bool:
        """Create Logical Volume.

        Change a general LV attribute:

        Args:
            *args: LV options (see LVMOptions)
            **options: LV options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            lv = LogicalVolume(name='lv0', vg='vg0')
            lv.change('-an', 'vg0/lv0')
            True
            ```
        """
        result = self._run('lvchange', *args, **options)
        return result.succeeded

    def extend(self, pvs: list[str], **options: str) -> bool:
        """Extend Volume Group with additional Physical Volumes.

        Adds one or more PVs to an existing VG:
        - PVs must be initialized (using pvcreate)
        - PVs must not belong to another VG
        - Updates VG metadata and extent allocation

        Args:
            pvs: List of PV device paths to add
            **options: VG options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            vg = VolumeGroup(name='vg0')
            vg.extend(['/dev/sdb1', '/dev/sdc1'])
            True
            ```
        """
        if not self.name:
            logging.error('Volume group name required')
            return False
        if not pvs:
            logging.error('Physical volumes required')
            return False

        result = self._run('lvextend', self.name, *pvs, **options)
        return result.succeeded

__post_init__()

Initialize Logical Volume.

  • Sets device path from name and VG
  • Discovers VG membership
Source code in sts_libs/src/sts/lvm.py
521
522
523
524
525
526
527
528
529
def __post_init__(self) -> None:
    """Initialize Logical Volume.

    - Sets device path from name and VG
    - Discovers VG membership
    """
    # Set path based on name and vg if not provided
    if not self.path and self.name and self.vg:
        self.path = f'/dev/{self.vg}/{self.name}'

change(*args, **options)

Create Logical Volume.

Change a general LV attribute:

Parameters:

Name Type Description Default
*args str

LV options (see LVMOptions)

()
**options str

LV options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
lv = LogicalVolume(name='lv0', vg='vg0')
lv.change('-an', 'vg0/lv0')
True
Source code in sts_libs/src/sts/lvm.py
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
def change(self, *args: str, **options: str) -> bool:
    """Create Logical Volume.

    Change a general LV attribute:

    Args:
        *args: LV options (see LVMOptions)
        **options: LV options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        lv = LogicalVolume(name='lv0', vg='vg0')
        lv.change('-an', 'vg0/lv0')
        True
        ```
    """
    result = self._run('lvchange', *args, **options)
    return result.succeeded

create(**options)

Create Logical Volume.

Creates a new LV in the specified VG: - Allocates space from VG - Creates device mapper device - Initializes LV metadata

Parameters:

Name Type Description Default
**options str

LV options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
lv = LogicalVolume(name='lv0', vg='vg0')
lv.create(size='1G')
True
Source code in sts_libs/src/sts/lvm.py
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
def create(self, **options: str) -> bool:
    """Create Logical Volume.

    Creates a new LV in the specified VG:
    - Allocates space from VG
    - Creates device mapper device
    - Initializes LV metadata

    Args:
        **options: LV options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        lv = LogicalVolume(name='lv0', vg='vg0')
        lv.create(size='1G')
        True
        ```
    """
    if not self.name:
        logging.error('Logical volume name required')
        return False
    if not self.vg:
        logging.error('Volume group required')
        return False

    result = self._run('lvcreate', '-n', self.name, self.vg, **options)
    return result.succeeded

discover_vg()

Discover VG if name is available.

Source code in sts_libs/src/sts/lvm.py
531
532
533
534
535
536
537
538
def discover_vg(self) -> str | None:
    """Discover VG if name is available."""
    if self.name and not self.vg:
        result = run(f'lvs {self.name} -o vg_name --noheadings')
        if result.succeeded:
            self.vg = result.stdout.strip()
            return self.vg
    return None

extend(pvs, **options)

Extend Volume Group with additional Physical Volumes.

Adds one or more PVs to an existing VG: - PVs must be initialized (using pvcreate) - PVs must not belong to another VG - Updates VG metadata and extent allocation

Parameters:

Name Type Description Default
pvs list[str]

List of PV device paths to add

required
**options str

VG options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
vg = VolumeGroup(name='vg0')
vg.extend(['/dev/sdb1', '/dev/sdc1'])
True
Source code in sts_libs/src/sts/lvm.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
def extend(self, pvs: list[str], **options: str) -> bool:
    """Extend Volume Group with additional Physical Volumes.

    Adds one or more PVs to an existing VG:
    - PVs must be initialized (using pvcreate)
    - PVs must not belong to another VG
    - Updates VG metadata and extent allocation

    Args:
        pvs: List of PV device paths to add
        **options: VG options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        vg = VolumeGroup(name='vg0')
        vg.extend(['/dev/sdb1', '/dev/sdc1'])
        True
        ```
    """
    if not self.name:
        logging.error('Volume group name required')
        return False
    if not pvs:
        logging.error('Physical volumes required')
        return False

    result = self._run('lvextend', self.name, *pvs, **options)
    return result.succeeded

remove(**options)

Remove Logical Volume.

Removes LV and its data: - Data is permanently lost - Space is returned to VG - Device mapper device is removed

Parameters:

Name Type Description Default
**options str

LV options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
lv = LogicalVolume(name='lv0', vg='vg0')
lv.remove()
True
Source code in sts_libs/src/sts/lvm.py
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
598
599
600
def remove(self, **options: str) -> bool:
    """Remove Logical Volume.

    Removes LV and its data:
    - Data is permanently lost
    - Space is returned to VG
    - Device mapper device is removed

    Args:
        **options: LV options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        lv = LogicalVolume(name='lv0', vg='vg0')
        lv.remove()
        True
        ```
    """
    if not self.name:
        logging.error('Logical volume name required')
        return False
    if not self.vg:
        logging.error('Volume group required')
        return False

    result = self._run('lvremove', f'{self.vg}/{self.name}', **options)
    return result.succeeded

LvmDevice dataclass

Bases: StorageDevice

Base class for LVM devices.

Provides common functionality for all LVM device types: - Command execution with standard options - Configuration management - Basic device operations

Parameters:

Name Type Description Default
name str | None

Device name (optional)

None
path Path | str | None

Device path (optional, defaults to /dev/)

None
size int | None

Device size in bytes (optional, discovered from device)

None
model str | None

Device model (optional)

None
yes bool

Automatically answer yes to prompts

True
force bool

Force operations without confirmation

False

The yes and force options are useful for automation: - yes: Skip interactive prompts - force: Ignore warnings and errors

Source code in sts_libs/src/sts/lvm.py
 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
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
@dataclass
class LvmDevice(StorageDevice):
    """Base class for LVM devices.

    Provides common functionality for all LVM device types:
    - Command execution with standard options
    - Configuration management
    - Basic device operations

    Args:
        name: Device name (optional)
        path: Device path (optional, defaults to /dev/<name>)
        size: Device size in bytes (optional, discovered from device)
        model: Device model (optional)
        yes: Automatically answer yes to prompts
        force: Force operations without confirmation

    The yes and force options are useful for automation:
    - yes: Skip interactive prompts
    - force: Ignore warnings and errors
    """

    # Optional parameters from parent classes
    name: str | None = None
    path: Path | str | None = None
    size: int | None = None
    model: str | None = None
    validate_on_init = False

    # Optional parameters for this class
    yes: bool = True  # Answer yes to prompts
    force: bool = False  # Force operations

    # Internal fields
    _config_path: Path = field(init=False, default=Path('/etc/lvm/lvm.conf'))

    def __post_init__(self) -> None:
        """Initialize LVM device."""
        # Set path based on name if not provided
        if not self.path and self.name:
            self.path = f'/dev/{self.name}'

        # Initialize parent class
        super().__post_init__()

    def _run(self, cmd: str, *args: str | Path | None, **kwargs: str) -> CommandResult:
        """Run LVM command.

        Builds and executes LVM commands with standard options:
        - Adds --yes for non-interactive mode
        - Adds --force to ignore warnings
        - Converts Python parameters to LVM options

        Args:
            cmd: Command name (e.g. 'pvcreate')
            *args: Command arguments
            **kwargs: Command parameters

        Returns:
            Command result
        """
        command = [cmd]
        if self.yes:
            command.append('--yes')
        if self.force:
            command.append('--force')
        if args:
            command.extend(str(arg) for arg in args if arg)
        if kwargs:
            command.extend(f'--{k.replace("_", "-")}={v}' for k, v in kwargs.items() if v)

        return run(' '.join(command))

    @abstractmethod
    def create(self, **options: str) -> bool:
        """Create LVM device.

        Args:
            **options: Device options (see LvmOptions)

        Returns:
            True if successful, False otherwise
        """

    @abstractmethod
    def remove(self, **options: str) -> bool:
        """Remove LVM device.

        Args:
            **options: Device options (see LvmOptions)

        Returns:
            True if successful, False otherwise
        """

__post_init__()

Initialize LVM device.

Source code in sts_libs/src/sts/lvm.py
122
123
124
125
126
127
128
129
def __post_init__(self) -> None:
    """Initialize LVM device."""
    # Set path based on name if not provided
    if not self.path and self.name:
        self.path = f'/dev/{self.name}'

    # Initialize parent class
    super().__post_init__()

create(**options) abstractmethod

Create LVM device.

Parameters:

Name Type Description Default
**options str

Device options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Source code in sts_libs/src/sts/lvm.py
159
160
161
162
163
164
165
166
167
168
@abstractmethod
def create(self, **options: str) -> bool:
    """Create LVM device.

    Args:
        **options: Device options (see LvmOptions)

    Returns:
        True if successful, False otherwise
    """

remove(**options) abstractmethod

Remove LVM device.

Parameters:

Name Type Description Default
**options str

Device options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Source code in sts_libs/src/sts/lvm.py
170
171
172
173
174
175
176
177
178
179
@abstractmethod
def remove(self, **options: str) -> bool:
    """Remove LVM device.

    Args:
        **options: Device options (see LvmOptions)

    Returns:
        True if successful, False otherwise
    """

LvmOptions

Bases: TypedDict

LVM command options.

Common options: - size: Volume size (e.g. '1G', '500M') - extents: Volume size in extents (e.g. '100%FREE') - permission: Volume permission (rw/r) - persistent: Make settings persistent across reboots - monitor: Monitor volume for events - autobackup: Auto backup metadata after changes

Size can be specified in: - Absolute size (1G, 500M) - Percentage of VG (80%VG) - Percentage of free space (100%FREE) - Physical extents (100)

Source code in sts_libs/src/sts/lvm.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class LvmOptions(TypedDict, total=False):
    """LVM command options.

    Common options:
    - size: Volume size (e.g. '1G', '500M')
    - extents: Volume size in extents (e.g. '100%FREE')
    - permission: Volume permission (rw/r)
    - persistent: Make settings persistent across reboots
    - monitor: Monitor volume for events
    - autobackup: Auto backup metadata after changes

    Size can be specified in:
    - Absolute size (1G, 500M)
    - Percentage of VG (80%VG)
    - Percentage of free space (100%FREE)
    - Physical extents (100)
    """

    size: str
    extents: str
    permission: str
    persistent: str
    monitor: str
    autobackup: str

PVInfo dataclass

Physical Volume information.

Stores key information about a Physical Volume: - Volume group membership - Format type (lvm2) - Attributes (allocatable, exported, etc) - Size information (total and free space)

Parameters:

Name Type Description Default
vg str | None

Volume group name (None if not in a VG)

required
fmt str

PV format (usually 'lvm2')

required
attr str

PV attributes (e.g. 'a--' for allocatable)

required
psize str

PV size (e.g. '1.00t')

required
pfree str

PV free space (e.g. '500.00g')

required
Source code in sts_libs/src/sts/lvm.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@dataclass
class PVInfo:
    """Physical Volume information.

    Stores key information about a Physical Volume:
    - Volume group membership
    - Format type (lvm2)
    - Attributes (allocatable, exported, etc)
    - Size information (total and free space)

    Args:
        vg: Volume group name (None if not in a VG)
        fmt: PV format (usually 'lvm2')
        attr: PV attributes (e.g. 'a--' for allocatable)
        psize: PV size (e.g. '1.00t')
        pfree: PV free space (e.g. '500.00g')
    """

    vg: str | None
    fmt: str
    attr: str
    psize: str
    pfree: str

PhysicalVolume dataclass

Bases: LvmDevice

Physical Volume device.

A Physical Volume (PV) is a disk or partition used by LVM. PVs provide the storage pool for Volume Groups.

Key features: - Initialize disks/partitions for LVM use - Track space allocation - Handle bad block management - Store LVM metadata

Parameters:

Name Type Description Default
name str | None

Device name (optional)

None
path Path | str | None

Device path (optional, defaults to /dev/)

None
size int | None

Device size in bytes (optional, discovered from device)

None
model str | None

Device model (optional)

None
yes bool

Automatically answer yes to prompts

True
force bool

Force operations without confirmation

False
vg str | None

Volume group name (optional, discovered from device)

None
fmt str | None

PV format (optional, discovered from device)

None
attr str | None

PV attributes (optional, discovered from device)

None
pfree str | None

PV free space (optional, discovered from device)

None
Example
pv = PhysicalVolume(name='sda1')  # Discovers other values
pv = PhysicalVolume.create('/dev/sda1')  # Creates new PV
Source code in sts_libs/src/sts/lvm.py
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
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
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
343
344
345
@dataclass
class PhysicalVolume(LvmDevice):
    """Physical Volume device.

    A Physical Volume (PV) is a disk or partition used by LVM.
    PVs provide the storage pool for Volume Groups.

    Key features:
    - Initialize disks/partitions for LVM use
    - Track space allocation
    - Handle bad block management
    - Store LVM metadata

    Args:
        name: Device name (optional)
        path: Device path (optional, defaults to /dev/<name>)
        size: Device size in bytes (optional, discovered from device)
        model: Device model (optional)
        yes: Automatically answer yes to prompts
        force: Force operations without confirmation
        vg: Volume group name (optional, discovered from device)
        fmt: PV format (optional, discovered from device)
        attr: PV attributes (optional, discovered from device)
        pfree: PV free space (optional, discovered from device)

    Example:
        ```python
        pv = PhysicalVolume(name='sda1')  # Discovers other values
        pv = PhysicalVolume.create('/dev/sda1')  # Creates new PV
        ```
    """

    # Optional parameters for this class
    vg: str | None = None  # Volume Group membership
    fmt: str | None = None  # PV format (usually lvm2)
    attr: str | None = None  # PV attributes
    pfree: str | None = None  # Free space

    # Available PV commands
    COMMANDS: ClassVar[list[str]] = [
        'pvchange',  # Modify PV attributes
        'pvck',  # Check PV metadata
        'pvcreate',  # Initialize PV
        'pvdisplay',  # Show PV details
        'pvmove',  # Move PV data
        'pvremove',  # Remove PV
        'pvresize',  # Resize PV
        'pvs',  # List PVs
        'pvscan',  # Scan for PVs
    ]

    # Discover PV info if path is available
    def discover_pv_info(self) -> None:
        """Discovers PV information if path is available.

        Volume group membership.
        Format and attributes.
        Size information.
        """
        result = run(f'pvs {self.path} --noheadings --separator ","')
        if result.succeeded:
            # Parse PV info line
            # Format: PV,VG,Fmt,Attr,PSize,PFree
            parts = result.stdout.strip().split(',')
            if len(parts) == 6:
                _, vg, fmt, attr, _, pfree = parts
                if not self.vg:
                    self.vg = vg or None
                if not self.fmt:
                    self.fmt = fmt
                if not self.attr:
                    self.attr = attr
                if not self.pfree:
                    self.pfree = pfree

    def create(self, **options: str) -> bool:
        """Create Physical Volume.

        Initializes a disk or partition for use with LVM:
        - Creates LVM metadata area
        - Prepares device for VG membership

        Args:
            **options: PV options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            pv = PhysicalVolume(path='/dev/sda1')
            pv.create()
            True
            ```
        """
        if not self.path:
            logging.error('Device path required')
            return False

        result = self._run('pvcreate', str(self.path), **options)
        if result.succeeded:
            self.discover_pv_info()
        return result.succeeded

    def remove(self, **options: str) -> bool:
        """Remove Physical Volume.

        Removes LVM metadata from device:
        - Device must not be in use by a VG
        - Data on device is not erased

        Args:
            **options: PV options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            pv = PhysicalVolume(path='/dev/sda1')
            pv.remove()
            True
            ```
        """
        if not self.path:
            logging.error('Device path required')
            return False

        result = self._run('pvremove', str(self.path), **options)
        return result.succeeded

    @classmethod
    def get_all(cls) -> dict[str, PVInfo]:
        """Get all Physical Volumes.

        Returns:
            Dictionary mapping PV names to their information

        Example:
            ```python
            PhysicalVolume.get_all()
            {'/dev/sda1': PVInfo(vg='vg0', fmt='lvm2', ...)}
            ```
        """
        result = run('pvs --noheadings --separator ","')
        if result.failed:
            logging.debug('No Physical Volumes found')
            return {}

        # Format: PV,VG,Fmt,Attr,PSize,PFree
        pv_info_regex = r'\s+(\S+),(\S+)?,(\S+),(.*),(.*),(.*)$'
        pv_dict = {}

        for line in result.stdout.splitlines():
            if match := re.match(pv_info_regex, line):
                pv_dict[match.group(1)] = PVInfo(
                    vg=match.group(2) or None,  # VG can be empty
                    fmt=match.group(3),
                    attr=match.group(4),
                    psize=match.group(5),
                    pfree=match.group(6),
                )

        return pv_dict

create(**options)

Create Physical Volume.

Initializes a disk or partition for use with LVM: - Creates LVM metadata area - Prepares device for VG membership

Parameters:

Name Type Description Default
**options str

PV options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
pv = PhysicalVolume(path='/dev/sda1')
pv.create()
True
Source code in sts_libs/src/sts/lvm.py
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
def create(self, **options: str) -> bool:
    """Create Physical Volume.

    Initializes a disk or partition for use with LVM:
    - Creates LVM metadata area
    - Prepares device for VG membership

    Args:
        **options: PV options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        pv = PhysicalVolume(path='/dev/sda1')
        pv.create()
        True
        ```
    """
    if not self.path:
        logging.error('Device path required')
        return False

    result = self._run('pvcreate', str(self.path), **options)
    if result.succeeded:
        self.discover_pv_info()
    return result.succeeded

discover_pv_info()

Discovers PV information if path is available.

Volume group membership. Format and attributes. Size information.

Source code in sts_libs/src/sts/lvm.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def discover_pv_info(self) -> None:
    """Discovers PV information if path is available.

    Volume group membership.
    Format and attributes.
    Size information.
    """
    result = run(f'pvs {self.path} --noheadings --separator ","')
    if result.succeeded:
        # Parse PV info line
        # Format: PV,VG,Fmt,Attr,PSize,PFree
        parts = result.stdout.strip().split(',')
        if len(parts) == 6:
            _, vg, fmt, attr, _, pfree = parts
            if not self.vg:
                self.vg = vg or None
            if not self.fmt:
                self.fmt = fmt
            if not self.attr:
                self.attr = attr
            if not self.pfree:
                self.pfree = pfree

get_all() classmethod

Get all Physical Volumes.

Returns:

Type Description
dict[str, PVInfo]

Dictionary mapping PV names to their information

Example
PhysicalVolume.get_all()
{'/dev/sda1': PVInfo(vg='vg0', fmt='lvm2', ...)}
Source code in sts_libs/src/sts/lvm.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
@classmethod
def get_all(cls) -> dict[str, PVInfo]:
    """Get all Physical Volumes.

    Returns:
        Dictionary mapping PV names to their information

    Example:
        ```python
        PhysicalVolume.get_all()
        {'/dev/sda1': PVInfo(vg='vg0', fmt='lvm2', ...)}
        ```
    """
    result = run('pvs --noheadings --separator ","')
    if result.failed:
        logging.debug('No Physical Volumes found')
        return {}

    # Format: PV,VG,Fmt,Attr,PSize,PFree
    pv_info_regex = r'\s+(\S+),(\S+)?,(\S+),(.*),(.*),(.*)$'
    pv_dict = {}

    for line in result.stdout.splitlines():
        if match := re.match(pv_info_regex, line):
            pv_dict[match.group(1)] = PVInfo(
                vg=match.group(2) or None,  # VG can be empty
                fmt=match.group(3),
                attr=match.group(4),
                psize=match.group(5),
                pfree=match.group(6),
            )

    return pv_dict

remove(**options)

Remove Physical Volume.

Removes LVM metadata from device: - Device must not be in use by a VG - Data on device is not erased

Parameters:

Name Type Description Default
**options str

PV options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
pv = PhysicalVolume(path='/dev/sda1')
pv.remove()
True
Source code in sts_libs/src/sts/lvm.py
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
def remove(self, **options: str) -> bool:
    """Remove Physical Volume.

    Removes LVM metadata from device:
    - Device must not be in use by a VG
    - Data on device is not erased

    Args:
        **options: PV options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        pv = PhysicalVolume(path='/dev/sda1')
        pv.remove()
        True
        ```
    """
    if not self.path:
        logging.error('Device path required')
        return False

    result = self._run('pvremove', str(self.path), **options)
    return result.succeeded

VolumeGroup dataclass

Bases: LvmDevice

Volume Group device.

A Volume Group (VG) combines Physical Volumes into a storage pool. This pool can then be divided into Logical Volumes.

Key features: - Combine multiple PVs - Manage storage pool - Track extent allocation - Handle PV addition/removal

Parameters:

Name Type Description Default
name str | None

Device name (optional)

None
path Path | str | None

Device path (optional, defaults to /dev/)

None
size int | None

Device size in bytes (optional, discovered from device)

None
model str | None

Device model (optional)

None
yes bool

Automatically answer yes to prompts

True
force bool

Force operations without confirmation

False
pvs list[str]

List of Physical Volumes (optional, discovered from device)

list()
Example
vg = VolumeGroup(name='vg0')  # Discovers other values
vg = VolumeGroup.create('vg0', ['/dev/sda1'])  # Creates new VG
Source code in sts_libs/src/sts/lvm.py
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
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
431
432
433
434
435
436
437
438
439
440
441
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
@dataclass
class VolumeGroup(LvmDevice):
    """Volume Group device.

    A Volume Group (VG) combines Physical Volumes into a storage pool.
    This pool can then be divided into Logical Volumes.

    Key features:
    - Combine multiple PVs
    - Manage storage pool
    - Track extent allocation
    - Handle PV addition/removal

    Args:
        name: Device name (optional)
        path: Device path (optional, defaults to /dev/<name>)
        size: Device size in bytes (optional, discovered from device)
        model: Device model (optional)
        yes: Automatically answer yes to prompts
        force: Force operations without confirmation
        pvs: List of Physical Volumes (optional, discovered from device)

    Example:
        ```python
        vg = VolumeGroup(name='vg0')  # Discovers other values
        vg = VolumeGroup.create('vg0', ['/dev/sda1'])  # Creates new VG
        ```
    """

    # Optional parameters for this class
    pvs: list[str] = field(default_factory=list)  # Member PVs

    # Available VG commands
    COMMANDS: ClassVar[list[str]] = [
        'vgcfgbackup',  # Backup VG metadata
        'vgcfgrestore',  # Restore VG metadata
        'vgchange',  # Change VG attributes
        'vgck',  # Check VG metadata
        'vgconvert',  # Convert VG metadata format
        'vgcreate',  # Create VG
        'vgdisplay',  # Show VG details
        'vgexport',  # Make VG inactive
        'vgextend',  # Add PVs to VG
        'vgimport',  # Make VG active
        'vgimportclone',  # Import cloned PVs
        'vgimportdevices',  # Import PVs into VG
        'vgmerge',  # Merge VGs
        'vgmknodes',  # Create VG special files
        'vgreduce',  # Remove PVs from VG
        'vgremove',  # Remove VG
        'vgrename',  # Rename VG
        'vgs',  # List VGs
        'vgscan',  # Scan for VGs
        'vgsplit',  # Split VG into two
    ]

    def discover_pvs(self) -> list[str] | None:
        """Discover PVs if name is available."""
        if self.name:
            result = run(f'vgs {self.name} -o pv_name --noheadings')
            if result.succeeded:
                self.pvs = result.stdout.strip().splitlines()
                return self.pvs
        return None

    def create(self, **options: str) -> bool:
        """Create Volume Group.

        Creates a new VG from specified PVs:
        - Initializes VG metadata
        - Sets up extent allocation
        - Creates device mapper devices

        Args:
            **options: VG options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            vg = VolumeGroup(name='vg0', pvs=['/dev/sda1'])
            vg.create()
            True
            ```
        """
        if not self.name:
            logging.error('Volume group name required')
            return False
        if not self.pvs:
            logging.error('Physical volumes required')
            return False

        result = self._run('vgcreate', self.name, *self.pvs, **options)
        return result.succeeded

    def remove(self, **options: str) -> bool:
        """Remove Volume Group.

        Removes VG and its metadata:
        - All LVs must be removed first
        - PVs are released but not removed

        Args:
            **options: VG options (see LvmOptions)

        Returns:
            True if successful, False otherwise

        Example:
            ```python
            vg = VolumeGroup(name='vg0')
            vg.remove()
            True
            ```
        """
        if not self.name:
            logging.error('Volume group name required')
            return False

        result = self._run('vgremove', self.name, **options)
        return result.succeeded

create(**options)

Create Volume Group.

Creates a new VG from specified PVs: - Initializes VG metadata - Sets up extent allocation - Creates device mapper devices

Parameters:

Name Type Description Default
**options str

VG options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
vg = VolumeGroup(name='vg0', pvs=['/dev/sda1'])
vg.create()
True
Source code in sts_libs/src/sts/lvm.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
def create(self, **options: str) -> bool:
    """Create Volume Group.

    Creates a new VG from specified PVs:
    - Initializes VG metadata
    - Sets up extent allocation
    - Creates device mapper devices

    Args:
        **options: VG options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        vg = VolumeGroup(name='vg0', pvs=['/dev/sda1'])
        vg.create()
        True
        ```
    """
    if not self.name:
        logging.error('Volume group name required')
        return False
    if not self.pvs:
        logging.error('Physical volumes required')
        return False

    result = self._run('vgcreate', self.name, *self.pvs, **options)
    return result.succeeded

discover_pvs()

Discover PVs if name is available.

Source code in sts_libs/src/sts/lvm.py
404
405
406
407
408
409
410
411
def discover_pvs(self) -> list[str] | None:
    """Discover PVs if name is available."""
    if self.name:
        result = run(f'vgs {self.name} -o pv_name --noheadings')
        if result.succeeded:
            self.pvs = result.stdout.strip().splitlines()
            return self.pvs
    return None

remove(**options)

Remove Volume Group.

Removes VG and its metadata: - All LVs must be removed first - PVs are released but not removed

Parameters:

Name Type Description Default
**options str

VG options (see LvmOptions)

{}

Returns:

Type Description
bool

True if successful, False otherwise

Example
vg = VolumeGroup(name='vg0')
vg.remove()
True
Source code in sts_libs/src/sts/lvm.py
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
def remove(self, **options: str) -> bool:
    """Remove Volume Group.

    Removes VG and its metadata:
    - All LVs must be removed first
    - PVs are released but not removed

    Args:
        **options: VG options (see LvmOptions)

    Returns:
        True if successful, False otherwise

    Example:
        ```python
        vg = VolumeGroup(name='vg0')
        vg.remove()
        True
        ```
    """
    if not self.name:
        logging.error('Volume group name required')
        return False

    result = self._run('vgremove', self.name, **options)
    return result.succeeded