Skip to content

Commit

Permalink
add bytesPerINode and numberOfINodes parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
fgksgf committed Jul 10, 2023
1 parent 09929ac commit 3c7e41b
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 149 deletions.
2 changes: 2 additions & 0 deletions docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The AWS EBS CSI Driver supports [tagging](tagging.md) through `StorageClass.para
| "kmsKeyId" | | | The full ARN of the key to use when encrypting the volume. If not specified, AWS will use the default KMS key for the region the volume is in. This will be an auto-generated key called `/aws/ebs` if not changed. |
| "blockSize" | | | The block size to use when formatting the underlying filesystem. Only supported on linux nodes and with fstype `ext2`, `ext3`, `ext4`, or `xfs`. |
| "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`. |

**Appendix**
* `gp3` is currently not supported on outposts. Outpost customers need to use a different type for their volumes.
Expand Down
13 changes: 12 additions & 1 deletion pkg/driver/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ const (
// INodeSizeKey configures the inode size when formatting a volume
INodeSizeKey = "inodesize"

// BytesPerINodeKey configures the `bytes-per-inode` when formatting a volume
BytesPerINodeKey = "bytesperinode"

// NumberOfINodesKey configures the `number-of-inodes` when formatting a volume
NumberOfINodesKey = "numberofinodes"

// TagKeyPrefix contains the prefix of a volume parameter that designates it as
// a tag to be attached to the resource
TagKeyPrefix = "tagSpecification"
Expand Down Expand Up @@ -173,10 +179,15 @@ var (
INodeSizeExcludedFSTypes = map[string]struct{}{
FSTypeNtfs: {},
}
// INodeOptionsExcludedFSTypes contains the filesystems that `bytes-per-inode` and `number-of-inodes` are *NOT* supported on
INodeOptionsExcludedFSTypes = map[string]struct{}{
FSTypeNtfs: {},
FSTypeXfs: {},
}
)

// constants for node k8s API use
const (
// AgentNotReadyTaintKey contains the key of taints to be removed on driver startup
// AgentNotReadyNodeTaintKey contains the key of taints to be removed on driver startup
AgentNotReadyNodeTaintKey = "ebs.csi.aws.com/agent-not-ready"
)
89 changes: 53 additions & 36 deletions pkg/driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
cloud.VolumeNameTagKey: volName,
cloud.AwsEbsDriverTagKey: isManagedByDriver,
}
blockSize string
inodeSize string
blockSize string
inodeSize string
bytesPerINode string
numberOfINodes string
)

tProps := new(template.PVProps)
Expand Down Expand Up @@ -195,6 +197,18 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
return nil, status.Errorf(codes.InvalidArgument, "Could not parse inodeSize (%s): %v", value, err)
}
inodeSize = value
case BytesPerINodeKey:
_, err = strconv.Atoi(value)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Could not parse bytesPerINode (%s): %v", value, err)
}
bytesPerINode = value
case NumberOfINodesKey:
_, err = strconv.Atoi(value)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Could not parse numberOfINodes (%s): %v", value, err)
}
numberOfINodes = value
default:
if strings.HasPrefix(key, TagKeyPrefix) {
scTags = append(scTags, value)
Expand All @@ -208,44 +222,26 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol

if len(blockSize) > 0 {
responseCtx[BlockSizeKey] = blockSize

for _, volCap := range req.GetVolumeCapabilities() {
switch volCap.GetAccessType().(type) {
case *csi.VolumeCapability_Block:
return nil, status.Error(codes.InvalidArgument, "Cannot use block size with block volume")
}

mountVolume := volCap.GetMount()
if mountVolume == nil {
return nil, status.Error(codes.InvalidArgument, "CreateVolume: mount is nil within volume capability")
}

fsType := mountVolume.GetFsType()

if _, ok := BlockSizeExcludedFSTypes[fsType]; ok {
return nil, status.Errorf(codes.InvalidArgument, "Cannot use block size with fstype %s", fsType)
}
if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), "block size", BlockSizeExcludedFSTypes); err != nil {
return nil, err
}
}

if len(inodeSize) > 0 {
responseCtx[INodeSizeKey] = inodeSize

for _, volCap := range req.GetVolumeCapabilities() {
switch volCap.GetAccessType().(type) {
case *csi.VolumeCapability_Block:
return nil, status.Error(codes.InvalidArgument, "Cannot use inode size with block volume")
}

mountVolume := volCap.GetMount()
if mountVolume == nil {
return nil, status.Error(codes.InvalidArgument, "CreateVolume: mount is nil within volume capability")
}

fsType := mountVolume.GetFsType()
if _, ok := INodeSizeExcludedFSTypes[fsType]; ok {
return nil, status.Errorf(codes.InvalidArgument, "Cannot use inode size with fstype %s", fsType)
}
if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), "inode size", INodeSizeExcludedFSTypes); err != nil {
return nil, err
}
}
if len(bytesPerINode) > 0 {
responseCtx[BytesPerINodeKey] = bytesPerINode
if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), "bytesPerINode", INodeOptionsExcludedFSTypes); err != nil {
return nil, err
}
}
if len(numberOfINodes) > 0 {
responseCtx[NumberOfINodesKey] = numberOfINodes
if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), "numberOfINodes", INodeOptionsExcludedFSTypes); err != nil {
return nil, err
}
}

Expand Down Expand Up @@ -967,3 +963,24 @@ func BuildOutpostArn(segments map[string]string) string {
segments[AwsOutpostIDKey],
)
}

func validateVolumeCapabilities(volumeCapabilities []*csi.VolumeCapability, paramName string, excludedFsTypes map[string]struct{}) error {
for _, volCap := range volumeCapabilities {
switch volCap.GetAccessType().(type) {
case *csi.VolumeCapability_Block:
return status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot use %s with block volume", paramName))
}

mountVolume := volCap.GetMount()
if mountVolume == nil {
return status.Error(codes.InvalidArgument, "CreateVolume: mount is nil within volume capability")
}

fsType := mountVolume.GetFsType()
if _, ok := excludedFsTypes[fsType]; ok {
return status.Errorf(codes.InvalidArgument, "Cannot use %s with fstype %s", paramName, fsType)
}
}

return nil
}
147 changes: 58 additions & 89 deletions pkg/driver/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1637,109 +1637,78 @@ func TestCreateVolume(t *testing.T) {
{
name: "success with block size",
testFunc: func(t *testing.T) {
req := &csi.CreateVolumeRequest{
Name: "random-vol-name",
CapacityRange: stdCapRange,
VolumeCapabilities: stdVolCap,
Parameters: map[string]string{
BlockSizeKey: "4096",
},
}

ctx := context.Background()

mockDisk := &cloud.Disk{
VolumeID: req.Name,
AvailabilityZone: expZone,
CapacityGiB: util.BytesToGiB(stdVolSize),
}

mockCtl := gomock.NewController(t)
defer mockCtl.Finish()

mockCloud := cloud.NewMockCloud(mockCtl)
mockCloud.EXPECT().CreateDisk(gomock.Eq(ctx), gomock.Eq(req.Name), gomock.Any()).Return(mockDisk, nil)

awsDriver := controllerService{
cloud: mockCloud,
inFlight: internal.NewInFlight(),
driverOptions: &DriverOptions{},
}

response, err := awsDriver.CreateVolume(ctx, req)
if err != nil {
srvErr, ok := status.FromError(err)
if !ok {
t.Fatalf("Could not get error status code from error: %v", srvErr)
}
t.Fatalf("Unexpected error: %v", srvErr.Code())
}

context := response.Volume.VolumeContext
if blockSize, ok := context[BlockSizeKey]; ok {
if blockSize != "4096" {
t.Fatalf("Invalid %s in VolumeContext (got %s expected 4096)", BlockSizeKey, blockSize)
}
} else {
t.Fatalf("Missing key %s in VolumeContext", BlockSizeKey)
}
testSuccessWithParameter(t, BlockSizeKey, "4096", stdCapRange, stdVolCap, stdVolSize)
},
},
{
name: "success with inode size",
testFunc: func(t *testing.T) {
req := &csi.CreateVolumeRequest{
Name: "random-vol-name",
CapacityRange: stdCapRange,
VolumeCapabilities: stdVolCap,
Parameters: map[string]string{
INodeSizeKey: "256",
},
}
testSuccessWithParameter(t, INodeSizeKey, "256", stdCapRange, stdVolCap, stdVolSize)
},
},
{
name: "success with bytes-per-inode",
testFunc: func(t *testing.T) {
testSuccessWithParameter(t, BytesPerINodeKey, "8192", stdCapRange, stdVolCap, stdVolSize)
},
},
{
name: "success with number-of-inodes",
testFunc: func(t *testing.T) {
testSuccessWithParameter(t, NumberOfINodesKey, "13107200", stdCapRange, stdVolCap, stdVolSize)
},
},
}

ctx := context.Background()
for _, tc := range testCases {
t.Run(tc.name, tc.testFunc)
}
}

mockDisk := &cloud.Disk{
VolumeID: req.Name,
AvailabilityZone: expZone,
CapacityGiB: util.BytesToGiB(stdVolSize),
}
func testSuccessWithParameter(t *testing.T, key, value string, capRange *csi.CapacityRange, volCap []*csi.VolumeCapability, volSize int64) {
req := &csi.CreateVolumeRequest{
Name: "random-vol-name",
CapacityRange: capRange,
VolumeCapabilities: volCap,
Parameters: map[string]string{key: value},
}

mockCtl := gomock.NewController(t)
defer mockCtl.Finish()
ctx := context.Background()

mockCloud := cloud.NewMockCloud(mockCtl)
mockCloud.EXPECT().CreateDisk(gomock.Eq(ctx), gomock.Eq(req.Name), gomock.Any()).Return(mockDisk, nil)
mockDisk := &cloud.Disk{
VolumeID: req.Name,
AvailabilityZone: expZone,
CapacityGiB: util.BytesToGiB(volSize),
}

awsDriver := controllerService{
cloud: mockCloud,
inFlight: internal.NewInFlight(),
driverOptions: &DriverOptions{},
}
mockCtl := gomock.NewController(t)
defer mockCtl.Finish()

response, err := awsDriver.CreateVolume(ctx, req)
if err != nil {
srvErr, ok := status.FromError(err)
if !ok {
t.Fatalf("Could not get error status code from error: %v", srvErr)
}
t.Fatalf("Unexpected error: %v", srvErr.Code())
}
mockCloud := cloud.NewMockCloud(mockCtl)
mockCloud.EXPECT().CreateDisk(gomock.Eq(ctx), gomock.Eq(req.Name), gomock.Any()).Return(mockDisk, nil)

context := response.Volume.VolumeContext
if inodeSize, ok := context[INodeSizeKey]; ok {
if inodeSize != "256" {
t.Fatalf("Invalid %s in VolumeContext (got %s expected 256)", INodeSizeKey, inodeSize)
}
} else {
t.Fatalf("Missing key %s in VolumeContext", INodeSizeKey)
}
},
},
awsDriver := controllerService{
cloud: mockCloud,
inFlight: internal.NewInFlight(),
driverOptions: &DriverOptions{},
}

for _, tc := range testCases {
t.Run(tc.name, tc.testFunc)
response, err := awsDriver.CreateVolume(ctx, req)
if err != nil {
srvErr, ok := status.FromError(err)
if !ok {
t.Fatalf("Could not get error status code from error: %v", srvErr)
}
t.Fatalf("Unexpected error: %v", srvErr.Code())
}

volCtx := response.Volume.VolumeContext
if sizeValue, ok := volCtx[key]; ok {
if sizeValue != value {
t.Fatalf("Invalid %s in VolumeContext (got %s expected %s)", key, sizeValue, value)
}
} else {
t.Fatalf("Missing key %s in VolumeContext", key)
}
}

Expand Down
Loading

0 comments on commit 3c7e41b

Please sign in to comment.