@@ -121,6 +121,13 @@ func (ns *NodeServer) NodeGetCapabilities(
121121 },
122122 },
123123 },
124+ {
125+ Type : & csi.NodeServiceCapability_Rpc {
126+ Rpc : & csi.NodeServiceCapability_RPC {
127+ Type : csi .NodeServiceCapability_RPC_EXPAND_VOLUME ,
128+ },
129+ },
130+ },
124131 },
125132 }, nil
126133}
@@ -337,6 +344,60 @@ func (ns *NodeServer) NodeUnstageVolume(
337344 return & csi.NodeUnstageVolumeResponse {}, nil
338345}
339346
347+ // NodeExpandVolume resizes nvmeof volumes (namespace).
348+ func (ns * NodeServer ) NodeExpandVolume (
349+ ctx context.Context ,
350+ req * csi.NodeExpandVolumeRequest ,
351+ ) (* csi.NodeExpandVolumeResponse , error ) {
352+ volumeID := req .GetVolumeId ()
353+ if volumeID == "" {
354+ return nil , status .Error (codes .InvalidArgument , "volume ID must be provided" )
355+ }
356+
357+ // Block mode - nothing to do
358+ if req .GetVolumeCapability () != nil && req .GetVolumeCapability ().GetBlock () != nil {
359+ log .DebugLog (ctx , "nvmeof: block mode volume, no filesystem resize needed for %s" , volumeID )
360+
361+ return & csi.NodeExpandVolumeResponse {}, nil
362+ }
363+
364+ // Get staging path
365+ volumePath := req .GetStagingTargetPath ()
366+ if volumePath == "" {
367+ return nil , status .Error (codes .InvalidArgument , "volume path must be provided" )
368+ }
369+
370+ if acquired := ns .volumeLocks .TryAcquire (volumeID ); ! acquired {
371+ log .ErrorLog (ctx , util .VolumeOperationAlreadyExistsFmt , volumeID )
372+
373+ return nil , status .Errorf (codes .Aborted , util .VolumeOperationAlreadyExistsFmt , volumeID )
374+ }
375+ defer ns .volumeLocks .Release (volumeID )
376+
377+ mountPath := volumePath + "/" + volumeID
378+
379+ // Find device from mount (no metadata needed!)
380+ devicePath , err := ns .getDeviceFromMount (ctx , mountPath )
381+ if err != nil {
382+ log .ErrorLog (ctx , "failed to find device for mount %s: %v" , mountPath , err )
383+
384+ return nil , status .Errorf (codes .Internal , "failed to find device: %v" , err )
385+ }
386+
387+ log .DebugLog (ctx , "nvmeof: resizing filesystem on device %s at mount path %s" , devicePath , mountPath )
388+
389+ resizer := mount .NewResizeFs (utilexec .New ())
390+ var ok bool
391+ ok , err = resizer .Resize (devicePath , mountPath )
392+ if ! ok {
393+ return nil , status .Errorf (codes .Internal ,
394+ "nvmeof: resize failed on path %s, error: %v" , req .GetVolumePath (), err )
395+ }
396+ log .DebugLog (ctx , "nvmeof: successfully resized filesystem for volume %s" , volumeID )
397+
398+ return & csi.NodeExpandVolumeResponse {}, nil
399+ }
400+
340401func (ns * NodeServer ) mountVolume (ctx context.Context , stagingPath string , req * csi.NodePublishVolumeRequest ) error {
341402 // Publish Path
342403 fsType := req .GetVolumeCapability ().GetMount ().GetFsType ()
@@ -653,3 +714,21 @@ func getStagingTargetPath(req interface{}) string {
653714
654715 return ""
655716}
717+
718+ // getDeviceFromMount finds the device path for a given mount path.
719+ func (ns * NodeServer ) getDeviceFromMount (ctx context.Context , mountPath string ) (string , error ) {
720+ mountPoints , err := ns .Mounter .List ()
721+ if err != nil {
722+ return "" , fmt .Errorf ("failed to list mounts: %w" , err )
723+ }
724+
725+ for _ , mp := range mountPoints {
726+ if mp .Path == mountPath {
727+ log .DebugLog (ctx , "found device %s for mount path %s" , mp .Device , mountPath )
728+
729+ return mp .Device , nil
730+ }
731+ }
732+
733+ return "" , fmt .Errorf ("no mount found for path %s" , mountPath )
734+ }
0 commit comments