From 749c9bf18b300a64b6e610241a2970337940a123 Mon Sep 17 00:00:00 2001 From: Drew Sirenko Date: Fri, 6 Oct 2023 14:47:11 +0000 Subject: [PATCH] Support clustered allocation when formatting ext4 filesystem --- docs/faq.md | 11 +++++++++++ docs/parameters.md | 2 ++ pkg/driver/constants.go | 13 +++++++++++-- pkg/driver/controller.go | 20 ++++++++++++++++---- pkg/driver/controller_test.go | 14 ++++++++++++++ pkg/driver/node.go | 4 ++++ pkg/driver/node_test.go | 14 ++++++++++++++ tests/e2e/format_options.go | 4 ++++ 8 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 docs/faq.md diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..9a6b1fb856 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,11 @@ +# Frequently Asked Questions + +## CreateVolume (`StorageClass`) Parameters + +### `ext4ClusterSize` + +Setting this parameter will enable the experimental `bigalloc` `ext4` feature. + +Warnings: +- The `bigalloc` feature may not be fully supported with your node's Linux kernel or may have various bugs. Please see the [ext4(5) man-page](https://man7.org/linux/man-pages/man5/ext4.5.html) for details. +- Linux kernel release 4.15 added support for resizing ext4 filesystems using clustered allocation. **Volumes mounted to nodes running prior kernel versions will fail to resize and require manual intervention and downtime of the volume. It is strongly recommended not to initiate a resize of a volume formatted with clustered allocation which is or will be attached to a node with a Linux kernel version prior to 4.15.** diff --git a/docs/parameters.md b/docs/parameters.md index 0818b71e2d..c0ab69ebab 100644 --- a/docs/parameters.md +++ b/docs/parameters.md @@ -20,6 +20,8 @@ The AWS EBS CSI Driver supports [tagging](tagging.md) through `StorageClass.para | "inodeSize" | | | The inode size to use when formatting the underlying filesystem. Only supported on linux nodes and with fstype `ext2`, `ext3`, `ext4`, or `xfs`. | | "bytesPerInode" | | | The `bytes-per-inode` to use when formatting the underlying filesystem. Only supported on linux nodes and with fstype `ext2`, `ext3`, `ext4`. | | "numberOfInodes" | | | The `number-of-inodes` to use when formatting the underlying filesystem. Only supported on linux nodes and with fstype `ext2`, `ext3`, `ext4`. | +| "ext4ClusterSize" | | | The cluster size to use when formatting an `ext4` filesystem using clustered allocation. Warning: setting this parameter enables the `bigalloc` `ext4` feature. See our [FAQ](/docs/faq.md). | + ## Restrictions * `gp3` is currently not supported on outposts. Outpost customers need to use a different type for their volumes. * If the requested IOPS (either directly from `iops` or from `iopsPerGB` multiplied by the volume's capacity) produces a value above the maximum IOPS allowed for the [volume type](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html), the IOPS will be capped at the maximum value allowed. If the value is lower than the minimal supported IOPS value per volume, either an error is returned (the default behavior), or the value is increased to fit into the supported range when `allowautoiopspergbincrease` is `"true"`. diff --git a/pkg/driver/constants.go b/pkg/driver/constants.go index 43b5e346d0..3cf230db02 100644 --- a/pkg/driver/constants.go +++ b/pkg/driver/constants.go @@ -94,6 +94,9 @@ const ( // NumberOfInodesKey configures the `number-of-inodes` when formatting a volume NumberOfInodesKey = "numberofinodes" + // Ext4ClusterSize enables the ext4 bigalloc option configures the cluster size when formatting an ext4 volume + Ext4ClusterSize = "ext4clustersize" + // TagKeyPrefix contains the prefix of a volume parameter that designates it as // a tag to be attached to the resource TagKeyPrefix = "tagSpecification" @@ -188,10 +191,14 @@ func (fsConfig fileSystemConfig) isParameterSupported(paramName string) bool { var ( FileSystemConfigs = map[string]fileSystemConfig{ FSTypeExt2: { - NotSupportedParams: map[string]struct{}{}, + NotSupportedParams: map[string]struct{}{ + Ext4ClusterSize: {}, + }, }, FSTypeExt3: { - NotSupportedParams: map[string]struct{}{}, + NotSupportedParams: map[string]struct{}{ + Ext4ClusterSize: {}, + }, }, FSTypeExt4: { NotSupportedParams: map[string]struct{}{}, @@ -200,6 +207,7 @@ var ( NotSupportedParams: map[string]struct{}{ BytesPerInodeKey: {}, NumberOfInodesKey: {}, + Ext4ClusterSize: {}, }, }, FSTypeNtfs: { @@ -208,6 +216,7 @@ var ( InodeSizeKey: {}, BytesPerInodeKey: {}, NumberOfInodesKey: {}, + Ext4ClusterSize: {}, }, }, } diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 34b83b06fd..3a691bd745 100644 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -137,10 +137,11 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol cloud.VolumeNameTagKey: volName, cloud.AwsEbsDriverTagKey: isManagedByDriver, } - blockSize string - inodeSize string - bytesPerInode string - numberOfInodes string + blockSize string + inodeSize string + bytesPerInode string + numberOfInodes string + ext4ClusterSize string ) tProps := new(template.PVProps) @@ -207,6 +208,11 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol return nil, status.Errorf(codes.InvalidArgument, "Could not parse numberOfInodes (%s): %v", value, err) } numberOfInodes = value + case Ext4ClusterSize: + if isAlphanumeric := util.StringIsAlphanumeric(value); !isAlphanumeric { + return nil, status.Errorf(codes.InvalidArgument, "Could not parse ext4ClusterSize (%s): %v", value, err) + } + ext4ClusterSize = value default: if strings.HasPrefix(key, TagKeyPrefix) { scTags = append(scTags, value) @@ -242,6 +248,12 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol return nil, err } } + if len(ext4ClusterSize) > 0 { + responseCtx[Ext4ClusterSize] = ext4ClusterSize + if err = validateVolumeCapabilities(req.GetVolumeCapabilities(), Ext4ClusterSize, FileSystemConfigs); err != nil { + return nil, err + } + } if blockExpress && volumeType != cloud.VolumeTypeIO2 { return nil, status.Errorf(codes.InvalidArgument, "Block Express is only supported on io2 volumes") diff --git a/pkg/driver/controller_test.go b/pkg/driver/controller_test.go index a39b819c6a..66dfb1a3ce 100644 --- a/pkg/driver/controller_test.go +++ b/pkg/driver/controller_test.go @@ -1688,6 +1688,13 @@ func TestCreateVolumeWithFormattingParameters(t *testing.T) { }, errExpected: false, }, + { + name: "success with ext4 bigalloc cluster size", + formattingOptionParameters: map[string]string{ + Ext4ClusterSize: "16384", + }, + errExpected: false, + }, { name: "failure with block size", formattingOptionParameters: map[string]string{ @@ -1716,6 +1723,13 @@ func TestCreateVolumeWithFormattingParameters(t *testing.T) { }, errExpected: true, }, + { + name: "failure with ext4 cluster size", + formattingOptionParameters: map[string]string{ + Ext4ClusterSize: "wrong_value", + }, + errExpected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 32bb7b9d3b..266f22b831 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -173,6 +173,7 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol if err != nil { return nil, err } + ext4ClusterSize, err := recheckFormattingOptionParameter(context, Ext4ClusterSize, FileSystemConfigs, fsType) if err != nil { return nil, err } @@ -262,6 +263,9 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol if len(numInodes) > 0 { formatOptions = append(formatOptions, "-N", numInodes) } + if len(ext4ClusterSize) > 0 { + formatOptions = append(formatOptions, "-O", "bigalloc", "-C", ext4ClusterSize) + } err = d.mounter.FormatAndMountSensitiveWithFormatOptions(source, target, fsType, mountOptions, nil, formatOptions) if err != nil { msg := fmt.Sprintf("could not format %q and mount it at %q: %v", source, target, err) diff --git a/pkg/driver/node_test.go b/pkg/driver/node_test.go index fbf785e29b..83ca729aa4 100644 --- a/pkg/driver/node_test.go +++ b/pkg/driver/node_test.go @@ -342,6 +342,20 @@ func TestNodeStageVolume(t *testing.T) { mockMounter.EXPECT().FormatAndMountSensitiveWithFormatOptions(gomock.Eq(devicePath), gomock.Eq(targetPath), gomock.Eq(defaultFsType), gomock.Any(), gomock.Nil(), gomock.Eq([]string{"-N", "13107200"})) }, }, + { + name: "success with cluster size in ext4", + request: &csi.NodeStageVolumeRequest{ + PublishContext: map[string]string{DevicePathKey: devicePath}, + StagingTargetPath: targetPath, + VolumeCapability: stdVolCap, + VolumeId: volumeID, + VolumeContext: map[string]string{Ext4ClusterSize: "16384"}, + }, + expectMock: func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier) { + successExpectMock(mockMounter, mockDeviceIdentifier) + mockMounter.EXPECT().FormatAndMountSensitiveWithFormatOptions(gomock.Eq(devicePath), gomock.Eq(targetPath), gomock.Eq(defaultFsType), gomock.Any(), gomock.Nil(), gomock.Eq([]string{"-O", "bigalloc", "-C", "16384"})) + }, + }, { name: "fail no VolumeId", request: &csi.NodeStageVolumeRequest{ diff --git a/tests/e2e/format_options.go b/tests/e2e/format_options.go index 6bb365db7b..540a97b454 100644 --- a/tests/e2e/format_options.go +++ b/tests/e2e/format_options.go @@ -46,6 +46,10 @@ var ( CreateVolumeParameterKey: ebscsidriver.NumberOfInodesKey, CreateVolumeParameterValue: "200192", }, + { + CreateVolumeParameterKey: ebscsidriver.Ext4ClusterSize, + CreateVolumeParameterValue: "16384", + }, } )