Skip to content
Merged
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
12 changes: 11 additions & 1 deletion hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,17 @@ It should not be used for workloads requiring predictable storage quotas.
title = "CRI Registry Configuration"
description = """\
The CRI registry configuration in v1apha1 legacy machine configuration under `.machine.registries` is now deprecated, but still supported for backwards compatibility.
New configuration documents `RegistryMirrorConfig`, `RegistryAuthConfig` and `RegistryTLSConfig` should be used instead.
New configuration documents `RegistryMirrorConfig`, `RegistryAuthConfig` and `RegistryTLSConfig` should be used instead.
"""

[notes.disk-user-volumes]
title = "New User Volume type - disk"
description = """\
`volumeType` in UserVolumeConfig can be set to `disk`.
When set to `disk`, a full block device is used for the volume.

When `volumeType = "disk"`:
- Size specific settings are not allowed in the provisioning block (`minSize`, `maxSize`, `grow`).
"""

[make_deps]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func Format(ctx context.Context, logger *zap.Logger, volumeContext ManagerContex
makefsOptions = append(makefsOptions, makefs.WithConfigFile(quirks.New("").XFSMkfsConfig()))

if err = makefs.XFS(volumeContext.Status.MountLocation, makefsOptions...); err != nil {
return fmt.Errorf("error formatting XFS: %w", err)
return xerrors.NewTaggedf[Retryable]("error formatting XFS: %w", err)
}
case block.FilesystemTypeEXT4:
var makefsOptions []makefs.Option
Expand All @@ -125,14 +125,14 @@ func Format(ctx context.Context, logger *zap.Logger, volumeContext ManagerContex
}

if err = makefs.Ext4(volumeContext.Status.MountLocation, makefsOptions...); err != nil {
return fmt.Errorf("error formatting ext4: %w", err)
return xerrors.NewTaggedf[Retryable]("error formatting ext4: %w", err)
}
case block.FilesystemTypeSwap:
if err = swap.Format(volumeContext.Status.MountLocation, swap.FormatOptions{
Label: volumeContext.Cfg.TypedSpec().Provisioning.FilesystemSpec.Label,
UUID: uuid.New(),
}); err != nil {
return fmt.Errorf("error formatting swap: %w", err)
return xerrors.NewTaggedf[Retryable]("error formatting swap: %w", err)
}
default:
return fmt.Errorf("unsupported filesystem type: %s", volumeContext.Cfg.TypedSpec().Provisioning.FilesystemSpec.Type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"fmt"

"github.com/google/cel-go/cel"
"github.com/siderolabs/gen/value"
"github.com/siderolabs/gen/xerrors"
"github.com/siderolabs/go-blockdevice/v2/partitioning"
Expand Down Expand Up @@ -44,8 +45,18 @@ func LocateAndProvision(ctx context.Context, logger *zap.Logger, volumeContext M

// attempt to discover the volume
for _, dv := range volumeContext.DiscoveredVolumes {
matchContext := map[string]any{
"volume": dv,
var locator *cel.Env

matchContext := map[string]any{}

switch volumeType { //nolint:exhaustive // we do not need to repeat exhaustive check here
case block.VolumeTypeDisk:
locator = celenv.DiskLocator()

case block.VolumeTypePartition:
locator = celenv.VolumeLocator()

matchContext["volume"] = dv
}

// add disk to the context, so we can use it in CEL expressions
Expand All @@ -63,7 +74,7 @@ func LocateAndProvision(ctx context.Context, logger *zap.Logger, volumeContext M
}
}

matches, err := volumeContext.Cfg.TypedSpec().Locator.Match.EvalBool(celenv.VolumeLocator(), matchContext)
matches, err := volumeContext.Cfg.TypedSpec().Locator.Match.EvalBool(locator, matchContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is also system_disk: boolean in the DiskLocator environment

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

system_disk is invalid here, as UserVolume with type disk cannot take control over the system_disk.

if err != nil {
return fmt.Errorf("error evaluating volume locator: %w", err)
}
Expand Down Expand Up @@ -127,6 +138,10 @@ func LocateAndProvision(ctx context.Context, logger *zap.Logger, volumeContext M
return fmt.Errorf("no disks matched selector for volume")
}

if volumeType == block.VolumeTypeDisk && len(matchedDisks) > 1 {
return fmt.Errorf("multiple disks matched selector for disk volume; matched disks: %v", matchedDisks)
}

logger.Debug("matched disks", zap.Strings("disks", matchedDisks))

// analyze each disk, until we find the one which is the best fit
Expand Down
58 changes: 44 additions & 14 deletions internal/app/machined/pkg/controllers/block/user_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,48 @@ var (
}

switch userVolumeConfig.Type().ValueOr(block.VolumeTypePartition) {
case block.VolumeTypeDirectory:
userVolumeResource.TransformFunc = newVolumeConfigBuilder().
WithType(block.VolumeTypeDirectory).
WithMount(block.MountSpec{
TargetPath: userVolumeConfig.Name(),
ParentID: constants.UserVolumeMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
FileMode: 0o755,
UID: 0,
GID: 0,
BindTarget: pointer.To(userVolumeConfig.Name()),
}).
WriterFunc()

case block.VolumeTypeDisk:
userVolumeResource.TransformFunc = newVolumeConfigBuilder().
WithType(block.VolumeTypeDisk).
WithLocator(userVolumeConfig.Provisioning().DiskSelector().ValueOr(noMatch)).
WithProvisioning(block.ProvisioningSpec{
Wave: block.WaveUserVolumes,
DiskSelector: block.DiskSelector{
Match: userVolumeConfig.Provisioning().DiskSelector().ValueOr(noMatch),
},
PartitionSpec: block.PartitionSpec{
TypeUUID: partition.LinuxFilesystemData,
},
FilesystemSpec: block.FilesystemSpec{
Type: userVolumeConfig.Filesystem().Type(),
},
}).
WithMount(block.MountSpec{
TargetPath: userVolumeConfig.Name(),
ParentID: constants.UserVolumeMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
FileMode: 0o755,
UID: 0,
GID: 0,
ProjectQuotaSupport: userVolumeConfig.Filesystem().ProjectQuotaSupport(),
}).
WithConvertEncryptionConfiguration(userVolumeConfig.Encryption()).
WriterFunc()

case block.VolumeTypePartition:
userVolumeResource.TransformFunc = newVolumeConfigBuilder().
WithType(block.VolumeTypePartition).
Expand Down Expand Up @@ -77,20 +119,8 @@ var (
}).
WithConvertEncryptionConfiguration(userVolumeConfig.Encryption()).
WriterFunc()
case block.VolumeTypeDirectory:
userVolumeResource.TransformFunc = newVolumeConfigBuilder().
WithType(block.VolumeTypeDirectory).
WithMount(block.MountSpec{
TargetPath: userVolumeConfig.Name(),
ParentID: constants.UserVolumeMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
FileMode: 0o755,
UID: 0,
GID: 0,
BindTarget: pointer.To(userVolumeConfig.Name()),
}).
WriterFunc()
case block.VolumeTypeDisk, block.VolumeTypeTmpfs, block.VolumeTypeSymlink, block.VolumeTypeOverlay:

case block.VolumeTypeTmpfs, block.VolumeTypeSymlink, block.VolumeTypeOverlay:
fallthrough

default:
Expand Down
92 changes: 57 additions & 35 deletions internal/app/machined/pkg/controllers/block/volume_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,18 +404,26 @@ func (suite *VolumeConfigSuite) TestReconcileUserRawVolumes() {
}

func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
uv1 := blockcfg.NewUserVolumeConfigV1Alpha1()
uv1.MetaName = "data1"
suite.Require().NoError(uv1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`)))
uv1.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("10GiB")
uv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("100GiB")
uv1.FilesystemSpec.FilesystemType = block.FilesystemTypeXFS

uv2 := blockcfg.NewUserVolumeConfigV1Alpha1()
uv2.MetaName = "data2"
suite.Require().NoError(uv2.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`)))
uv2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1TiB")
uv2.EncryptionSpec = blockcfg.EncryptionSpec{
userVolumeNames := []string{
"data-part1",
"data-part2",
"data-dir1",
"data-disk1",
}

uvPart1 := blockcfg.NewUserVolumeConfigV1Alpha1()
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.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.EncryptionSpec = blockcfg.EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,
EncryptionKeys: []blockcfg.EncryptionKey{
{
Expand All @@ -429,32 +437,45 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
},
}

uv3 := blockcfg.NewUserVolumeConfigV1Alpha1()
uv3.MetaName = "data3"
uv3.VolumeType = pointer.To(block.VolumeTypeDirectory)
uvDir1 := blockcfg.NewUserVolumeConfigV1Alpha1()
uvDir1.MetaName = userVolumeNames[2]
uvDir1.VolumeType = pointer.To(block.VolumeTypeDirectory)

uvDisk1 := blockcfg.NewUserVolumeConfigV1Alpha1()
uvDisk1.MetaName = userVolumeNames[3]
suite.Require().NoError(uvDisk1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`)))
uvDisk1.EncryptionSpec = blockcfg.EncryptionSpec{
EncryptionProvider: block.EncryptionProviderLUKS2,
EncryptionKeys: []blockcfg.EncryptionKey{
{
KeySlot: 0,
KeyTPM: &blockcfg.EncryptionKeyTPM{},
},
{
KeySlot: 1,
KeyStatic: &blockcfg.EncryptionKeyStatic{KeyData: "secret"},
},
},
}

sv1 := blockcfg.NewSwapVolumeConfigV1Alpha1()
sv1.MetaName = "swap"
suite.Require().NoError(sv1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`disk.transport == "nvme"`)))
sv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("2GiB")

ctr, err := container.New(uv1, uv2, uv3, sv1)
ctr, err := container.New(uvPart1, uvPart2, uvDir1, uvDisk1, sv1)
suite.Require().NoError(err)

cfg := config.NewMachineConfig(ctr)
suite.Create(cfg)

userVolumes := []string{
constants.UserVolumePrefix + "data1",
constants.UserVolumePrefix + "data2",
constants.UserVolumePrefix + "data3",
}
userVolumes := xslices.Map(userVolumeNames, func(in string) string { return constants.UserVolumePrefix + in })

ctest.AssertResources(suite, userVolumes, func(vc *block.VolumeConfig, asrt *assert.Assertions) {
asrt.Contains(vc.Metadata().Labels().Raw(), block.UserVolumeLabel)

switch vc.Metadata().ID() {
case userVolumes[0], userVolumes[1]:
case userVolumes[0], userVolumes[1], userVolumes[3]:
asrt.Equal(block.VolumeTypePartition, vc.TypedSpec().Type)

asrt.Contains(userVolumes, vc.TypedSpec().Provisioning.PartitionSpec.Label)
Expand All @@ -463,11 +484,12 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
asrt.NoError(err)

asrt.Contains(string(locator), vc.TypedSpec().Provisioning.PartitionSpec.Label)

case userVolumes[2]:
asrt.Equal(block.VolumeTypeDirectory, vc.TypedSpec().Type)
}

asrt.Contains([]string{"data1", "data2", "data3"}, vc.TypedSpec().Mount.TargetPath)
asrt.Contains(userVolumeNames, vc.TypedSpec().Mount.TargetPath)
asrt.Equal(constants.UserVolumeMountPoint, vc.TypedSpec().Mount.ParentID)

switch vc.Metadata().ID() {
Expand Down Expand Up @@ -506,8 +528,8 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {
suite.AddFinalizer(block.NewVolumeMountRequest(block.NamespaceName, volumeID).Metadata(), "test")
}

// drop the first volume
ctr, err = container.New(uv2)
// keep only the first volume
ctr, err = container.New(uvPart1)
suite.Require().NoError(err)

newCfg := config.NewMachineConfig(ctr)
Expand All @@ -516,30 +538,30 @@ func (suite *VolumeConfigSuite) TestReconcileUserSwapVolumes() {

// controller should tear down removed resources
ctest.AssertResources(suite, userVolumes, func(vc *block.VolumeConfig, asrt *assert.Assertions) {
if vc.Metadata().ID() == userVolumes[1] {
if vc.Metadata().ID() == userVolumes[0] {
asrt.Equal(resource.PhaseRunning, vc.Metadata().Phase())
} else {
asrt.Equal(resource.PhaseTearingDown, vc.Metadata().Phase())
}
})

ctest.AssertResources(suite, userVolumes, func(vmr *block.VolumeMountRequest, asrt *assert.Assertions) {
if vmr.Metadata().ID() == userVolumes[1] {
if vmr.Metadata().ID() == userVolumes[0] {
asrt.Equal(resource.PhaseRunning, vmr.Metadata().Phase())
} else {
asrt.Equal(resource.PhaseTearingDown, vmr.Metadata().Phase())
}
})

// remove finalizers
suite.RemoveFinalizer(block.NewVolumeConfig(block.NamespaceName, userVolumes[0]).Metadata(), "test")
suite.RemoveFinalizer(block.NewVolumeMountRequest(block.NamespaceName, userVolumes[0]).Metadata(), "test")
suite.RemoveFinalizer(block.NewVolumeConfig(block.NamespaceName, userVolumes[2]).Metadata(), "test")
suite.RemoveFinalizer(block.NewVolumeMountRequest(block.NamespaceName, userVolumes[2]).Metadata(), "test")
for _, userVolume := range userVolumes[1:] {
suite.RemoveFinalizer(block.NewVolumeConfig(block.NamespaceName, userVolume).Metadata(), "test")
suite.RemoveFinalizer(block.NewVolumeMountRequest(block.NamespaceName, userVolume).Metadata(), "test")
}

// now the resources should be removed
ctest.AssertNoResource[*block.VolumeConfig](suite, userVolumes[0])
ctest.AssertNoResource[*block.VolumeMountRequest](suite, userVolumes[0])
ctest.AssertNoResource[*block.VolumeConfig](suite, userVolumes[2])
ctest.AssertNoResource[*block.VolumeMountRequest](suite, userVolumes[2])
for _, userVolume := range userVolumes[1:] {
ctest.AssertNoResource[*block.VolumeConfig](suite, userVolume)
ctest.AssertNoResource[*block.VolumeMountRequest](suite, userVolume)
}
}
Loading