Skip to content

Commit b53e1f5

Browse files
authored
Bolster nodeExpandVolume for iSCSI for fast repeated resizes
1 parent 42d54ab commit b53e1f5

12 files changed

Lines changed: 484 additions & 165 deletions

frontend/csi/node_server.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,11 @@ func (p *Plugin) nodeExpandVolume(
598598
return status.Errorf(codes.FailedPrecondition, "unable to read protocol info from publish info; %s", err)
599599
}
600600

601-
var fsType, mountOptions string
601+
var (
602+
fsType, mountOptions string
603+
preExpandFilesystemSize int64
604+
preExpandDeviceSizeBytes int64
605+
)
602606

603607
switch protocol {
604608
case tridentconfig.File:
@@ -608,6 +612,25 @@ func (p *Plugin) nodeExpandVolume(
608612
if fsType, err = filesystem.VerifyFilesystemSupport(publishInfo.FilesystemType); err != nil {
609613
break
610614
}
615+
616+
// Capture pre-expand filesystem size via statfs on the kubelet-staged mount.
617+
if fsType != filesystem.Raw {
618+
preExpandFilesystemSize, err = p.fs.GetFilesystemSize(ctx, stagingTargetPath)
619+
if err != nil {
620+
return status.Error(codes.Internal, err.Error())
621+
}
622+
}
623+
624+
// Capture pre-expand block device size before any rescan/resize work.
625+
if publishInfo.DevicePath != "" {
626+
preExpandDeviceSizeBytes, err = p.devices.GetDiskSize(ctx, publishInfo.DevicePath)
627+
if err != nil {
628+
Logc(ctx).WithFields(LogFields{
629+
"devicePath": publishInfo.DevicePath,
630+
}).WithError(err).Warn("Failed to read pre-expand device size; skipping device growth check.")
631+
}
632+
}
633+
611634
// We don't need to rescan mount devices for NVMe protocol backend. Automatic namespace rescanning happens
612635
// everytime the NVMe controller is reset, or if the controller posts an asynchronous event indicating
613636
// namespace attributes have changed.
@@ -663,19 +686,43 @@ func (p *Plugin) nodeExpandVolume(
663686
}
664687
}
665688

689+
// Read the device size after rescan/resize but before filesystem expand.
690+
var postExpandDeviceSizeBytes int64
691+
if publishInfo.DevicePath != "" {
692+
postExpandDeviceSizeBytes, err = p.devices.GetDiskSize(ctx, publishInfo.DevicePath)
693+
if err != nil {
694+
Logc(ctx).WithFields(LogFields{
695+
"devicePath": publishInfo.DevicePath,
696+
}).WithError(err).Warn("Failed to read post-expand device size; skipping device growth check.")
697+
}
698+
}
699+
devicesGrew := preExpandDeviceSizeBytes > 0 && postExpandDeviceSizeBytes > preExpandDeviceSizeBytes
700+
666701
// Expand filesystem.
667702
if fsType != filesystem.Raw {
668-
filesystemSize, err := p.fs.ExpandFilesystemOnNode(ctx, publishInfo, devicePath, stagingTargetPath, fsType,
669-
mountOptions)
703+
newFilesystemSize, err := p.fs.ExpandFilesystemOnNode(ctx, publishInfo, devicePath, stagingTargetPath, fsType,
704+
mountOptions, requiredBytes)
670705
if err != nil {
671706
Logc(ctx).WithFields(LogFields{
672707
"device": publishInfo.DevicePath,
673708
"filesystemType": fsType,
674709
}).WithError(err).Error("Unable to expand filesystem.")
675710
return status.Error(codes.Internal, err.Error())
676711
}
712+
713+
if devicesGrew && newFilesystemSize <= preExpandFilesystemSize {
714+
Logc(ctx).WithFields(LogFields{
715+
"preExpandFilesystemSize": preExpandFilesystemSize,
716+
"newFilesystemSize": newFilesystemSize,
717+
"preExpandDeviceSizeBytes": preExpandDeviceSizeBytes,
718+
"postExpandDeviceSizeBytes": postExpandDeviceSizeBytes,
719+
"requiredBytes": requiredBytes,
720+
}).Error("Filesystem did not grow despite block device growing during expand.")
721+
return status.Error(codes.Internal, "filesystem size did not grow")
722+
}
723+
677724
Logc(ctx).WithFields(LogFields{
678-
"filesystemSize": filesystemSize,
725+
"filesystemSize": newFilesystemSize,
679726
"requiredBytes": requiredBytes,
680727
}).Debug("Filesystem size after expand.")
681728
}

frontend/csi/node_server_test.go

Lines changed: 141 additions & 13 deletions
Large diffs are not rendered by default.

mocks/mock_utils/mock_filesystem/mock_filesystem_client.go

Lines changed: 19 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

utils/filesystem/filesystem.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ type Filesystem interface {
6060
RepairVolume(ctx context.Context, device, fstype string)
6161
ExpandFilesystemOnNode(
6262
ctx context.Context, publishInfo *models.VolumePublishInfo, devicePath, stagedTargetPath, fsType, mountOptions string,
63+
targetSizeBytes int64,
6364
) (int64, error)
6465
DeleteFile(ctx context.Context, filepath, fileDescription string) (string, error)
6566
GetFilesystemStats(
6667
ctx context.Context, path string,
6768
) (available, capacity, usage, inodes, inodesFree, inodesUsed int64, err error)
69+
GetFilesystemSize(ctx context.Context, path string) (int64, error)
6870
GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error)
6971
ScanFile(filename string) ([]byte, error)
7072
ScanDir(path string) ([]os.FileInfo, error)
@@ -93,6 +95,12 @@ func NewDetailed(command tridentexec.Command, osFs afero.Fs, mount Mount) *FSCli
9395
}
9496
}
9597

98+
// GetFilesystemSize returns the total capacity in bytes of the filesystem at the given path.
99+
func (f *FSClient) GetFilesystemSize(ctx context.Context, path string) (int64, error) {
100+
_, capacity, _, _, _, _, err := f.GetFilesystemStats(ctx, path)
101+
return capacity, err
102+
}
103+
96104
// GetDFOutput returns parsed DF output
97105
func (f *FSClient) GetDFOutput(ctx context.Context) ([]models.DFInfo, error) {
98106
GenerateRequestContextForLayer(ctx, LogLayerUtils)
@@ -254,9 +262,10 @@ func (f *FSClient) RepairVolume(ctx context.Context, device, fstype string) {
254262
}
255263
}
256264

257-
// ExpandFilesystemOnNode will expand the filesystem of an already expanded volume.
265+
// ExpandFilesystemOnNode expands the filesystem after the volume capacity step.
258266
func (f *FSClient) ExpandFilesystemOnNode(
259267
ctx context.Context, publishInfo *models.VolumePublishInfo, devicePath, stagedTargetPath, fsType, mountOptions string,
268+
targetSizeBytes int64,
260269
) (int64, error) {
261270
GenerateRequestContextForLayer(ctx, LogLayerUtils)
262271

@@ -269,6 +278,7 @@ func (f *FSClient) ExpandFilesystemOnNode(
269278
"mountOptions": mountOptions,
270279
"filesystemType": fsType,
271280
"stagingMountpoint": expansionMountPoint,
281+
"targetSizeBytes": targetSizeBytes,
272282
}
273283
Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.ExpandFilesystemOnNode")
274284
defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.ExpandFilesystemOnNode")
@@ -289,9 +299,17 @@ func (f *FSClient) ExpandFilesystemOnNode(
289299
var size int64
290300
switch fsType {
291301
case "xfs":
292-
size, err = f.expandFilesystem(ctx, "xfs_growfs", expansionMountPoint, expansionMountPoint)
302+
size, err = expandFilesystem(
303+
ctx, f.command,
304+
"xfs_growfs", expansionMountPoint, expansionMountPoint,
305+
f.GetFilesystemSize,
306+
)
293307
case "ext3", "ext4":
294-
size, err = f.expandFilesystem(ctx, "resize2fs", devicePath, expansionMountPoint)
308+
size, err = expandFilesystem(
309+
ctx, f.command,
310+
"resize2fs", devicePath, expansionMountPoint,
311+
f.GetFilesystemSize,
312+
)
295313
default:
296314
err = fmt.Errorf("unsupported file system type: %s", fsType)
297315
}
@@ -301,34 +319,31 @@ func (f *FSClient) ExpandFilesystemOnNode(
301319
return size, err
302320
}
303321

304-
func (f *FSClient) expandFilesystem(ctx context.Context, cmd, cmdArguments, tmpMountPoint string) (int64, error) {
322+
// expandFilesystem runs the resize tool and returns the post-expand filesystem size.
323+
func expandFilesystem(
324+
ctx context.Context, cmd tridentexec.Command,
325+
cmdName, cmdArguments, tmpMountPoint string,
326+
getFilesystemSize func(context.Context, string) (int64, error),
327+
) (int64, error) {
305328
logFields := LogFields{
306-
"cmd": cmd,
329+
"cmd": cmdName,
307330
"cmdArguments": cmdArguments,
308331
"tmpMountPoint": tmpMountPoint,
309332
}
310333
Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.expandFilesystem")
311334
defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.expandFilesystem")
312335

313-
preExpandSize, err := f.getFilesystemSize(ctx, tmpMountPoint)
314-
if err != nil {
315-
return 0, err
316-
}
317-
_, err = f.command.Execute(ctx, cmd, cmdArguments)
336+
_, err := cmd.Execute(ctx, cmdName, cmdArguments)
318337
if err != nil {
319338
Logc(ctx).Errorf("Expanding filesystem failed; %s", err)
320339
return 0, err
321340
}
322341

323-
postExpandSize, err := f.getFilesystemSize(ctx, tmpMountPoint)
342+
postExpandSize, err := getFilesystemSize(ctx, tmpMountPoint)
324343
if err != nil {
325344
return 0, err
326345
}
327346

328-
if postExpandSize == preExpandSize {
329-
Logc(ctx).Warnf("Failed to expand filesystem; size=%d", postExpandSize)
330-
}
331-
332347
return postExpandSize, nil
333348
}
334349

utils/filesystem/filesystem_darwin.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ func (f *FSClient) GetFilesystemStats(ctx context.Context, _ string) (int64, int
1919
return 0, 0, 0, 0, 0, 0, errors.UnsupportedError("GetFilesystemStats is not supported for darwin")
2020
}
2121

22-
// getFilesystemSize unused stub function
23-
func (f *FSClient) getFilesystemSize(ctx context.Context, _ string) (int64, error) {
24-
Logc(ctx).Debug(">>>> filesystem_darwin.getFilesystemSize")
25-
defer Logc(ctx).Debug("<<<< filesystem_darwin.getFilesystemSize")
26-
return 0, errors.UnsupportedError("getFilesystemSize is not supported for darwin")
22+
// getBlockDeviceSize unused stub function.
23+
func (f *FSClient) getBlockDeviceSize(ctx context.Context, _ string) (int64, error) {
24+
Logc(ctx).Debug(">>>> filesystem_darwin.getBlockDeviceSize")
25+
defer Logc(ctx).Debug("<<<< filesystem_darwin.getBlockDeviceSize")
26+
return 0, errors.UnsupportedError("getBlockDeviceSize is not supported for darwin")
2727
}
2828

2929
// GetDeviceFilePath returns the staging path for volume.

utils/filesystem/filesystem_darwin_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@ import (
1212
"github.com/netapp/trident/utils/models"
1313
)
1414

15-
func TestGetFilesystemSize(t *testing.T) {
16-
ctx := context.Background()
17-
fsClient := New(nil)
18-
19-
result, err := fsClient.getFilesystemSize(ctx, "")
20-
assert.Equal(t, result, int64(0), "got non-zero filesystem size")
21-
assert.Error(t, err, "no error")
22-
assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError")
23-
}
24-
2515
func TestGetFilesystemStats(t *testing.T) {
2616
ctx := context.Background()
2717
fsClient := New(nil)

utils/filesystem/filesystem_linux.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ package filesystem
77
import (
88
"context"
99
"fmt"
10+
"os"
11+
"syscall"
1012
"time"
13+
"unsafe"
14+
15+
"golang.org/x/sys/unix"
1116

1217
"github.com/netapp/trident/internal/syswrap"
1318
. "github.com/netapp/trident/logging"
@@ -53,15 +58,21 @@ func (f *FSClient) GetFilesystemStats(
5358
return available, capacity, usage, inodes, inodesFree, inodesUsed, nil
5459
}
5560

56-
// getFilesystemSize returns the size of the filesystem for the given path.
57-
// The caller of the func is responsible for verifying the mountPoint existence and readiness.
58-
func (f *FSClient) getFilesystemSize(ctx context.Context, path string) (int64, error) {
59-
Logc(ctx).Debug(">>>> filesystem_linux.getFilesystemSize")
60-
defer Logc(ctx).Debug("<<<< filesystem_linux.getFilesystemSize")
61+
// getBlockDeviceSize reports devicePath capacity via BLKGETSIZE64 (kernel-visible; may trail provisioned size slightly).
62+
func (f *FSClient) getBlockDeviceSize(ctx context.Context, devicePath string) (int64, error) {
63+
Logc(ctx).WithField("devicePath", devicePath).Debug(">>>> filesystem_linux.getBlockDeviceSize")
64+
defer Logc(ctx).WithField("devicePath", devicePath).Debug("<<<< filesystem_linux.getBlockDeviceSize")
6165

62-
_, size, _, _, _, _, err := f.GetFilesystemStats(ctx, path)
66+
disk, err := os.Open(devicePath)
6367
if err != nil {
64-
return 0, err
68+
return 0, fmt.Errorf("failed to open block device %s: %w", devicePath, err)
69+
}
70+
defer disk.Close()
71+
72+
var size int64
73+
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, disk.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
74+
if errno != 0 {
75+
return 0, fmt.Errorf("BLKGETSIZE64 ioctl failed for %s: %w", devicePath, os.NewSyscallError("ioctl", errno))
6576
}
6677

6778
return size, nil

0 commit comments

Comments
 (0)