feat: support project quota support for user volumes

Just exposting existing value via the config.

Fixes #11090

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2025-07-09 19:28:38 +04:00
parent 52656cc3c1
commit 6415055847
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
11 changed files with 106 additions and 8 deletions

View File

@ -275,12 +275,13 @@ func (ctrl *UserVolumeConfigController) handleUserVolumeConfig(
},
}
v.TypedSpec().Mount = block.MountSpec{
TargetPath: userVolumeConfig.Name(),
ParentID: constants.UserVolumeMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
FileMode: 0o755,
UID: 0,
GID: 0,
TargetPath: userVolumeConfig.Name(),
ParentID: constants.UserVolumeMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
FileMode: 0o755,
UID: 0,
GID: 0,
ProjectQuotaSupport: userVolumeConfig.Filesystem().ProjectQuotaSupport(),
}
if err := convertEncryptionConfiguration(userVolumeConfig.Encryption(), v.TypedSpec()); err != nil {

View File

@ -23,6 +23,7 @@ import (
"github.com/cosi-project/runtime/pkg/state"
"github.com/google/uuid"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/go-pointer"
"github.com/stretchr/testify/assert"
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
@ -598,7 +599,8 @@ func (suite *VolumesSuite) TestUserVolumes() {
// wait for the discovered volume to disappear
rtestutils.AssertNoResource[*block.DiscoveredVolume](ctx, suite.T(), suite.Client.COSI, filepath.Base(vs.TypedSpec().Location))
// re-create the volume
// re-create the volume with project quota support
configDocs[0].(*blockcfg.UserVolumeConfigV1Alpha1).FilesystemSpec.ProjectQuotaSupportConfig = pointer.To(true)
suite.PatchMachineConfig(ctx, configDocs[0])
rtestutils.AssertResources(ctx, suite.T(), suite.Client.COSI, userVolumeIDs,
@ -608,7 +610,15 @@ func (suite *VolumesSuite) TestUserVolumes() {
)
rtestutils.AssertResources(ctx, suite.T(), suite.Client.COSI, userVolumeIDs,
func(vs *block.MountStatus, _ *assert.Assertions) {})
func(vs *block.MountStatus, asrt *assert.Assertions) {
if vs.Metadata().ID() == userVolumeIDs[0] {
// check that the project quota support is enabled
asrt.True(vs.TypedSpec().ProjectQuotaSupport, "project quota support should be enabled for %s", vs.Metadata().ID())
} else {
// check that the project quota support is disabled
asrt.False(vs.TypedSpec().ProjectQuotaSupport, "project quota support should be disabled for %s", vs.Metadata().ID())
}
})
// clean up
suite.RemoveMachineConfigDocumentsByName(ctx, blockcfg.UserVolumeConfigKind, volumeIDs...)

View File

@ -89,6 +89,8 @@ type UserVolumeConfig interface {
type FilesystemConfig interface {
// Type returns the filesystem type.
Type() block.FilesystemType
// ProjectQuotaSupport returns true if the filesystem should support project quotas.
ProjectQuotaSupport() bool
}
// SwapVolumeConfig defines the interface to access swap volume configuration.

View File

@ -179,6 +179,13 @@
"description": "Filesystem type. Default is xfs.\n",
"markdownDescription": "Filesystem type. Default is `xfs`.",
"x-intellij-html-description": "\u003cp\u003eFilesystem type. Default is \u003ccode\u003exfs\u003c/code\u003e.\u003c/p\u003e\n"
},
"projectQuotaSupport": {
"type": "boolean",
"title": "projectQuotaSupport",
"description": "Enables project quota support, valid only for xfs filesystem.\n\nNote: changing this value might require a full remount of the filesystem.\n",
"markdownDescription": "Enables project quota support, valid only for 'xfs' filesystem.\n\nNote: changing this value might require a full remount of the filesystem.",
"x-intellij-html-description": "\u003cp\u003eEnables project quota support, valid only for \u0026lsquo;xfs\u0026rsquo; filesystem.\u003c/p\u003e\n\n\u003cp\u003eNote: changing this value might require a full remount of the filesystem.\u003c/p\u003e\n"
}
},
"additionalProperties": false,

View File

@ -342,6 +342,13 @@ func (FilesystemSpec) Doc() *encoder.Doc {
"xfs",
},
},
{
Name: "projectQuotaSupport",
Type: "bool",
Note: "",
Description: "Enables project quota support, valid only for 'xfs' filesystem.\n\nNote: changing this value might require a full remount of the filesystem.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enables project quota support, valid only for 'xfs' filesystem." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}

View File

@ -85,6 +85,10 @@ func (o *UserVolumeConfigV1Alpha1) DeepCopy() *UserVolumeConfigV1Alpha1 {
cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw))
copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw)
}
if o.FilesystemSpec.ProjectQuotaSupportConfig != nil {
cp.FilesystemSpec.ProjectQuotaSupportConfig = new(bool)
*cp.FilesystemSpec.ProjectQuotaSupportConfig = *o.FilesystemSpec.ProjectQuotaSupportConfig
}
if o.EncryptionSpec.EncryptionKeys != nil {
cp.EncryptionSpec.EncryptionKeys = make([]EncryptionKey, len(o.EncryptionSpec.EncryptionKeys))
copy(cp.EncryptionSpec.EncryptionKeys, o.EncryptionSpec.EncryptionKeys)

View File

@ -0,0 +1,10 @@
apiVersion: v1alpha1
kind: UserVolumeConfig
name: secret-store
provisioning:
diskSelector:
match: '!system_disk'
minSize: 10GiB
filesystem:
type: xfs
projectQuotaSupport: true

View File

@ -11,6 +11,8 @@ import (
"fmt"
"strings"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/talos/pkg/machinery/cel"
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
"github.com/siderolabs/talos/pkg/machinery/config/config"
@ -206,6 +208,11 @@ type FilesystemSpec struct {
// - ext4
// - xfs
FilesystemType block.FilesystemType `yaml:"type,omitempty"`
// description: |
// Enables project quota support, valid only for 'xfs' filesystem.
//
// Note: changing this value might require a full remount of the filesystem.
ProjectQuotaSupportConfig *bool `yaml:"projectQuotaSupport,omitempty"`
}
// Type implements config.FilesystemConfig interface.
@ -217,6 +224,11 @@ func (s FilesystemSpec) Type() block.FilesystemType {
return s.FilesystemType
}
// ProjectQuotaSupport implements config.FilesysteemConfig interface.
func (s FilesystemSpec) ProjectQuotaSupport() bool {
return pointer.SafeDeref(s.ProjectQuotaSupportConfig)
}
// Validate implements config.Validator interface.
func (s FilesystemSpec) Validate() ([]string, error) {
switch s.FilesystemType { //nolint:exhaustive
@ -227,5 +239,9 @@ func (s FilesystemSpec) Validate() ([]string, error) {
return nil, fmt.Errorf("unsupported filesystem type: %s", s.FilesystemType)
}
if pointer.SafeDeref(s.ProjectQuotaSupportConfig) && s.Type() != block.FilesystemTypeXFS {
return nil, fmt.Errorf("project quota support is only available for xfs filesystem")
}
return nil, nil
}

View File

@ -11,6 +11,7 @@ import (
"strings"
"testing"
"github.com/siderolabs/go-pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -69,6 +70,21 @@ func TestUserVolumeConfigMarshalUnmarshal(t *testing.T) {
},
}
return c
},
},
{
name: "prjquota",
filename: "uservolumeconfig_prjquota.yaml",
cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 {
c := block.NewUserVolumeConfigV1Alpha1()
c.MetaName = "secret-store"
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`)))
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeXFS
c.FilesystemSpec.ProjectQuotaSupportConfig = pointer.To(true)
return c
},
},
@ -269,6 +285,23 @@ func TestUserVolumeConfigValidate(t *testing.T) {
expectedErrors: "at least one encryption key type must be specified for slot 0\nduplicate key slot 1",
},
{
name: "prjquota not supported",
cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 {
c := block.NewUserVolumeConfigV1Alpha1()
c.MetaName = constants.EphemeralPartitionLabel
require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`)))
c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB")
c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeEXT4
c.FilesystemSpec.ProjectQuotaSupportConfig = pointer.To(true)
return c
},
expectedErrors: "project quota support is only available for xfs filesystem",
},
{
name: "valid",

View File

@ -130,6 +130,7 @@ FilesystemSpec configures the filesystem for the volume.
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`type` |FilesystemType |Filesystem type. Default is `xfs`. |`ext4`<br />`xfs`<br /> |
|`projectQuotaSupport` |bool |Enables project quota support, valid only for 'xfs' filesystem.<br><br>Note: changing this value might require a full remount of the filesystem. | |

View File

@ -179,6 +179,13 @@
"description": "Filesystem type. Default is xfs.\n",
"markdownDescription": "Filesystem type. Default is `xfs`.",
"x-intellij-html-description": "\u003cp\u003eFilesystem type. Default is \u003ccode\u003exfs\u003c/code\u003e.\u003c/p\u003e\n"
},
"projectQuotaSupport": {
"type": "boolean",
"title": "projectQuotaSupport",
"description": "Enables project quota support, valid only for xfs filesystem.\n\nNote: changing this value might require a full remount of the filesystem.\n",
"markdownDescription": "Enables project quota support, valid only for 'xfs' filesystem.\n\nNote: changing this value might require a full remount of the filesystem.",
"x-intellij-html-description": "\u003cp\u003eEnables project quota support, valid only for \u0026lsquo;xfs\u0026rsquo; filesystem.\u003c/p\u003e\n\n\u003cp\u003eNote: changing this value might require a full remount of the filesystem.\u003c/p\u003e\n"
}
},
"additionalProperties": false,