feat: support relative voume size

Include percent-based maxSize, e.g. use 50% of available space.

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
(cherry picked from commit 83f2bdb9ce6c9466716a6ac9c94dc2222e569ee8)
This commit is contained in:
Mateusz Urbanek 2025-10-30 10:35:36 +01:00
parent 3d997d7421
commit aebbbaf274
No known key found for this signature in database
GPG Key ID: F16F84591E26D77F
34 changed files with 489 additions and 123 deletions

View File

@ -158,6 +158,7 @@ message PartitionSpec {
bool grow = 3;
string label = 4;
string type_uuid = 5;
uint64 relative_max_size = 6;
}
// ProvisioningSpec is the spec for volume provisioning.

View File

@ -536,7 +536,7 @@ func (m *Qemu) initExtraDisks() error {
Match: cel.MustExpression(cel.ParseBooleanExpression(fmt.Sprintf("'%s' in disk.symlinks", m.Provisioner.UserDiskName(diskID+1)), celenv.DiskLocator())),
},
ProvisioningMinSize: block.MustByteSize(volumeSize),
ProvisioningMaxSize: block.MustByteSize(volumeSize),
ProvisioningMaxSize: block.MustSize(volumeSize),
}
userVolume.EncryptionSpec = encryptionSpec

View File

@ -64,7 +64,7 @@ func CreatePartition(ctx context.Context, logger *zap.Logger, diskPath string, v
available := pt.LargestContiguousAllocatable()
size := volumeCfg.TypedSpec().Provisioning.PartitionSpec.MinSize
maxSize := volumeCfg.TypedSpec().Provisioning.PartitionSpec.MaxSize
maxSize := volumeCfg.TypedSpec().Provisioning.PartitionSpec.ResolveMaxSize(available)
if available < size {
// should never happen

View File

@ -113,11 +113,12 @@ func GetEphemeralVolumeTransformer(inContainer bool) volumeConfigTransformer {
Match: extraVolumeConfig.Provisioning().DiskSelector().ValueOr(systemDiskMatch()),
},
PartitionSpec: block.PartitionSpec{
MinSize: extraVolumeConfig.Provisioning().MinSize().ValueOr(quirks.New("").PartitionSizes().EphemeralMinSize()),
MaxSize: extraVolumeConfig.Provisioning().MaxSize().ValueOr(0),
Grow: extraVolumeConfig.Provisioning().Grow().ValueOr(true),
Label: constants.EphemeralPartitionLabel,
TypeUUID: partition.LinuxFilesystemData,
MinSize: extraVolumeConfig.Provisioning().MinSize().ValueOr(quirks.New("").PartitionSizes().EphemeralMinSize()),
MaxSize: extraVolumeConfig.Provisioning().MaxSize().ValueOrZero(),
RelativeMaxSize: extraVolumeConfig.Provisioning().RelativeMaxSize().ValueOrZero(),
Grow: extraVolumeConfig.Provisioning().Grow().ValueOr(true),
Label: constants.EphemeralPartitionLabel,
TypeUUID: partition.LinuxFilesystemData,
},
FilesystemSpec: block.FilesystemSpec{
Type: block.FilesystemTypeXFS,

View File

@ -422,7 +422,7 @@ func TestEphemeralVolumeTransformerWithExtraConfig(t *testing.T) {
ephemeralConfig := blockcfg.NewVolumeConfigV1Alpha1()
ephemeralConfig.MetaName = constants.EphemeralPartitionLabel
ephemeralConfig.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("10GiB")
ephemeralConfig.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("100GiB")
ephemeralConfig.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("100GiB")
cfg, err := container.New(
baseCfg.DeepCopy(),

View File

@ -100,11 +100,12 @@ func UserVolumeTransformer(c configconfig.Config) ([]VolumeResource, error) {
Match: userVolumeConfig.Provisioning().DiskSelector().ValueOr(noMatch),
},
PartitionSpec: block.PartitionSpec{
MinSize: cmp.Or(userVolumeConfig.Provisioning().MinSize().ValueOrZero(), MinUserVolumeSize),
MaxSize: userVolumeConfig.Provisioning().MaxSize().ValueOrZero(),
Grow: userVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinuxFilesystemData,
MinSize: cmp.Or(userVolumeConfig.Provisioning().MinSize().ValueOrZero(), MinUserVolumeSize),
MaxSize: userVolumeConfig.Provisioning().MaxSize().ValueOrZero(),
RelativeMaxSize: userVolumeConfig.Provisioning().RelativeMaxSize().ValueOrZero(),
Grow: userVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinuxFilesystemData,
},
FilesystemSpec: block.FilesystemSpec{
Type: userVolumeConfig.Filesystem().Type(),
@ -157,11 +158,12 @@ func RawVolumeTransformer(c configconfig.Config) ([]VolumeResource, error) {
Match: rawVolumeConfig.Provisioning().DiskSelector().ValueOr(noMatch),
},
PartitionSpec: block.PartitionSpec{
MinSize: cmp.Or(rawVolumeConfig.Provisioning().MinSize().ValueOrZero(), MinUserVolumeSize),
MaxSize: rawVolumeConfig.Provisioning().MaxSize().ValueOrZero(),
Grow: rawVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinuxFilesystemData,
MinSize: cmp.Or(rawVolumeConfig.Provisioning().MinSize().ValueOrZero(), MinUserVolumeSize),
MaxSize: rawVolumeConfig.Provisioning().MaxSize().ValueOrZero(),
RelativeMaxSize: rawVolumeConfig.Provisioning().RelativeMaxSize().ValueOrZero(),
Grow: rawVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinuxFilesystemData,
},
FilesystemSpec: block.FilesystemSpec{
Type: block.FilesystemTypeNone,
@ -230,10 +232,11 @@ func SwapVolumeTransformer(c configconfig.Config) ([]VolumeResource, error) {
Match: swapVolumeConfig.Provisioning().DiskSelector().ValueOr(noMatch),
},
PartitionSpec: block.PartitionSpec{
MaxSize: cmp.Or(swapVolumeConfig.Provisioning().MaxSize().ValueOrZero(), MinUserVolumeSize),
Grow: swapVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinkSwap,
MaxSize: cmp.Or(swapVolumeConfig.Provisioning().MaxSize().ValueOrZero(), MinUserVolumeSize),
RelativeMaxSize: swapVolumeConfig.Provisioning().RelativeMaxSize().ValueOrZero(),
Grow: swapVolumeConfig.Provisioning().Grow().ValueOrZero(),
Label: volumeID,
TypeUUID: partition.LinkSwap,
},
FilesystemSpec: block.FilesystemSpec{
Type: block.FilesystemTypeSwap,

View File

@ -301,7 +301,7 @@ func (suite *VolumeConfigSuite) TestReconcileExtraEPHEMERALConfig() {
Match: cel.MustExpression(cel.ParseBooleanExpression(`disk.transport == "nvme"`, celenv.DiskLocator())),
},
ProvisioningGrow: pointer.To(false),
ProvisioningMaxSize: blockcfg.MustByteSize("2.5TiB"),
ProvisioningMaxSize: blockcfg.MustSize("2.5TiB"),
},
EncryptionSpec: blockcfg.EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,
@ -341,12 +341,12 @@ func (suite *VolumeConfigSuite) TestReconcileUserRawVolumes() {
rv1.MetaName = "data1"
suite.Require().NoError(rv1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`)))
rv1.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("10GiB")
rv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("100GiB")
rv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("100GiB")
rv2 := blockcfg.NewRawVolumeConfigV1Alpha1()
rv2.MetaName = "data2"
suite.Require().NoError(rv2.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`)))
rv2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1TiB")
rv2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("1TiB")
rv2.EncryptionSpec = blockcfg.EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,
EncryptionKeys: []blockcfg.EncryptionKey{
@ -415,14 +415,14 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
uvPart1.MetaName = userVolumeNames[0]
suite.Require().NoError(uvPart1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`)))
uvPart1.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("10GiB")
uvPart1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("100GiB")
uvPart1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("100GiB")
uvPart1.FilesystemSpec.FilesystemType = block.FilesystemTypeXFS
uvPart2 := blockcfg.NewUserVolumeConfigV1Alpha1()
uvPart2.MetaName = userVolumeNames[1]
uvPart2.VolumeType = pointer.To(block.VolumeTypePartition)
suite.Require().NoError(uvPart2.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`)))
uvPart2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1TiB")
uvPart2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("1TiB")
uvPart2.EncryptionSpec = blockcfg.EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,
EncryptionKeys: []blockcfg.EncryptionKey{
@ -461,7 +461,7 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
sv1 := blockcfg.NewSwapVolumeConfigV1Alpha1()
sv1.MetaName = "swap"
suite.Require().NoError(sv1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.transport == "nvme"`)))
sv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("2GiB")
sv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("2GiB")
ctr, err := container.New(uvPart1, uvPart2, uvDir1, uvDisk1, sv1)
suite.Require().NoError(err)

View File

@ -240,7 +240,7 @@ func (suite *ImageCacheConfigSuite) TestReconcileWithImageCacheVolume() {
volumeConfig := blockcfg.NewVolumeConfigV1Alpha1()
volumeConfig.MetaName = constants.ImageCachePartitionLabel
volumeConfig.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("10GiB")
volumeConfig.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("10GiB")
container, err := container.New(v1alpha1Cfg, volumeConfig)
suite.Require().NoError(err)

View File

@ -512,18 +512,30 @@ func (suite *VolumesSuite) TestUserVolumesPartition() {
userVolumeIDs := xslices.Map(volumeIDs, func(volumeID string) string { return constants.UserVolumePrefix + volumeID })
configDocs := xslices.Map(volumeIDs, func(volumeID string) any {
configDocs := xslices.Map(volumeIDs[:1], func(volumeID string) any {
doc := blockcfg.NewUserVolumeConfigV1Alpha1()
doc.MetaName = volumeID
doc.ProvisioningSpec.DiskSelectorSpec.Match = cel.MustExpression(
cel.ParseBooleanExpression(fmt.Sprintf("'%s' in disk.symlinks", disk.TypedSpec().Symlinks[0]), celenv.DiskLocator()),
)
doc.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("100MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1GiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("1GiB")
return doc
})
configDocs = append(configDocs, xslices.Map(volumeIDs[1:], func(volumeID string) any {
doc := blockcfg.NewUserVolumeConfigV1Alpha1()
doc.MetaName = volumeID
doc.ProvisioningSpec.DiskSelectorSpec.Match = cel.MustExpression(
cel.ParseBooleanExpression(fmt.Sprintf("'%s' in disk.symlinks", disk.TypedSpec().Symlinks[0]), celenv.DiskLocator()),
)
doc.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("100MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("20%")
return doc
})...)
// create user volumes
suite.PatchMachineConfig(ctx, configDocs...)
@ -979,7 +991,7 @@ func (suite *VolumesSuite) TestRawVolumes() {
cel.ParseBooleanExpression(fmt.Sprintf("'%s' in disk.symlinks", disk.TypedSpec().Symlinks[0]), celenv.DiskLocator()),
)
doc.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("100MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("500MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("500MiB")
return doc
})
@ -1104,7 +1116,7 @@ func (suite *VolumesSuite) TestExistingVolumes() {
cel.ParseBooleanExpression(fmt.Sprintf("'%s' in disk.symlinks", disk.TypedSpec().Symlinks[0]), celenv.DiskLocator()),
)
userVolumeDoc.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("100MiB")
userVolumeDoc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1GiB")
userVolumeDoc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("1GiB")
// create user volumes
suite.PatchMachineConfig(ctx, userVolumeDoc)
@ -1299,7 +1311,7 @@ func (suite *VolumesSuite) TestSwapOnOff() {
},
}
doc.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("100MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("500MiB")
doc.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustSize("500MiB")
// create user volumes
suite.PatchMachineConfig(ctx, doc)

View File

@ -1236,14 +1236,15 @@ func (x *MountStatusSpec) GetDetached() bool {
// PartitionSpec is the spec for volume partitioning.
type PartitionSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
MinSize uint64 `protobuf:"varint,1,opt,name=min_size,json=minSize,proto3" json:"min_size,omitempty"`
MaxSize uint64 `protobuf:"varint,2,opt,name=max_size,json=maxSize,proto3" json:"max_size,omitempty"`
Grow bool `protobuf:"varint,3,opt,name=grow,proto3" json:"grow,omitempty"`
Label string `protobuf:"bytes,4,opt,name=label,proto3" json:"label,omitempty"`
TypeUuid string `protobuf:"bytes,5,opt,name=type_uuid,json=typeUuid,proto3" json:"type_uuid,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
MinSize uint64 `protobuf:"varint,1,opt,name=min_size,json=minSize,proto3" json:"min_size,omitempty"`
MaxSize uint64 `protobuf:"varint,2,opt,name=max_size,json=maxSize,proto3" json:"max_size,omitempty"`
Grow bool `protobuf:"varint,3,opt,name=grow,proto3" json:"grow,omitempty"`
Label string `protobuf:"bytes,4,opt,name=label,proto3" json:"label,omitempty"`
TypeUuid string `protobuf:"bytes,5,opt,name=type_uuid,json=typeUuid,proto3" json:"type_uuid,omitempty"`
RelativeMaxSize uint64 `protobuf:"varint,6,opt,name=relative_max_size,json=relativeMaxSize,proto3" json:"relative_max_size,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PartitionSpec) Reset() {
@ -1311,6 +1312,13 @@ func (x *PartitionSpec) GetTypeUuid() string {
return ""
}
func (x *PartitionSpec) GetRelativeMaxSize() uint64 {
if x != nil {
return x.RelativeMaxSize
}
return 0
}
// ProvisioningSpec is the spec for volume provisioning.
type ProvisioningSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -2431,13 +2439,14 @@ const file_resource_definitions_block_block_proto_rawDesc = "" +
"\tread_only\x18\x05 \x01(\bR\breadOnly\x122\n" +
"\x15project_quota_support\x18\x06 \x01(\bR\x13projectQuotaSupport\x12n\n" +
"\x13encryption_provider\x18\a \x01(\x0e2=.talos.resource.definitions.enums.BlockEncryptionProviderTypeR\x12encryptionProvider\x12\x1a\n" +
"\bdetached\x18\b \x01(\bR\bdetached\"\x8c\x01\n" +
"\bdetached\x18\b \x01(\bR\bdetached\"\xb8\x01\n" +
"\rPartitionSpec\x12\x19\n" +
"\bmin_size\x18\x01 \x01(\x04R\aminSize\x12\x19\n" +
"\bmax_size\x18\x02 \x01(\x04R\amaxSize\x12\x12\n" +
"\x04grow\x18\x03 \x01(\bR\x04grow\x12\x14\n" +
"\x05label\x18\x04 \x01(\tR\x05label\x12\x1b\n" +
"\ttype_uuid\x18\x05 \x01(\tR\btypeUuid\"\xae\x02\n" +
"\ttype_uuid\x18\x05 \x01(\tR\btypeUuid\x12*\n" +
"\x11relative_max_size\x18\x06 \x01(\x04R\x0frelativeMaxSize\"\xae\x02\n" +
"\x10ProvisioningSpec\x12S\n" +
"\rdisk_selector\x18\x01 \x01(\v2..talos.resource.definitions.block.DiskSelectorR\fdiskSelector\x12V\n" +
"\x0epartition_spec\x18\x02 \x01(\v2/.talos.resource.definitions.block.PartitionSpecR\rpartitionSpec\x12\x12\n" +

View File

@ -1208,6 +1208,11 @@ func (m *PartitionSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.RelativeMaxSize != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RelativeMaxSize))
i--
dAtA[i] = 0x30
}
if len(m.TypeUuid) > 0 {
i -= len(m.TypeUuid)
copy(dAtA[i:], m.TypeUuid)
@ -2693,6 +2698,9 @@ func (m *PartitionSpec) SizeVT() (n int) {
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.RelativeMaxSize != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RelativeMaxSize))
}
n += len(m.unknownFields)
return n
}
@ -6421,6 +6429,25 @@ func (m *PartitionSpec) UnmarshalVT(dAtA []byte) error {
}
m.TypeUuid = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RelativeMaxSize", wireType)
}
m.RelativeMaxSize = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RelativeMaxSize |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])

View File

@ -32,6 +32,7 @@ type VolumeProvisioningConfig interface {
Grow() optional.Optional[bool]
MinSize() optional.Optional[uint64]
MaxSize() optional.Optional[uint64]
RelativeMaxSize() optional.Optional[uint64]
}
// WrapVolumesConfigList wraps a list of VolumeConfig providing access by name.
@ -81,6 +82,14 @@ func (emptyVolumeConfig) MaxSize() optional.Optional[uint64] {
return optional.None[uint64]()
}
func (emptyVolumeConfig) RelativeMinSize() optional.Optional[uint64] {
return optional.None[uint64]()
}
func (emptyVolumeConfig) RelativeMaxSize() optional.Optional[uint64] {
return optional.None[uint64]()
}
// UserVolumeConfig defines the interface to access user volume configuration.
type UserVolumeConfig interface {
NamedDocument

View File

@ -313,9 +313,9 @@
"maxSize": {
"type": "string",
"title": "maxSize",
"description": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.\n",
"markdownDescription": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.",
"x-intellij-html-description": "\u003cp\u003eThe maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\u003c/p\u003e\n\n\u003cp\u003eSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.\u003c/p\u003e\n"
"description": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.\n",
"markdownDescription": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.",
"x-intellij-html-description": "\u003cp\u003eThe maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\u003c/p\u003e\n\n\u003cp\u003eSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.\u003c/p\u003e\n"
}
},
"additionalProperties": false,

View File

@ -666,9 +666,9 @@ func (ProvisioningSpec) Doc() *encoder.Doc {
},
{
Name: "maxSize",
Type: "ByteSize",
Type: "Size",
Note: "",
Description: "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.",
Description: "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.",
Comments: [3]string{"" /* encoder.HeadComment */, "The maximum size of the volume, if not specified the volume can grow to the size of the" /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
@ -676,6 +676,7 @@ func (ProvisioningSpec) Doc() *encoder.Doc {
doc.Fields[2].AddExample("", "2.5GiB")
doc.Fields[3].AddExample("", "50GiB")
doc.Fields[3].AddExample("", "80%")
return doc
}

View File

@ -29,19 +29,6 @@ type ByteSize struct {
raw []byte
}
// MustByteSize returns a new ByteSize with the given value.
//
// It panics if the value is invalid.
func MustByteSize(value string) ByteSize {
var bs ByteSize
if err := bs.UnmarshalText([]byte(value)); err != nil {
panic(err)
}
return bs
}
// Value returns the value.
func (bs ByteSize) Value() uint64 {
return pointer.SafeDeref(bs.value)

View File

@ -31,13 +31,29 @@ func (o *RawVolumeConfigV1Alpha1) DeepCopy() *RawVolumeConfigV1Alpha1 {
cp.ProvisioningSpec.ProvisioningMinSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMinSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMinSize.raw, o.ProvisioningSpec.ProvisioningMinSize.raw)
}
if o.ProvisioningSpec.ProvisioningMaxSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.value
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = new(PercentageSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw)
}
}
if o.ProvisioningSpec.ProvisioningMaxSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw)
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = new(ByteSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw)
}
}
if o.EncryptionSpec.EncryptionKeys != nil {
cp.EncryptionSpec.EncryptionKeys = make([]EncryptionKey, len(o.EncryptionSpec.EncryptionKeys))
@ -99,13 +115,29 @@ func (o *SwapVolumeConfigV1Alpha1) DeepCopy() *SwapVolumeConfigV1Alpha1 {
cp.ProvisioningSpec.ProvisioningMinSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMinSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMinSize.raw, o.ProvisioningSpec.ProvisioningMinSize.raw)
}
if o.ProvisioningSpec.ProvisioningMaxSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.value
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = new(PercentageSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw)
}
}
if o.ProvisioningSpec.ProvisioningMaxSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw)
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = new(ByteSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw)
}
}
if o.EncryptionSpec.EncryptionKeys != nil {
cp.EncryptionSpec.EncryptionKeys = make([]EncryptionKey, len(o.EncryptionSpec.EncryptionKeys))
@ -171,13 +203,29 @@ func (o *UserVolumeConfigV1Alpha1) DeepCopy() *UserVolumeConfigV1Alpha1 {
cp.ProvisioningSpec.ProvisioningMinSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMinSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMinSize.raw, o.ProvisioningSpec.ProvisioningMinSize.raw)
}
if o.ProvisioningSpec.ProvisioningMaxSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.value
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = new(PercentageSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw)
}
}
if o.ProvisioningSpec.ProvisioningMaxSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw)
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = new(ByteSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw)
}
}
if o.FilesystemSpec.ProjectQuotaSupportConfig != nil {
cp.FilesystemSpec.ProjectQuotaSupportConfig = new(bool)
@ -243,13 +291,29 @@ func (o *VolumeConfigV1Alpha1) DeepCopy() *VolumeConfigV1Alpha1 {
cp.ProvisioningSpec.ProvisioningMinSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMinSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMinSize.raw, o.ProvisioningSpec.ProvisioningMinSize.raw)
}
if o.ProvisioningSpec.ProvisioningMaxSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.value
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = new(PercentageSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.PercentageSize.raw)
}
}
if o.ProvisioningSpec.ProvisioningMaxSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw)
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = new(ByteSize)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = new(uint64)
*cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value = *o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.value
}
if o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw != nil {
cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.ByteSize.raw)
}
}
if o.EncryptionSpec.EncryptionKeys != nil {
cp.EncryptionSpec.EncryptionKeys = make([]EncryptionKey, len(o.EncryptionSpec.EncryptionKeys))

View File

@ -0,0 +1,82 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
import (
"bytes"
"encoding"
"fmt"
"slices"
"strconv"
"github.com/siderolabs/go-pointer"
"gopkg.in/yaml.v3"
)
// Check interfaces.
var (
_ encoding.TextMarshaler = PercentageSize{}
_ encoding.TextUnmarshaler = (*PercentageSize)(nil)
_ yaml.IsZeroer = PercentageSize{}
)
// PercentageSize is a size in percents.
type PercentageSize struct {
value *uint64
raw []byte
}
// Value returns the value.
func (ps PercentageSize) Value() uint64 {
return pointer.SafeDeref(ps.value)
}
// MarshalText implements encoding.TextMarshaler.
func (ps PercentageSize) MarshalText() ([]byte, error) {
if ps.raw != nil {
return ps.raw, nil
}
if ps.value != nil {
return []byte(strconv.FormatUint(*ps.value, 10)), nil
}
return nil, nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (ps *PercentageSize) UnmarshalText(text []byte) error {
if len(text) == 0 {
ps.value = nil
ps.raw = nil
return nil
}
if !bytes.HasSuffix(text, []byte("%")) {
return fmt.Errorf("percentage must end with '%%'")
}
numStr := string(text[:len(text)-1])
value, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return fmt.Errorf("invalid percentage value: %w", err)
}
if value < 0 || value > 100 {
return fmt.Errorf("percentage must be between 0 and 100, got %v", value)
}
ps.value = pointer.To(uint64(value))
ps.raw = slices.Clone(text)
return nil
}
// IsZero implements yaml.IsZeroer.
func (ps PercentageSize) IsZero() bool {
return ps.value == nil && ps.raw == nil
}

View File

@ -90,7 +90,7 @@ func exampleRawVolumeConfigV1Alpha1() *RawVolumeConfigV1Alpha1 {
DiskSelectorSpec: DiskSelector{
Match: cel.MustExpression(cel.ParseBooleanExpression(`disk.transport == "nvme"`, celenv.DiskLocator())),
},
ProvisioningMaxSize: MustByteSize("50GiB"),
ProvisioningMaxSize: MustSize("50GiB"),
}
return cfg

View File

@ -39,7 +39,7 @@ func TestRawVolumeConfigMarshalUnmarshal(t *testing.T) {
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.transport == "nvme" && !system_disk`)))
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("100GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("100GiB")
return c
},
@ -178,7 +178,7 @@ func TestRawVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("10GiB")
return c
},
@ -259,7 +259,7 @@ func TestRawVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
return c

View File

@ -0,0 +1,117 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
import (
"encoding"
"strings"
"gopkg.in/yaml.v3"
)
// Check interfaces.
var (
_ encoding.TextMarshaler = Size{}
_ encoding.TextUnmarshaler = (*PercentageSize)(nil)
_ yaml.IsZeroer = Size{}
)
// Size is either a PercentageSize or ByteSize.
type Size struct {
PercentageSize *PercentageSize
ByteSize *ByteSize
}
// MustSize returns a new Size with the given value.
//
// It panics if the value is invalid.
func MustSize(value string) Size {
var s Size
if err := s.UnmarshalText([]byte(value)); err != nil {
panic(err)
}
return s
}
// MustByteSize returns a new Size with the given ByteSize value.
//
// It panics if the value is invalid.
func MustByteSize(value string) ByteSize {
var bs ByteSize
if err := bs.UnmarshalText([]byte(value)); err != nil {
panic(err)
}
return bs
}
// Value returns the value.
func (s Size) Value() uint64 {
if s.ByteSize != nil {
return s.ByteSize.Value()
}
return 0
}
// RelativeValue returns the relative value.
func (s Size) RelativeValue() (uint64, bool) {
if s.PercentageSize != nil {
return s.PercentageSize.Value(), true
}
return 0, false
}
// MarshalText implements encoding.TextMarshaler.
func (s Size) MarshalText() ([]byte, error) {
if s.ByteSize != nil {
return s.ByteSize.MarshalText()
}
if s.PercentageSize != nil {
return s.PercentageSize.MarshalText()
}
return nil, nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (s *Size) UnmarshalText(text []byte) error {
if string(text) == "" {
return nil
}
if strings.Contains(string(text), "%") {
var ps PercentageSize
if err := ps.UnmarshalText(text); err != nil {
return err
}
s.PercentageSize = &ps
} else {
var bs ByteSize
if err := bs.UnmarshalText(text); err != nil {
return err
}
s.ByteSize = &bs
}
return nil
}
// IsZero implements yaml.IsZeroer.
func (s Size) IsZero() bool {
return (s.PercentageSize == nil || s.PercentageSize.IsZero()) && (s.ByteSize == nil || s.ByteSize.IsZero())
}
// IsRelative returns if the Size is a relative size.
func (s Size) IsRelative() bool {
return (s.PercentageSize != nil && !s.PercentageSize.IsZero())
}

View File

@ -5,6 +5,7 @@
package block_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -13,15 +14,17 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/types/block"
)
func TestByteSizeUnmarshal(t *testing.T) {
func TestSizeUnmarshal(t *testing.T) {
t.Parallel()
for _, test := range []struct {
in string
in string
want uint64
}{
{in: "", want: 0},
{in: "100%", want: 100},
{in: "33.4%", want: 33},
{in: "33.4124%", want: 33},
{in: "1048576", want: 1048576},
{in: "2.5GiB", want: 2684354560},
{in: "2.5GB", want: 2500000000},
@ -31,13 +34,25 @@ func TestByteSizeUnmarshal(t *testing.T) {
t.Run(test.in, func(t *testing.T) {
t.Parallel()
var bs block.ByteSize
var s block.Size
require.NoError(t, bs.UnmarshalText([]byte(test.in)))
require.NoError(t, s.UnmarshalText([]byte(test.in)))
assert.Equal(t, test.want, bs.Value())
if strings.Contains(test.in, "%") {
assert.Zero(t, s.Value())
out, err := bs.MarshalText()
val, ok := s.RelativeValue()
assert.True(t, ok)
assert.Equal(t, test.want, val)
} else {
assert.Equal(t, test.want, s.Value())
val, ok := s.RelativeValue()
assert.False(t, ok)
assert.Zero(t, val)
}
out, err := s.MarshalText()
require.NoError(t, err)
assert.Equal(t, test.in, string(out))

View File

@ -90,7 +90,7 @@ func exampleSwapVolumeConfigV1Alpha1() *SwapVolumeConfigV1Alpha1 {
Match: cel.MustExpression(cel.ParseBooleanExpression(`disk.transport == "nvme"`, celenv.DiskLocator())),
},
ProvisioningMinSize: MustByteSize("3GiB"),
ProvisioningMaxSize: MustByteSize("4GiB"),
ProvisioningMaxSize: MustSize("4GiB"),
}
cfg.EncryptionSpec = EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,

View File

@ -39,7 +39,7 @@ func TestSwapVolumeConfigMarshalUnmarshal(t *testing.T) {
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.transport == "nvme" && !system_disk`)))
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("100GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("100GiB")
return c
},
@ -178,7 +178,7 @@ func TestSwapVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("10GiB")
return c
},
@ -259,7 +259,7 @@ func TestSwapVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
return c

View File

@ -113,7 +113,7 @@ func exampleUserVolumeConfigV1Alpha1Partition() *UserVolumeConfigV1Alpha1 {
DiskSelectorSpec: DiskSelector{
Match: cel.MustExpression(cel.ParseBooleanExpression(`disk.transport == "nvme"`, celenv.DiskLocator())),
},
ProvisioningMaxSize: MustByteSize("50GiB"),
ProvisioningMaxSize: MustSize("50GiB"),
}
cfg.FilesystemSpec = FilesystemSpec{
FilesystemType: block.FilesystemTypeXFS,

View File

@ -40,7 +40,7 @@ func TestUserVolumeConfigMarshalUnmarshal(t *testing.T) {
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.transport == "nvme" && !system_disk`)))
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("100GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("100GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeXFS
return c
@ -195,7 +195,7 @@ func TestUserVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("10GiB")
return c
},
@ -210,7 +210,7 @@ func TestUserVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeISO9660
@ -349,7 +349,7 @@ func TestUserVolumeConfigValidate(t *testing.T) {
c.VolumeType = pointer.To(blockres.VolumeTypeDisk)
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeEXT4
@ -405,7 +405,7 @@ func TestUserVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeEXT4
@ -421,7 +421,7 @@ func TestUserVolumeConfigValidate(t *testing.T) {
c.VolumeType = pointer.To(blockres.VolumeTypePartition)
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeEXT4

View File

@ -93,13 +93,15 @@ type ProvisioningSpec struct {
// The maximum size of the volume, if not specified the volume can grow to the size of the
// disk.
//
// Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB.
// Size is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.
// examples:
// - value: >
// "50GiB"
// - value: >
// "80%"
// schema:
// type: string
ProvisioningMaxSize ByteSize `yaml:"maxSize,omitempty"`
ProvisioningMaxSize Size `yaml:"maxSize,omitempty"`
}
// DiskSelector selects a disk for the volume.
@ -135,7 +137,7 @@ func exampleVolumeConfigEphemeralV1Alpha1() *VolumeConfigV1Alpha1 {
DiskSelectorSpec: DiskSelector{
Match: cel.MustExpression(cel.ParseBooleanExpression(`disk.transport == "nvme"`, celenv.DiskLocator())),
},
ProvisioningMaxSize: MustByteSize("50GiB"),
ProvisioningMaxSize: MustSize("50GiB"),
}
return cfg
@ -230,7 +232,7 @@ func (s ProvisioningSpec) Validate(required bool, sizeSupported bool) ([]string,
}
if sizeSupported {
if !s.ProvisioningMinSize.IsZero() && !s.ProvisioningMaxSize.IsZero() {
if !s.ProvisioningMinSize.IsZero() && !s.ProvisioningMaxSize.IsZero() && !s.ProvisioningMaxSize.IsRelative() {
if s.ProvisioningMinSize.Value() > s.ProvisioningMaxSize.Value() {
validationErrors = errors.Join(validationErrors, errors.New("min size is greater than max size"))
}
@ -306,3 +308,17 @@ func (s ProvisioningSpec) MaxSize() optional.Optional[uint64] {
return optional.Some(s.ProvisioningMaxSize.Value())
}
// RelativeMaxSize implements config.VolumeProvisioningConfig interface.
func (s ProvisioningSpec) RelativeMaxSize() optional.Optional[uint64] {
if s.ProvisioningMaxSize.IsZero() {
return optional.None[uint64]()
}
val, ok := s.ProvisioningMaxSize.RelativeValue()
if !ok {
return optional.None[uint64]()
}
return optional.Some(val)
}

View File

@ -59,7 +59,7 @@ func TestVolumeConfigMarshalUnmarshal(t *testing.T) {
c := block.NewVolumeConfigV1Alpha1()
c.MetaName = constants.EphemeralPartitionLabel
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
return c
@ -164,7 +164,7 @@ func TestVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("10GiB")
return c
},
@ -179,7 +179,7 @@ func TestVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.StatePartitionLabel
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("2.5GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("10GiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("10GiB")
return c
},
@ -251,7 +251,7 @@ func TestVolumeConfigValidate(t *testing.T) {
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.size > 120u * GiB`)))
c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMaxSize = block.MustSize("2.5TiB")
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
return c

View File

@ -92,6 +92,9 @@ type PartitionSpec struct {
// Partition maximum size in bytes, if not set, grows to the maximum size.
MaxSize uint64 `yaml:"maxSize,omitempty" protobuf:"2"`
// Partition maximum size (relative), if not set, grows to the maximum size.
RelativeMaxSize uint64 `yaml:"relativeMaxSize" protobuf:"6"`
// Grow the partition automatically to the maximum size.
Grow bool `yaml:"grow" protobuf:"3"`
@ -102,6 +105,15 @@ type PartitionSpec struct {
TypeUUID string `yaml:"typeUUID,omitempty" protobuf:"5"`
}
// ResolveMaxSize resolves the maximum size of the partition.
func (ps *PartitionSpec) ResolveMaxSize(available uint64) uint64 {
if ps.RelativeMaxSize != 0 {
return available * ps.RelativeMaxSize / 100
}
return ps.MaxSize
}
// LocatorSpec is the spec for volume locator.
//
//gotagsrewrite:gen

View File

@ -5798,6 +5798,7 @@ PartitionSpec is the spec for volume partitioning.
| grow | [bool](#bool) | | |
| label | [string](#string) | | |
| type_uuid | [string](#string) | | |
| relative_max_size | [uint64](#uint64) | | |

View File

@ -81,8 +81,10 @@ ProvisioningSpec describes how the volume is provisioned.
|`minSize` |ByteSize |The minimum size of the volume.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
minSize: 2.5GiB
{{< /highlight >}}</details> | |
|`maxSize` |ByteSize |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|`maxSize` |Size |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
maxSize: 50GiB
{{< /highlight >}}{{< highlight yaml >}}
maxSize: 80%
{{< /highlight >}}</details> | |

View File

@ -78,8 +78,10 @@ ProvisioningSpec describes how the volume is provisioned.
|`minSize` |ByteSize |The minimum size of the volume.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
minSize: 2.5GiB
{{< /highlight >}}</details> | |
|`maxSize` |ByteSize |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|`maxSize` |Size |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
maxSize: 50GiB
{{< /highlight >}}{{< highlight yaml >}}
maxSize: 80%
{{< /highlight >}}</details> | |

View File

@ -64,6 +64,7 @@ provisioning:
# # The maximum size of the volume, if not specified the volume can grow to the size of the
# maxSize: 50GiB
# maxSize: 80%
# The filesystem describes how the volume is formatted.
filesystem:
type: xfs # Filesystem type. Default is `xfs`.
@ -176,8 +177,10 @@ ProvisioningSpec describes how the volume is provisioned.
|`minSize` |ByteSize |The minimum size of the volume.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
minSize: 2.5GiB
{{< /highlight >}}</details> | |
|`maxSize` |ByteSize |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|`maxSize` |Size |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
maxSize: 50GiB
{{< /highlight >}}{{< highlight yaml >}}
maxSize: 80%
{{< /highlight >}}</details> | |

View File

@ -78,8 +78,10 @@ ProvisioningSpec describes how the volume is provisioned.
|`minSize` |ByteSize |The minimum size of the volume.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
minSize: 2.5GiB
{{< /highlight >}}</details> | |
|`maxSize` |ByteSize |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes, but can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|`maxSize` |Size |The maximum size of the volume, if not specified the volume can grow to the size of the<br>disk.<br><br>Size is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
maxSize: 50GiB
{{< /highlight >}}{{< highlight yaml >}}
maxSize: 80%
{{< /highlight >}}</details> | |

View File

@ -313,9 +313,9 @@
"maxSize": {
"type": "string",
"title": "maxSize",
"description": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.\n",
"markdownDescription": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.",
"x-intellij-html-description": "\u003cp\u003eThe maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\u003c/p\u003e\n\n\u003cp\u003eSize is specified in bytes, but can be expressed in human readable format, e.g. 100MB.\u003c/p\u003e\n"
"description": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.\n",
"markdownDescription": "The maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\n\nSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.",
"x-intellij-html-description": "\u003cp\u003eThe maximum size of the volume, if not specified the volume can grow to the size of the\ndisk.\u003c/p\u003e\n\n\u003cp\u003eSize is specified in bytes or in percents. It can be expressed in human readable format, e.g. 100MB.\u003c/p\u003e\n"
}
},
"additionalProperties": false,