diff --git a/internal/app/machined/pkg/controllers/block/user_volume_config.go b/internal/app/machined/pkg/controllers/block/user_volume_config.go index 64aba1bcd..2c36736df 100644 --- a/internal/app/machined/pkg/controllers/block/user_volume_config.go +++ b/internal/app/machined/pkg/controllers/block/user_volume_config.go @@ -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 { diff --git a/internal/integration/api/volumes.go b/internal/integration/api/volumes.go index cf35a15db..4e0c32f0d 100644 --- a/internal/integration/api/volumes.go +++ b/internal/integration/api/volumes.go @@ -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...) diff --git a/pkg/machinery/config/config/volume.go b/pkg/machinery/config/config/volume.go index e222bc06e..44d788c69 100644 --- a/pkg/machinery/config/config/volume.go +++ b/pkg/machinery/config/config/volume.go @@ -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. diff --git a/pkg/machinery/config/schemas/config.schema.json b/pkg/machinery/config/schemas/config.schema.json index 3d259d8d7..6a2f28249 100644 --- a/pkg/machinery/config/schemas/config.schema.json +++ b/pkg/machinery/config/schemas/config.schema.json @@ -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, diff --git a/pkg/machinery/config/types/block/block_doc.go b/pkg/machinery/config/types/block/block_doc.go index e1055d232..5fa517ffb 100644 --- a/pkg/machinery/config/types/block/block_doc.go +++ b/pkg/machinery/config/types/block/block_doc.go @@ -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 */}, + }, }, } diff --git a/pkg/machinery/config/types/block/deep_copy.generated.go b/pkg/machinery/config/types/block/deep_copy.generated.go index 47ad4e53e..f82539de2 100644 --- a/pkg/machinery/config/types/block/deep_copy.generated.go +++ b/pkg/machinery/config/types/block/deep_copy.generated.go @@ -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) diff --git a/pkg/machinery/config/types/block/testdata/uservolumeconfig_prjquota.yaml b/pkg/machinery/config/types/block/testdata/uservolumeconfig_prjquota.yaml new file mode 100644 index 000000000..a7ab35ae7 --- /dev/null +++ b/pkg/machinery/config/types/block/testdata/uservolumeconfig_prjquota.yaml @@ -0,0 +1,10 @@ +apiVersion: v1alpha1 +kind: UserVolumeConfig +name: secret-store +provisioning: + diskSelector: + match: '!system_disk' + minSize: 10GiB +filesystem: + type: xfs + projectQuotaSupport: true diff --git a/pkg/machinery/config/types/block/user_volume_config.go b/pkg/machinery/config/types/block/user_volume_config.go index 7797aca98..8907e508b 100644 --- a/pkg/machinery/config/types/block/user_volume_config.go +++ b/pkg/machinery/config/types/block/user_volume_config.go @@ -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 } diff --git a/pkg/machinery/config/types/block/user_volume_config_test.go b/pkg/machinery/config/types/block/user_volume_config_test.go index e706316d9..a212e82be 100644 --- a/pkg/machinery/config/types/block/user_volume_config_test.go +++ b/pkg/machinery/config/types/block/user_volume_config_test.go @@ -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", diff --git a/website/content/v1.11/reference/configuration/block/uservolumeconfig.md b/website/content/v1.11/reference/configuration/block/uservolumeconfig.md index e5a3c42c9..5da3e9ddc 100644 --- a/website/content/v1.11/reference/configuration/block/uservolumeconfig.md +++ b/website/content/v1.11/reference/configuration/block/uservolumeconfig.md @@ -130,6 +130,7 @@ FilesystemSpec configures the filesystem for the volume. | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`type` |FilesystemType |Filesystem type. Default is `xfs`. |`ext4`
`xfs`
| +|`projectQuotaSupport` |bool |Enables project quota support, valid only for 'xfs' filesystem.

Note: changing this value might require a full remount of the filesystem. | | diff --git a/website/content/v1.11/schemas/config.schema.json b/website/content/v1.11/schemas/config.schema.json index 3d259d8d7..6a2f28249 100644 --- a/website/content/v1.11/schemas/config.schema.json +++ b/website/content/v1.11/schemas/config.schema.json @@ -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,