Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add support for swap partitions and logical volumes #886

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion internal/testdisk/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func MakeFakePartitionTable(mntPoints ...string) *disk.PartitionTable {
case "/boot/efi":
payload.UUID = disk.EFIFilesystemUUID
payload.Type = "vfat"
case "swap":
payload.Type = "swap"
default:
payload.UUID = disk.FilesystemDataUUID
}
Expand Down Expand Up @@ -70,6 +72,15 @@ func MakeFakeBtrfsPartitionTable(mntPoints ...string) *disk.PartitionTable {
},
})
size += 100 * common.MiB
case "swap":
pt.Partitions = append(pt.Partitions, disk.Partition{
Start: size,
Size: 1 * common.GiB,
Payload: &disk.Filesystem{
Type: "swap",
},
})
size += 1 * common.GiB
default:
name := mntPoint
if name == "/" {
Expand Down Expand Up @@ -104,6 +115,7 @@ func MakeFakeBtrfsPartitionTable(mntPoints ...string) *disk.PartitionTable {

// MakeFakeLVMPartitionTable is similar to MakeFakePartitionTable but
// creates a lvm-based partition table.
// Note that mntPoint "swap" is created as a LV-based swap filesystem.
func MakeFakeLVMPartitionTable(mntPoints ...string) *disk.PartitionTable {
var lvs []disk.LVMLogicalVolume
pt := &disk.PartitionTable{
Expand Down Expand Up @@ -140,12 +152,17 @@ func MakeFakeLVMPartitionTable(mntPoints ...string) *disk.PartitionTable {
if name == "/" {
name = "lvroot"
}
fsType := "xfs"
if mntPoint == "swap" {
fsType = "swap"
}

lvs = append(
lvs,
disk.LVMLogicalVolume{
Name: name,
Payload: &disk.Filesystem{
Type: "xfs",
Type: fsType,
Mountpoint: mntPoint,
},
},
Expand All @@ -157,6 +174,7 @@ func MakeFakeLVMPartitionTable(mntPoints ...string) *disk.PartitionTable {
Start: size,
Size: 9 * common.GiB,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
LogicalVolumes: lvs,
},
})
Expand Down
19 changes: 17 additions & 2 deletions pkg/blueprint/filesystem_customizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ import (
"github.com/osbuild/images/pkg/pathpolicy"
)

type FilesystemType string

const (
FilesystemTypeUnset FilesystemType = ""
FilesystemTypeSwap FilesystemType = "swap"
)

type FilesystemCustomization struct {
Mountpoint string `json:"mountpoint,omitempty" toml:"mountpoint,omitempty"`
MinSize uint64 `json:"minsize,omitempty" toml:"minsize,omitempty"`
Mountpoint string `json:"mountpoint,omitempty" toml:"mountpoint,omitempty"`
MinSize uint64 `json:"minsize,omitempty" toml:"minsize,omitempty"`
Type FilesystemType `json:"type,omitempty" toml:"type,omitempty"`
}

func (fsc *FilesystemCustomization) UnmarshalTOML(data interface{}) error {
Expand All @@ -37,6 +45,13 @@ func (fsc *FilesystemCustomization) UnmarshalTOML(data interface{}) error {
return fmt.Errorf("TOML unmarshal: minsize must be integer or string, got %v of type %T", d["minsize"], d["minsize"])
}

switch d["type"].(type) {
case string:
fsc.Type = FilesystemType(d["type"].(string))
default:
return fmt.Errorf("TOML unmarshal: type must be string, got %v of type %T", d["type"], d["type"])
}

return nil
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/disk/btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"

"github.com/google/uuid"
"github.com/osbuild/images/pkg/blueprint"
)

const DefaultBtrfsCompression = "zstd:1"
Expand Down Expand Up @@ -56,7 +57,10 @@ func (b *Btrfs) GetItemCount() uint {
func (b *Btrfs) GetChild(n uint) Entity {
return &b.Subvolumes[n]
}
func (b *Btrfs) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {
func (b *Btrfs) CreateMountpoint(mountpoint string, size uint64, fstype blueprint.FilesystemType) (Entity, error) {
if fstype == blueprint.FilesystemTypeSwap {
return nil, fmt.Errorf("btrfs subvolumes cannot be swap")
}
name := mountpoint
if name == "/" {
name = "root"
Expand Down
10 changes: 6 additions & 4 deletions pkg/disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"slices"

"github.com/google/uuid"
"github.com/osbuild/images/pkg/blueprint"
)

const (
Expand Down Expand Up @@ -56,6 +57,8 @@ const (

// Extended Boot Loader Partition
XBootLDRPartitionGUID = "BC13C2FF-59E6-4262-A352-B275FD6F7172"

SwapPartitionGUID = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"
)

// Entity is the base interface for all disk-related entities.
Expand Down Expand Up @@ -114,11 +117,10 @@ type Mountable interface {
}

// A MountpointCreator is a container that is able to create new volumes.
//
// CreateMountpoint creates a new mountpoint with the given size and
// returns the entity that represents the new mountpoint.
type MountpointCreator interface {
CreateMountpoint(mountpoint string, size uint64) (Entity, error)
// CreateMountpoint creates a new mountpoint with the given size and type
// and returns the entity that represents the new mountpoint.
CreateMountpoint(mountpoint string, size uint64, fsType blueprint.FilesystemType) (Entity, error)

// AlignUp will align the given bytes according to the
// requirements of the container type.
Expand Down
54 changes: 37 additions & 17 deletions pkg/disk/disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ var testBlueprints = map[string][]blueprint.FilesystemCustomization{
Mountpoint: "/opt",
MinSize: 7 * GiB,
},
{
Type: blueprint.FilesystemTypeSwap,
MinSize: 7 * GiB,
},
},
"small": {
{
Expand Down Expand Up @@ -423,10 +427,26 @@ func TestDisk_ForEachEntity(t *testing.T) {
// blueprintApplied checks if the blueprint was applied correctly
// returns nil if the blueprint was applied correctly, an error otherwise
func blueprintApplied(pt *PartitionTable, bp []blueprint.FilesystemCustomization) error {

// finds an entity in the partition table representing the desired custom mountpoint
// and returns an entity path to it
customMountpointPath := func(mnt blueprint.FilesystemCustomization) []Entity {
if mnt.Type == blueprint.FilesystemTypeSwap {
isSwap := func(e Entity) bool {
if fs, ok := e.(*Filesystem); ok {
return fs.Type == "swap"
}
return false
}
return entityPath(pt, isSwap)
}
return entityPathForMountpoint(pt, mnt.Mountpoint)
}

for _, mnt := range bp {
path := entityPath(pt, mnt.Mountpoint)
path := customMountpointPath(mnt)
if path == nil {
return fmt.Errorf("mountpoint %s not found", mnt.Mountpoint)
return fmt.Errorf("mountpoint %s (type %s) not found", mnt.Mountpoint, mnt.Type)
}
for idx, ent := range path {
if sz, ok := ent.(Sizeable); ok {
Expand Down Expand Up @@ -510,12 +530,12 @@ func TestCreatePartitionTableLVMify(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tbp, uint64(13*MiB), AutoLVMPartitioningMode, nil, rng)
assert.NoError(err, "PT %q BP %q: Partition table generation failed: (%s)", ptName, bpName, err)

rootPath := entityPath(mpt, "/")
rootPath := entityPathForMountpoint(mpt, "/")
if rootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no root mountpoint", ptName, bpName))
}

bootPath := entityPath(mpt, "/boot")
bootPath := entityPathForMountpoint(mpt, "/boot")
if tbp != nil && bootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no boot mountpoint", ptName, bpName))
}
Expand Down Expand Up @@ -548,12 +568,12 @@ func TestCreatePartitionTableBtrfsify(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tbp, uint64(13*MiB), BtrfsPartitioningMode, nil, rng)
assert.NoError(err, "PT %q BP %q: Partition table generation failed: (%s)", ptName, bpName, err)

rootPath := entityPath(mpt, "/")
rootPath := entityPathForMountpoint(mpt, "/")
if rootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no root mountpoint", ptName, bpName))
}

bootPath := entityPath(mpt, "/boot")
bootPath := entityPathForMountpoint(mpt, "/boot")
if tbp != nil && bootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no boot mountpoint", ptName, bpName))
}
Expand Down Expand Up @@ -586,12 +606,12 @@ func TestCreatePartitionTableLVMOnly(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tbp, uint64(13*MiB), LVMPartitioningMode, nil, rng)
require.NoError(t, err, "PT %q BP %q: Partition table generation failed: (%s)", ptName, bpName, err)

rootPath := entityPath(mpt, "/")
rootPath := entityPathForMountpoint(mpt, "/")
if rootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no root mountpoint", ptName, bpName))
}

bootPath := entityPath(mpt, "/boot")
bootPath := entityPathForMountpoint(mpt, "/boot")
if tbp != nil && bootPath == nil {
panic(fmt.Sprintf("PT %q BP %q: no boot mountpoint", ptName, bpName))
}
Expand All @@ -606,11 +626,11 @@ func TestCreatePartitionTableLVMOnly(t *testing.T) {
// check logical volume sizes against blueprint
var lvsum uint64
for _, mnt := range tbp {
if mnt.Mountpoint == "/boot" {
// not on LVM; skipping
if mnt.Mountpoint == "/boot" || mnt.Type == blueprint.FilesystemTypeSwap {
// not on LVM or swap; skipping
continue
}
mntPath := entityPath(mpt, mnt.Mountpoint)
mntPath := entityPathForMountpoint(mpt, mnt.Mountpoint)
mntParent := mntPath[1]
mntLV, ok := mntParent.(*LVMLogicalVolume) // the partition's parent should be the logical volume
assert.True(ok, "PT %q BP %q: %s's parent (%+v) is not an LVM logical volume", ptName, bpName, mnt.Mountpoint, mntParent)
Expand Down Expand Up @@ -738,7 +758,7 @@ func TestMinimumSizes(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), RawPartitioningMode, nil, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*Partition)
Expand All @@ -752,7 +772,7 @@ func TestMinimumSizes(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), AutoLVMPartitioningMode, nil, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*LVMLogicalVolume)
Expand Down Expand Up @@ -839,7 +859,7 @@ func TestLVMExtentAlignment(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), AutoLVMPartitioningMode, nil, rng)
assert.NoError(err)
for mnt, expSize := range tc.ExpectedSizes {
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*LVMLogicalVolume)
Expand Down Expand Up @@ -870,7 +890,7 @@ func TestNewBootWithSizeLVMify(t *testing.T) {

for idx, c := range custom {
mnt, minSize := c.Mountpoint, c.MinSize
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*Partition)
Expand Down Expand Up @@ -1208,7 +1228,7 @@ func TestMinimumSizesWithRequiredSizes(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), RawPartitioningMode, map[string]uint64{"/": 1 * GiB, "/usr": 3 * GiB}, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*Partition)
Expand All @@ -1222,7 +1242,7 @@ func TestMinimumSizesWithRequiredSizes(t *testing.T) {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), AutoLVMPartitioningMode, map[string]uint64{"/": 1 * GiB, "/usr": 3 * GiB}, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
path := entityPathForMountpoint(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*LVMLogicalVolume)
Expand Down
24 changes: 24 additions & 0 deletions pkg/disk/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"

"github.com/google/uuid"
"github.com/osbuild/images/pkg/blueprint"
)

// Filesystem related functions
Expand Down Expand Up @@ -52,6 +53,11 @@ func (fs *Filesystem) GetMountpoint() string {
if fs == nil {
return ""
}

if fs.Type == "swap" {
return "none"
}

return fs.Mountpoint
}

Expand Down Expand Up @@ -93,3 +99,21 @@ func (fs *Filesystem) GenUUID(rng *rand.Rand) {
fs.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}

func createFilesystem(mountpoint string, fstype blueprint.FilesystemType) *Filesystem {
var typ string
switch fstype {
case blueprint.FilesystemTypeSwap:
typ = "swap"
default:
typ = "xfs"
}

return &Filesystem{
Type: typ,
Mountpoint: mountpoint,
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
}
}
14 changes: 3 additions & 11 deletions pkg/disk/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/blueprint"
)

// Default physical extent size in bytes: logical volumes
Expand Down Expand Up @@ -71,17 +72,8 @@ func (vg *LVMVolumeGroup) GetChild(n uint) Entity {
return &vg.LogicalVolumes[n]
}

func (vg *LVMVolumeGroup) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {

filesystem := Filesystem{
Type: "xfs",
Mountpoint: mountpoint,
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
}

return vg.CreateLogicalVolume(mountpoint, size, &filesystem)
func (vg *LVMVolumeGroup) CreateMountpoint(mountpoint string, size uint64, fstype blueprint.FilesystemType) (Entity, error) {
return vg.CreateLogicalVolume(mountpoint, size, createFilesystem(mountpoint, fstype))
}

func (vg *LVMVolumeGroup) CreateLogicalVolume(lvName string, size uint64, payload Entity) (Entity, error) {
Expand Down
Loading
Loading