Skip to content

Commit 24a1494

Browse files
author
Power Cloud Robot
authored
Merge pull request #110 from dharaneeshvrd/NodeGetVolumeStats
GET_VOLUME_STATS node capability added
2 parents 74c35cb + 4ba7312 commit 24a1494

File tree

4 files changed

+346
-1
lines changed

4 files changed

+346
-1
lines changed

pkg/driver/mocks/mock_stats.go

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

pkg/driver/node.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var (
5656
nodeCaps = []csi.NodeServiceCapability_RPC_Type{
5757
csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
5858
csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
59+
csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
5960
}
6061
)
6162

@@ -66,6 +67,7 @@ type nodeService struct {
6667
driverOptions *Options
6768
pvmInstanceId string
6869
volumeLocks *util.VolumeLocks
70+
stats StatsUtils
6971
}
7072

7173
// newNodeService creates a new node service
@@ -88,6 +90,7 @@ func newNodeService(driverOptions *Options) nodeService {
8890
driverOptions: driverOptions,
8991
pvmInstanceId: metadata.GetPvmInstanceId(),
9092
volumeLocks: util.NewVolumeLocks(),
93+
stats: &VolumeStatUtils{},
9194
}
9295
}
9396

@@ -380,7 +383,72 @@ func (d *nodeService) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu
380383
}
381384

382385
func (d *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
383-
return nil, status.Error(codes.Unimplemented, "NodeGetVolumeStats is not implemented yet")
386+
klog.V(4).Infof("NodeGetVolumeStats: called with args %+v", *req)
387+
var resp *csi.NodeGetVolumeStatsResponse
388+
389+
if req == nil || req.VolumeId == "" {
390+
return nil, status.Error(codes.InvalidArgument, "Volume ID not provided")
391+
}
392+
393+
if req.VolumePath == "" {
394+
return nil, status.Error(codes.InvalidArgument, "VolumePath not provided")
395+
}
396+
397+
volumePath := req.VolumePath
398+
// return if path does not exist
399+
if d.stats.IsPathNotExist(volumePath) {
400+
return nil, status.Error(codes.NotFound, "VolumePath not exist")
401+
}
402+
403+
// check if volume mode is raw volume mode
404+
isBlock, err := d.stats.IsBlockDevice(volumePath)
405+
if err != nil {
406+
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to check volume %s is block device or not: %v", req.VolumeId, err))
407+
}
408+
// if block device, get deviceStats
409+
if isBlock {
410+
capacity, err := d.stats.DeviceInfo(volumePath)
411+
if err != nil {
412+
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to collect block device info: %v", err))
413+
}
414+
415+
resp = &csi.NodeGetVolumeStatsResponse{
416+
Usage: []*csi.VolumeUsage{
417+
{
418+
Total: capacity,
419+
Unit: csi.VolumeUsage_BYTES,
420+
},
421+
},
422+
}
423+
424+
klog.V(4).Infof("Block Device Volume stats collected: %+v\n", resp)
425+
return resp, nil
426+
}
427+
428+
// else get the file system stats
429+
available, capacity, usage, inodes, inodesFree, inodesUsed, err := d.stats.FSInfo(volumePath)
430+
if err != nil {
431+
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to collect FSInfo: %v", err))
432+
}
433+
resp = &csi.NodeGetVolumeStatsResponse{
434+
Usage: []*csi.VolumeUsage{
435+
{
436+
Available: available,
437+
Total: capacity,
438+
Used: usage,
439+
Unit: csi.VolumeUsage_BYTES,
440+
},
441+
{
442+
Available: inodesFree,
443+
Total: inodes,
444+
Used: inodesUsed,
445+
Unit: csi.VolumeUsage_INODES,
446+
},
447+
},
448+
}
449+
450+
klog.V(4).Infof("FS Volume stats collected: %+v\n", resp)
451+
return resp, nil
384452
}
385453

386454
func (d *nodeService) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) {

pkg/driver/node_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package driver
1616
import (
1717
"context"
1818
"errors"
19+
"fmt"
1920
"reflect"
2021
"testing"
2122

@@ -882,6 +883,117 @@ func TestNodeGetInfo(t *testing.T) {
882883
}
883884
}
884885

886+
func TestNodeGetVolumeStats(t *testing.T) {
887+
testCases := []struct {
888+
name string
889+
testFunc func(t *testing.T)
890+
}{
891+
{name: "success block device volume",
892+
testFunc: func(t *testing.T) {
893+
mockCtl := gomock.NewController(t)
894+
defer mockCtl.Finish()
895+
mockStatUtils := mocks.NewMockStatsUtils(mockCtl)
896+
897+
volumePath := "./test"
898+
var mockCapacity int64 = 100
899+
mockStatUtils.EXPECT().IsPathNotExist(volumePath).Return(false)
900+
mockStatUtils.EXPECT().IsBlockDevice(volumePath).Return(true, nil)
901+
mockStatUtils.EXPECT().DeviceInfo(volumePath).Return(mockCapacity, nil)
902+
driver := &nodeService{stats: mockStatUtils}
903+
904+
req := csi.NodeGetVolumeStatsRequest{VolumeId: volumeID, VolumePath: volumePath}
905+
906+
resp, err := driver.NodeGetVolumeStats(context.TODO(), &req)
907+
if err != nil {
908+
t.Fatalf("Expect no error but got: %v", err)
909+
}
910+
911+
if resp.Usage[0].Total != mockCapacity {
912+
t.Fatalf("Expected total capacity as %d, got %d", mockCapacity, resp.Usage[0].Total)
913+
}
914+
},
915+
}, {
916+
name: "failure path not exist",
917+
testFunc: func(t *testing.T) {
918+
mockCtl := gomock.NewController(t)
919+
defer mockCtl.Finish()
920+
mockStatUtils := mocks.NewMockStatsUtils(mockCtl)
921+
922+
volumePath := "./test"
923+
mockStatUtils.EXPECT().IsPathNotExist(volumePath).Return(true)
924+
driver := &nodeService{stats: mockStatUtils}
925+
926+
req := csi.NodeGetVolumeStatsRequest{VolumeId: volumeID, VolumePath: volumePath}
927+
928+
_, err := driver.NodeGetVolumeStats(context.TODO(), &req)
929+
expectErr(t, err, codes.NotFound)
930+
},
931+
}, {
932+
name: "failure checking for block device",
933+
testFunc: func(t *testing.T) {
934+
mockCtl := gomock.NewController(t)
935+
defer mockCtl.Finish()
936+
mockStatUtils := mocks.NewMockStatsUtils(mockCtl)
937+
938+
volumePath := "./test"
939+
mockStatUtils.EXPECT().IsPathNotExist(volumePath).Return(false)
940+
mockStatUtils.EXPECT().IsBlockDevice(volumePath).Return(false, errors.New("Error checking for block device"))
941+
driver := &nodeService{stats: mockStatUtils}
942+
943+
req := csi.NodeGetVolumeStatsRequest{VolumeId: volumeID, VolumePath: volumePath}
944+
945+
_, err := driver.NodeGetVolumeStats(context.TODO(), &req)
946+
expectErr(t, err, codes.Internal)
947+
},
948+
}, {
949+
name: "failure collecting block device info",
950+
testFunc: func(t *testing.T) {
951+
mockCtl := gomock.NewController(t)
952+
defer mockCtl.Finish()
953+
mockStatUtils := mocks.NewMockStatsUtils(mockCtl)
954+
955+
volumePath := "./test"
956+
mockStatUtils.EXPECT().IsPathNotExist(volumePath).Return(false)
957+
mockStatUtils.EXPECT().IsBlockDevice(volumePath).Return(true, nil)
958+
mockStatUtils.EXPECT().DeviceInfo(volumePath).Return(int64(0), errors.New("Error collecting block device info"))
959+
960+
driver := &nodeService{stats: mockStatUtils}
961+
962+
req := csi.NodeGetVolumeStatsRequest{VolumeId: volumeID, VolumePath: volumePath}
963+
964+
_, err := driver.NodeGetVolumeStats(context.TODO(), &req)
965+
fmt.Println(err)
966+
expectErr(t, err, codes.Internal)
967+
},
968+
},
969+
{
970+
name: "failure collecting fs info",
971+
testFunc: func(t *testing.T) {
972+
mockCtl := gomock.NewController(t)
973+
defer mockCtl.Finish()
974+
mockStatUtils := mocks.NewMockStatsUtils(mockCtl)
975+
976+
volumePath := "./test"
977+
mockStatUtils.EXPECT().IsPathNotExist(volumePath).Return(false)
978+
mockStatUtils.EXPECT().IsBlockDevice(volumePath).Return(false, nil)
979+
var statUnit int64 = 0
980+
mockStatUtils.EXPECT().FSInfo(volumePath).Return(statUnit, statUnit, statUnit, statUnit, statUnit, statUnit, errors.New("Error collecting FS Info"))
981+
driver := &nodeService{stats: mockStatUtils}
982+
983+
req := csi.NodeGetVolumeStatsRequest{VolumeId: volumeID, VolumePath: volumePath}
984+
985+
_, err := driver.NodeGetVolumeStats(context.TODO(), &req)
986+
fmt.Println(err)
987+
988+
expectErr(t, err, codes.Internal)
989+
},
990+
},
991+
}
992+
for _, tc := range testCases {
993+
t.Run(tc.name, tc.testFunc)
994+
}
995+
}
996+
885997
func expectErr(t *testing.T, actualErr error, expectedCode codes.Code) {
886998
if actualErr == nil {
887999
t.Fatalf("Expect error but got no error")

pkg/driver/stats.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package driver
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"strconv"
8+
"strings"
9+
10+
"golang.org/x/sys/unix"
11+
"k8s.io/kubernetes/pkg/volume/util/fs"
12+
)
13+
14+
// StatsUtils ...
15+
type StatsUtils interface {
16+
FSInfo(path string) (int64, int64, int64, int64, int64, int64, error)
17+
IsBlockDevice(devicePath string) (bool, error)
18+
DeviceInfo(devicePath string) (int64, error)
19+
IsPathNotExist(path string) bool
20+
}
21+
22+
// VolumeStatUtils ...
23+
type VolumeStatUtils struct {
24+
}
25+
26+
// IsDevicePathNotExist ...
27+
func (su *VolumeStatUtils) IsPathNotExist(path string) bool {
28+
var stat unix.Stat_t
29+
err := unix.Stat(path, &stat)
30+
if err != nil {
31+
if os.IsNotExist(err) {
32+
return true
33+
}
34+
}
35+
return false
36+
}
37+
38+
// IsBlockDevice ...
39+
func (su *VolumeStatUtils) IsBlockDevice(devicePath string) (bool, error) {
40+
var stat unix.Stat_t
41+
err := unix.Stat(devicePath, &stat)
42+
if err != nil {
43+
return false, err
44+
}
45+
46+
return (stat.Mode & unix.S_IFMT) == unix.S_IFBLK, nil
47+
}
48+
49+
// DeviceInfo ...
50+
func (su *VolumeStatUtils) DeviceInfo(devicePath string) (int64, error) {
51+
output, err := exec.Command("blockdev", "--getsize64", devicePath).CombinedOutput()
52+
if err != nil {
53+
return 0, fmt.Errorf("failed to get size of block volume at path %s: output: %s, err: %v", devicePath, string(output), err)
54+
}
55+
strOut := strings.TrimSpace(string(output))
56+
gotSizeBytes, err := strconv.ParseInt(strOut, 10, 64)
57+
if err != nil {
58+
return 0, fmt.Errorf("failed to parse size '%s' into int", strOut)
59+
}
60+
61+
return gotSizeBytes, nil
62+
}
63+
64+
//FSInfo ...
65+
func (su *VolumeStatUtils) FSInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
66+
return fs.Info(path)
67+
}

0 commit comments

Comments
 (0)