distro/*: allow /boot to be customized

Since the LVM support was added to all distros, our disk
related code is adaptive, i.e. we will set the correct BLS
and grub2 prefix if there a `boot` partiton is present in
the layout after all customizations happen, which includes
LVMification.
One thing that was not yet fully working was layouts that
do not yet have a `/boot` partition but allow LVMification.
In that case `NewPartitionTable` and if `/boot` was the
first (or only) customization, would LVMify the partition
which in turn would create the `/boot` partition; but after
`newPT.ensureLVM()` the call to `newPT.createFilesystem`
with `/boot` would try to create another `/boot` mountpoint.
In order to deal with this situation correctly we are now
using a two phase approach: 1) enlarge existing mountpoints
and collect new ones. 2) if there are new ones and LMVify
was allowed, switch to LVM layout. Do a second pass and now
create or enlarge existing partitions, handling `/boot` in
the process.
This commit is contained in:
Christian Kellner 2022-08-06 22:41:27 +02:00
parent 3c3f1b4969
commit b562d144ca
9 changed files with 70 additions and 17 deletions

View file

@ -553,7 +553,7 @@ func TestComposeUnsupportedMountPointV0(t *testing.T) {
description="TestComposeUnsupportedMountPointV0"
version="0.0.1"
[[customizations.filesystem]]
mountpoint = "/boot"
mountpoint = "/etc"
size = 4294967296
`
resp, err := PostTOMLBlueprintV0(testState.socket, bp)

View file

@ -54,6 +54,7 @@ const (
var MountpointPolicies = NewPathPolicies(map[string]PathPolicy{
"/": {Exact: true},
"/boot": {Exact: true},
"/var": {},
"/opt": {},
"/srv": {},

View file

@ -617,6 +617,36 @@ func TestMinimumSizes(t *testing.T) {
}
}
func TestNewBootWithSizeLVMify(t *testing.T) {
pt := testPartitionTables["plain-noboot"]
assert := assert.New(t)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(rand.NewSource(13))
custom := []blueprint.FilesystemCustomization{
{
Mountpoint: "/boot",
MinSize: 700 * MiB,
},
}
mpt, err := NewPartitionTable(&pt, custom, uint64(3*GiB), true, rng)
assert.NoError(err)
for idx, c := range custom {
mnt, minSize := c.Mountpoint, c.MinSize
path := entityPath(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*Partition)
assert.True(ok, "%q parent (%v) is not a partition", mnt, parent)
assert.GreaterOrEqual(part.GetSize(), minSize,
"[%d] %q size %d should be greater or equal to %d", idx, mnt, part.GetSize(), minSize)
}
}
func collectEntities(pt *PartitionTable) []Entity {
entities := make([]Entity, 0)
collector := func(ent Entity, path []Entity) error {

View file

@ -21,17 +21,33 @@ type PartitionTable struct {
func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, lvmify bool, rng *rand.Rand) (*PartitionTable, error) {
newPT := basePT.Clone().(*PartitionTable)
newMountpoints := []blueprint.FilesystemCustomization{}
// first pass: enlarge existing mountpoints and collect new ones
for _, mnt := range mountpoints {
if path := entityPath(newPT, mnt.Mountpoint); len(path) != 0 {
size := newPT.AlignUp(clampFSSize(mnt.Mountpoint, mnt.MinSize))
resizeEntityBranch(path, size)
} else {
newMountpoints = append(newMountpoints, mnt)
}
}
// if there is any new mountpoint and lvmify is enabled, ensure we have LVM layout
if lvmify && len(newMountpoints) > 0 {
err := newPT.ensureLVM()
if err != nil {
return nil, err
}
}
// second pass: deal with new mountpoints and newly created ones, after switching to
// the LVM layout, if requested, which might introduce new mount points, i.e. `/boot`
for _, mnt := range newMountpoints {
size := newPT.AlignUp(clampFSSize(mnt.Mountpoint, mnt.MinSize))
if path := entityPath(newPT, mnt.Mountpoint); len(path) != 0 {
resizeEntityBranch(path, size)
} else {
if lvmify {
err := newPT.ensureLVM()
if err != nil {
return nil, err
}
}
if err := newPT.createFilesystem(mnt.Mountpoint, size); err != nil {
return nil, err
}
@ -467,7 +483,13 @@ func (pt *PartitionTable) FindMountable(mountpoint string) Mountable {
func clampFSSize(mountpoint string, size uint64) uint64 {
// set a minimum size of 1GB for all mountpoints
// with the exception for '/boot' (= 500 MB)
var minSize uint64 = 1073741824
if mountpoint == "/boot" {
minSize = 524288000
}
if minSize > size {
return minSize
}

View file

@ -529,7 +529,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/boot",
Mountpoint: "/etc",
},
},
},
@ -544,7 +544,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
} else if imgTypeName == "fedora-iot-installer" {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/boot\"]")
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}

View file

@ -275,7 +275,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/boot",
Mountpoint: "/etc",
},
},
},
@ -285,7 +285,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, nil, 0)
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/boot\"]")
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}

View file

@ -614,7 +614,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/boot",
Mountpoint: "/etc",
},
},
},
@ -629,7 +629,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/boot\"]")
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}

View file

@ -606,7 +606,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/boot",
Mountpoint: "/etc",
},
},
},
@ -621,7 +621,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) {
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/boot\"]")
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}

View file

@ -242,7 +242,7 @@ mountpoint = "/etc"
size = 131072000
[[customizations.filesystem]]
mountpoint = "/boot"
mountpoint = "/lost+found"
size = 131072000
EOF
@ -254,7 +254,7 @@ build_image "$BLUEPRINT_FILE" rhel85-custom-filesystem-fail qcow2 true
FAILED_MOUNTPOINTS=()
greenprint "💬 Checking expected failures"
for MOUNTPOINT in '/etc' '/boot' ; do
for MOUNTPOINT in '/etc' '/lost+found' ; do
if ! [[ $ERROR_MSG == *"$MOUNTPOINT"* ]]; then
FAILED_MOUNTPOINTS+=("$MOUNTPOINT")
fi