disk: add RequiredSizes to ImageOptions

These RequiredSizes are a map that is passed on to the partition table
logic which had hardcoded defaults. This makes it possible to define
either no RequiredSizes (`nil`) or empty RequiredSizes which means no
further constraint checks or partition resizes will be done.
This commit is contained in:
Simon de Vlieger 2023-01-26 11:21:00 +01:00 committed by Ondřej Budai
parent d7f5fac183
commit 39879a9f60
17 changed files with 188 additions and 57 deletions

View file

@ -74,7 +74,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, false, nil, rng)
assert.NoError(t, err)
assert.GreaterOrEqual(t, newpt.Size, expectedSize)
}
@ -449,7 +449,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), false, nil, 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))
@ -475,12 +475,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), true, nil, 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), true, nil, rng)
assert.NoError(err, "PT %q BP %q: Partition table generation failed: (%s)", ptName, bpName, err)
rootPath := entityPath(mpt, "/")
@ -588,7 +588,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), false, nil, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
@ -602,7 +602,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), true, nil, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
@ -689,7 +689,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), true, nil, rng)
assert.NoError(err)
for mnt, expSize := range tc.ExpectedSizes {
path := entityPath(mpt, mnt)
@ -718,7 +718,7 @@ func TestNewBootWithSizeLVMify(t *testing.T) {
},
}
mpt, err := NewPartitionTable(&pt, custom, uint64(3*GiB), true, rng)
mpt, err := NewPartitionTable(&pt, custom, uint64(3*GiB), true, nil, rng)
assert.NoError(err)
for idx, c := range custom {
@ -971,3 +971,118 @@ func TestEnsureDirectorySizes(t *testing.T) {
}
}
func TestMinimumSizesWithRequiredSizes(t *testing.T) {
assert := assert.New(t)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(rand.NewSource(13))
pt := testPartitionTables["plain"]
type testCase struct {
Blueprint []blueprint.FilesystemCustomization
ExpectedMinSizes map[string]uint64
}
testCases := []testCase{
{ // specify small /usr -> / and /usr get default size
Blueprint: []blueprint.FilesystemCustomization{
{
Mountpoint: "/usr",
MinSize: 1 * MiB,
},
},
ExpectedMinSizes: map[string]uint64{
"/usr": 3 * GiB,
"/": 1 * GiB,
},
},
{ // specify small / and /usr -> / and /usr get default size
Blueprint: []blueprint.FilesystemCustomization{
{
Mountpoint: "/",
MinSize: 1 * MiB,
},
{
Mountpoint: "/usr",
MinSize: 1 * KiB,
},
},
ExpectedMinSizes: map[string]uint64{
"/usr": 3 * GiB,
"/": 1 * GiB,
},
},
{ // big /usr -> / gets default size
Blueprint: []blueprint.FilesystemCustomization{
{
Mountpoint: "/usr",
MinSize: 10 * GiB,
},
},
ExpectedMinSizes: map[string]uint64{
"/usr": 10 * GiB,
"/": 1 * GiB,
},
},
{
Blueprint: []blueprint.FilesystemCustomization{
{
Mountpoint: "/",
MinSize: 10 * GiB,
},
{
Mountpoint: "/home",
MinSize: 1 * MiB,
},
},
ExpectedMinSizes: map[string]uint64{
"/": 10 * GiB,
"/home": 1 * GiB,
},
},
{ // no separate /usr and no size for / -> / gets sum of default sizes for / and /usr
Blueprint: []blueprint.FilesystemCustomization{
{
Mountpoint: "/opt",
MinSize: 10 * GiB,
},
},
ExpectedMinSizes: map[string]uint64{
"/opt": 10 * GiB,
"/": 1 * GiB,
},
},
}
for idx, tc := range testCases {
{ // without LVM
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), false, map[string]uint64{"/": 1 * GiB, "/usr": 3 * GiB}, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
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)
}
}
{ // with LVM
mpt, err := NewPartitionTable(&pt, tc.Blueprint, uint64(3*GiB), true, map[string]uint64{"/": 1 * GiB, "/usr": 3 * GiB}, rng)
assert.NoError(err)
for mnt, minSize := range tc.ExpectedMinSizes {
path := entityPath(mpt, mnt)
assert.NotNil(path, "[%d] mountpoint %q not found", idx, mnt)
parent := path[1]
part, ok := parent.(*LVMLogicalVolume)
assert.True(ok, "[%d] %q parent (%v) is not an LVM logical volume", idx, mnt, parent)
assert.GreaterOrEqual(part.GetSize(), minSize,
"[%d] %q size %d should be greater or equal to %d", idx, mnt, part.GetSize(), minSize)
}
}
}
}

View file

@ -17,9 +17,11 @@ type PartitionTable struct {
SectorSize uint64 // Sector size in bytes
ExtraPadding uint64 // Extra space at the end of the partition table (sectors)
RequiredSizes map[string]uint64
}
func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, lvmify bool, rng *rand.Rand) (*PartitionTable, error) {
func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, lvmify bool, requiredSizes map[string]uint64, rng *rand.Rand) (*PartitionTable, error) {
newPT := basePT.Clone().(*PartitionTable)
// first pass: enlarge existing mountpoints and collect new ones
@ -40,11 +42,19 @@ func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.Filesyste
return nil, err
}
// TODO: make these overrideable for each image type
newPT.EnsureDirectorySizes(map[string]uint64{
"/": 1073741824,
"/usr": 2147483648,
})
// If no separate requiredSizes are given then we use our defaults
if requiredSizes == nil {
newPT.RequiredSizes = map[string]uint64{
"/": 1073741824,
"/usr": 2147483648,
}
} else {
newPT.RequiredSizes = requiredSizes
}
if len(newPT.RequiredSizes) != 0 {
newPT.EnsureDirectorySizes(newPT.RequiredSizes)
}
// Calculate partition table offsets and sizes
newPT.relayout(imageSize)
@ -65,12 +75,13 @@ func (pt *PartitionTable) Clone() Entity {
}
clone := &PartitionTable{
Size: pt.Size,
UUID: pt.UUID,
Type: pt.Type,
Partitions: make([]Partition, len(pt.Partitions)),
SectorSize: pt.SectorSize,
ExtraPadding: pt.ExtraPadding,
Size: pt.Size,
UUID: pt.UUID,
Type: pt.Type,
Partitions: make([]Partition, len(pt.Partitions)),
SectorSize: pt.SectorSize,
ExtraPadding: pt.ExtraPadding,
RequiredSizes: pt.RequiredSizes,
}
for idx, partition := range pt.Partitions {

View file

@ -125,10 +125,11 @@ type ImageType interface {
// The ImageOptions specify options for a specific image build
type ImageOptions struct {
Size uint64
OSTree OSTreeImageOptions
Subscription *SubscriptionImageOptions
Facts *FactsImageOptions
Size uint64
OSTree OSTreeImageOptions
Subscription *SubscriptionImageOptions
Facts *FactsImageOptions
RequiredSizes map[string]uint64
}
// The OSTreeImageOptions specify an ostree ref, checksum, URL, ContentURL, and RHSM. The meaning of

View file

@ -653,7 +653,7 @@ func (t *imageType) getPartitionTable(
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, options.RequiredSizes, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {

View file

@ -402,6 +402,10 @@ func iotRawImage(workload workload.Workload,
}
img.OSName = "fedora-iot"
// the iot raw image is laid out quite specifically, so we set no constraints; this means the
// default constraints don't apply either
options.RequiredSizes = map[string]uint64{}
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {

View file

@ -310,7 +310,7 @@ func (t *imageType) getPartitionTable(
imageSize := t.Size(options.Size)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, true, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, true, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {

View file

@ -157,7 +157,7 @@ func (t *imageType) getPartitionTable(
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {

View file

@ -160,7 +160,7 @@ func (t *imageType) getPartitionTable(
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, rng)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {

View file

@ -18,7 +18,7 @@ func TestGenDeviceCreationStages(t *testing.T) {
luks_lvm := testPartitionTables["luks+lvm"]
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, rng)
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, make(map[string]uint64), rng)
assert.NoError(err)
stages := GenDeviceCreationStages(pt, "image.raw")
@ -81,7 +81,7 @@ func TestGenDeviceFinishStages(t *testing.T) {
luks_lvm := testPartitionTables["luks+lvm"]
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, rng)
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, make(map[string]uint64), rng)
assert.NoError(err)
stages := GenDeviceFinishStages(pt, "image.raw")
@ -124,7 +124,7 @@ func TestGenDeviceFinishStagesOrderWithLVMClevisBind(t *testing.T) {
luks_lvm := testPartitionTables["luks+lvm+clevisBind"]
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, rng)
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, make(map[string]uint64), rng)
assert.NoError(err)
stages := GenDeviceFinishStages(pt, "image.raw")

View file

@ -18,7 +18,7 @@ func TestGenImageKernelOptions(t *testing.T) {
luks_lvm := testPartitionTables["luks+lvm"]
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, rng)
pt, err := disk.NewPartitionTable(&luks_lvm, []blueprint.FilesystemCustomization{}, 0, false, make(map[string]uint64), rng)
assert.NoError(err)
var uuid string