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

Introduce partitioning modes #3222

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 1 addition & 1 deletion cmd/osbuild-playground/my-image.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (img *MyImage) InstantiateManifest(m *manifest.Manifest,
}

// TODO: add helper
pt, err := disk.NewPartitionTable(&basePT, nil, 0, false, rng)
pt, err := disk.NewPartitionTable(&basePT, nil, 0, disk.RawPartitioningMode, rng)
if err != nil {
panic(err)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/cloudapi/v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
ErrorUnsupportedImage ServiceErrorCode = 32
ErrorInvalidImageFromComposeId ServiceErrorCode = 33
ErrorImageNotFound ServiceErrorCode = 34
ErrorInvalidPartitioningMode ServiceErrorCode = 35

// Internal errors, these are bugs
ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000
Expand Down Expand Up @@ -120,6 +121,7 @@ func getServiceErrors() serviceErrors {
serviceError{ErrorUnsupportedImage, http.StatusBadRequest, "This compose doesn't support the creation of multiple images"},
serviceError{ErrorInvalidImageFromComposeId, http.StatusBadRequest, "Invalid format for image id"},
serviceError{ErrorImageNotFound, http.StatusBadRequest, "Image with given id not found"},
serviceError{ErrorInvalidPartitioningMode, http.StatusBadRequest, "Requested partitioning mode is invalid"},

serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"},
serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"},
Expand Down
19 changes: 18 additions & 1 deletion internal/cloudapi/v2/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/ostree"
Expand Down Expand Up @@ -269,7 +270,10 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
return err
}

imageOptions := distro.ImageOptions{Size: imageType.Size(0)}
imageOptions := distro.ImageOptions{
Size: imageType.Size(0),
PartitioningMode: disk.AutoLVMPartitioningMode,
}

if request.Koji == nil {
imageOptions.Facts = &distro.FactsImageOptions{
Expand All @@ -287,6 +291,19 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
}
}

if request.Customizations != nil && request.Customizations.PartitioningMode != nil {
switch *request.Customizations.PartitioningMode {
case CustomizationsPartitioningModeRaw:
imageOptions.PartitioningMode = disk.RawPartitioningMode
case CustomizationsPartitioningModeLvm:
imageOptions.PartitioningMode = disk.LVMPartitioningMode
case CustomizationsPartitioningModeAutoLvm:
imageOptions.PartitioningMode = disk.AutoLVMPartitioningMode
default:
return HTTPError(ErrorInvalidPartitioningMode)
}
}

var ostreeOptions *ostree.RequestParams
// assume it's an ostree image if the type has a default ostree ref
if imageType.OSTreeRef() != "" {
Expand Down
194 changes: 104 additions & 90 deletions internal/cloudapi/v2/openapi.v2.gen.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions internal/cloudapi/v2/openapi.v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,13 @@ components:
type: array
items:
$ref: '#/components/schemas/Filesystem'
partitioning_mode:
type: string
enum:
- raw
- lvm
- auto-lvm
default: auto-lvm
services:
type: object
additionalProperties: false
Expand Down
19 changes: 10 additions & 9 deletions internal/disk/disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"strings"
"testing"

"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/stretchr/testify/assert"

"github.com/osbuild/osbuild-composer/internal/blueprint"
)

const (
Expand Down Expand Up @@ -74,7 +75,7 @@ func TestDisk_DynamicallyResizePartitionTable(t *testing.T) {
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(rand.NewSource(0))
newpt, err := NewPartitionTable(&pt, mountpoints, 1024, false, rng)
newpt, err := NewPartitionTable(&pt, mountpoints, 1024, RawPartitioningMode, rng)
assert.NoError(t, err)
assert.GreaterOrEqual(t, newpt.Size, expectedSize)
}
Expand Down Expand Up @@ -449,7 +450,7 @@ func TestCreatePartitionTable(t *testing.T) {
for ptName := range testPartitionTables {
pt := testPartitionTables[ptName]
for bpName, bp := range testBlueprints {
mpt, err := NewPartitionTable(&pt, bp, uint64(13*MiB), false, rng)
mpt, err := NewPartitionTable(&pt, bp, uint64(13*MiB), RawPartitioningMode, rng)
assert.NoError(err, "Partition table generation failed: PT %q BP %q (%s)", ptName, bpName, err)
assert.NotNil(mpt, "Partition table generation failed: PT %q BP %q (nil partition table)", ptName, bpName)
assert.Greater(mpt.GetSize(), sumSizes(bp))
Expand All @@ -475,12 +476,12 @@ func TestCreatePartitionTableLVMify(t *testing.T) {

if tbp != nil && (ptName == "btrfs" || ptName == "luks") {
assert.Panics(func() {
_, _ = NewPartitionTable(&pt, tbp, uint64(13*MiB), true, rng)
_, _ = NewPartitionTable(&pt, tbp, uint64(13*MiB), AutoLVMPartitioningMode, rng)
}, fmt.Sprintf("PT %q BP %q: should panic", ptName, bpName))
continue
}

mpt, err := NewPartitionTable(&pt, tbp, uint64(13*MiB), true, rng)
mpt, err := NewPartitionTable(&pt, tbp, uint64(13*MiB), AutoLVMPartitioningMode, rng)
assert.NoError(err, "PT %q BP %q: Partition table generation failed: (%s)", ptName, bpName, err)

rootPath := entityPath(mpt, "/")
Expand Down Expand Up @@ -588,7 +589,7 @@ func TestMinimumSizes(t *testing.T) {

for idx, tc := range testCases {
{ // without LVM
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), false, rng)
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), RawPartitioningMode, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
Expand All @@ -602,7 +603,7 @@ func TestMinimumSizes(t *testing.T) {
}

{ // with LVM
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), true, rng)
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), AutoLVMPartitioningMode, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
Expand Down Expand Up @@ -689,7 +690,7 @@ func TestLVMExtentAlignment(t *testing.T) {
}

for idx, tc := range testCases {
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), true, rng)
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), AutoLVMPartitioningMode, rng)
assert.NoError(err)
for mnt, expSize := range tc.ExpectedSizes {
path := entityPath(mpt, mnt)
Expand Down Expand Up @@ -718,7 +719,7 @@ func TestNewBootWithSizeLVMify(t *testing.T) {
},
}

mpt, err := NewPartitionTable(&pt, custom, uint64(3*GiB), true, rng)
mpt, err := NewPartitionTable(&pt, custom, uint64(3*GiB), AutoLVMPartitioningMode, rng)
assert.NoError(err)

for idx, c := range custom {
Expand Down
93 changes: 68 additions & 25 deletions internal/disk/partition_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"

"github.com/google/uuid"

"github.com/osbuild/osbuild-composer/internal/blueprint"
)

Expand All @@ -19,14 +20,47 @@ type PartitionTable struct {
ExtraPadding uint64 // Extra space at the end of the partition table (sectors)
}

func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, lvmify bool, rng *rand.Rand) (*PartitionTable, error) {
type PartitioningMode string

const (
// AutoLVMPartitioningMode creates a LVM layout if the filesystem
// contains a mountpoint that's not defined in the base partition table
// of the specified image type. In the other case, a raw layout is used.
//
// This is the default mode.
AutoLVMPartitioningMode PartitioningMode = ""

// LVMPartitioningMode always creates an LVM layout.
LVMPartitioningMode PartitioningMode = "lvm"

// RawPartitioningMode always creates a raw layout.
RawPartitioningMode PartitioningMode = "raw"
)

func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, mode PartitioningMode, rng *rand.Rand) (*PartitionTable, error) {
newPT := basePT.Clone().(*PartitionTable)

if basePT.introspect().LVM && mode == RawPartitioningMode {
return nil, fmt.Errorf("raw partitioning mode set for a base partition table with LVM, this is unsupported")
}

// first pass: enlarge existing mountpoints and collect new ones
newMountpoints, _ := newPT.applyCustomization(mountpoints, false)

// if there is any new mountpoint and lvmify is enabled, ensure we have LVM layout
if lvmify && len(newMountpoints) > 0 {
var ensureLVM bool

switch mode {
case LVMPartitioningMode:
ensureLVM = true
case RawPartitioningMode:
ensureLVM = false
case AutoLVMPartitioningMode:
ensureLVM = len(newMountpoints) > 0
default:
panic(fmt.Sprintf("NewPartitionTable got an unexpected partitioning mode (%s)l this is a programming error", mode))
}

if ensureLVM {
err := newPT.ensureLVM()
if err != nil {
return nil, err
Expand Down Expand Up @@ -623,57 +657,66 @@ func (pt *PartitionTable) ensureLVM() error {
return nil
}

func (pt *PartitionTable) GetBuildPackages() []string {
packages := []string{}
type partitionTableFeatures struct {
LVM bool
Btrfs bool
XFS bool
FAT bool
EXT4 bool
LUKS bool
}

hasLVM := false
hasBtrfs := false
hasXFS := false
hasFAT := false
hasEXT4 := false
hasLUKS := false
func (pt *PartitionTable) introspect() partitionTableFeatures {
var features partitionTableFeatures

introspectPT := func(e Entity, path []Entity) error {
switch ent := e.(type) {
case *LVMLogicalVolume:
hasLVM = true
features.LVM = true
case *Btrfs:
hasBtrfs = true
features.Btrfs = true
case *Filesystem:
switch ent.GetFSType() {
case "vfat":
hasFAT = true
features.FAT = true
case "btrfs":
hasBtrfs = true
features.Btrfs = true
case "xfs":
hasXFS = true
features.XFS = true
case "ext4":
hasEXT4 = true
features.EXT4 = true
}
case *LUKSContainer:
hasLUKS = true
features.LUKS = true
}
return nil
}
_ = pt.ForEachEntity(introspectPT)

// TODO: LUKS
if hasLVM {
return features
}

func (pt *PartitionTable) GetBuildPackages() []string {
packages := []string{}

features := pt.introspect()

if features.LVM {
packages = append(packages, "lvm2")
}
if hasBtrfs {
if features.Btrfs {
packages = append(packages, "btrfs-progs")
}
if hasXFS {
if features.XFS {
packages = append(packages, "xfsprogs")
}
if hasFAT {
if features.FAT {
packages = append(packages, "dosfstools")
}
if hasEXT4 {
if features.EXT4 {
packages = append(packages, "e2fsprogs")
}
if hasLUKS {
if features.LUKS {
packages = append(packages,
"clevis",
"clevis-luks",
Expand Down
11 changes: 7 additions & 4 deletions internal/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,15 @@ type ImageType interface {
Manifest(b *blueprint.Customizations, options ImageOptions, repos []rpmmd.RepoConfig, packageSpecSets map[string][]rpmmd.PackageSpec, containers []container.Spec, seed int64) (Manifest, error)
}

type PartitioningMode string

// The ImageOptions specify options for a specific image build
type ImageOptions struct {
Size uint64
OSTree OSTreeImageOptions
Subscription *SubscriptionImageOptions
Facts *FactsImageOptions
Size uint64
PartitioningMode disk.PartitioningMode
OSTree OSTreeImageOptions
Subscription *SubscriptionImageOptions
Facts *FactsImageOptions
}

// The OSTreeImageOptions specify an ostree ref, checksum, URL, ContentURL, and RHSM. The meaning of
Expand Down
12 changes: 10 additions & 2 deletions internal/distro/fedora/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,17 @@ func (t *imageType) getPartitionTable(

imageSize := t.Size(options.Size)

lvmify := !t.rpmOstree
partitioningMode := options.PartitioningMode
if t.rpmOstree {
// IoT supports only raw, force it.
// If the caller specifically wants LVM, this is an error.
if partitioningMode == disk.LVMPartitioningMode {
return nil, fmt.Errorf("partitioning mode lvm not supported for %s on %s", t.Name(), t.arch.Name())
}
partitioningMode = disk.RawPartitioningMode
}

return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, partitioningMode, rng)
}

func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
Expand Down
2 changes: 1 addition & 1 deletion internal/distro/rhel7/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func (t *imageType) getPartitionTable(

imageSize := t.Size(options.Size)

return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, true, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, options.PartitioningMode, rng)
}

func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
Expand Down
12 changes: 10 additions & 2 deletions internal/distro/rhel8/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,17 @@ func (t *imageType) getPartitionTable(

imageSize := t.Size(options.Size)

lvmify := !t.rpmOstree
partitioningMode := options.PartitioningMode
if t.rpmOstree {
// Edge supports only raw, force it.
// If the caller specifically wants LVM, this is an error.
if partitioningMode == disk.LVMPartitioningMode {
return nil, fmt.Errorf("partitioning mode lvm not supported for %s on %s", t.Name(), t.arch.Name())
}
partitioningMode = disk.RawPartitioningMode
}

return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, partitioningMode, rng)
}

func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
Expand Down
Loading