When the size of a logical volume is not aligned to the extent size of
the volume group, LVM2 will automatically align it by rounding up[1]:
Rounding up size to full physical extent 29.80 GiB
Rounding up size to full physical extent <3.82 GiB
Since we don't take that into account when we create a new volume or
set the size of an existing one, the size for the whole volume group
will be short by that amount and thus the creation of the last volume
will fail:
Volume group <uuid> has insufficient free space (975 extents): 977 required.
To fix this a new `AlignUp` method is added to the `MountpointCreator`
creator interface. It will align a given size to the requirements of
the implementing container, like e.g. `LVMVolumeGroup`. It is then
used by a new `alignEntityBranch` which takes a size and walks the
entity path, calling `AlignUp` for all entities that implement said
`MountpointCreator` interface; thus the resulting size should fullfil
the alignment requirement for all elements in the path.
NB: `PartitionTable` already had an `AlignUp` method.
Add a corresponding test.
[1]: 8686657664/lib/metadata/metadata.c (L1072)
Co-authored-by: Achilleas Koutsou <achilleas@koutsou.net>
158 lines
2.8 KiB
Go
158 lines
2.8 KiB
Go
package disk
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Btrfs struct {
|
|
UUID string
|
|
Label string
|
|
Mountpoint string
|
|
Subvolumes []BtrfsSubvolume
|
|
}
|
|
|
|
func (b *Btrfs) IsContainer() bool {
|
|
return true
|
|
}
|
|
|
|
func (b *Btrfs) Clone() Entity {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := &Btrfs{
|
|
UUID: b.UUID,
|
|
Label: b.Label,
|
|
Mountpoint: b.Mountpoint,
|
|
Subvolumes: make([]BtrfsSubvolume, len(b.Subvolumes)),
|
|
}
|
|
|
|
for idx, subvol := range b.Subvolumes {
|
|
entClone := subvol.Clone()
|
|
svClone, cloneOk := entClone.(*BtrfsSubvolume)
|
|
if !cloneOk {
|
|
panic("BtrfsSubvolume.Clone() returned an Entity that cannot be converted to *BtrfsSubvolume; this is a programming error")
|
|
}
|
|
clone.Subvolumes[idx] = *svClone
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
func (b *Btrfs) GetItemCount() uint {
|
|
return uint(len(b.Subvolumes))
|
|
}
|
|
|
|
func (b *Btrfs) GetChild(n uint) Entity {
|
|
return &b.Subvolumes[n]
|
|
}
|
|
func (b *Btrfs) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {
|
|
name := mountpoint
|
|
if name == "/" {
|
|
name = "root"
|
|
}
|
|
subvolume := BtrfsSubvolume{
|
|
Size: size,
|
|
Mountpoint: mountpoint,
|
|
GroupID: 0,
|
|
UUID: b.UUID, // subvolumes inherit UUID of main volume
|
|
Name: name,
|
|
}
|
|
|
|
b.Subvolumes = append(b.Subvolumes, subvolume)
|
|
return &b.Subvolumes[len(b.Subvolumes)-1], nil
|
|
}
|
|
|
|
func (b *Btrfs) AlignUp(size uint64) uint64 {
|
|
return size // No extra alignment necessary for subvolumes
|
|
}
|
|
|
|
func (b *Btrfs) GenUUID(rng *rand.Rand) {
|
|
if b.UUID == "" {
|
|
b.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
|
|
}
|
|
}
|
|
|
|
type BtrfsSubvolume struct {
|
|
Name string
|
|
Size uint64
|
|
Mountpoint string
|
|
GroupID uint64
|
|
|
|
MntOps string
|
|
|
|
// UUID of the parent volume
|
|
UUID string
|
|
}
|
|
|
|
func (subvol *BtrfsSubvolume) IsContainer() bool {
|
|
return false
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) Clone() Entity {
|
|
if bs == nil {
|
|
return nil
|
|
}
|
|
|
|
return &BtrfsSubvolume{
|
|
Name: bs.Name,
|
|
Size: bs.Size,
|
|
Mountpoint: bs.Mountpoint,
|
|
GroupID: bs.GroupID,
|
|
MntOps: bs.MntOps,
|
|
UUID: bs.UUID,
|
|
}
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) GetSize() uint64 {
|
|
if bs == nil {
|
|
return 0
|
|
}
|
|
return bs.Size
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) EnsureSize(s uint64) bool {
|
|
if s > bs.Size {
|
|
bs.Size = s
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) GetMountpoint() string {
|
|
if bs == nil {
|
|
return ""
|
|
}
|
|
return bs.Mountpoint
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) GetFSType() string {
|
|
return "btrfs"
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) GetFSSpec() FSSpec {
|
|
if bs == nil {
|
|
return FSSpec{}
|
|
}
|
|
return FSSpec{
|
|
UUID: bs.UUID,
|
|
Label: bs.Name,
|
|
}
|
|
}
|
|
|
|
func (bs *BtrfsSubvolume) GetFSTabOptions() FSTabOptions {
|
|
if bs == nil {
|
|
return FSTabOptions{}
|
|
}
|
|
|
|
ops := strings.Join([]string{bs.MntOps, fmt.Sprintf("subvol=%s", bs.Name)}, ",")
|
|
return FSTabOptions{
|
|
MntOps: ops,
|
|
Freq: 0,
|
|
PassNo: 0,
|
|
}
|
|
}
|