diff --git a/go.mod b/go.mod index 9bc090f..c4a62b6 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver v0.6.0 github.com/nifcloud/nifcloud-sdk-go v1.21.0 github.com/stretchr/testify v1.8.1 + golang.org/x/sys v0.4.0 google.golang.org/grpc v1.27.0 k8s.io/apimachinery v0.19.2 k8s.io/klog v1.0.0 @@ -23,6 +24,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.17.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v0.2.0 // indirect + github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect @@ -31,12 +33,12 @@ require ( go.opencensus.io v0.22.2 // indirect golang.org/x/net v0.1.0 // indirect golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect - golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect google.golang.org/api v0.15.1 // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.2.0 // indirect ) diff --git a/go.sum b/go.sum index 269d27b..2b96068 100644 --- a/go.sum +++ b/go.sum @@ -243,6 +243,7 @@ github.com/godbus/dbus v0.0.0-20181101234600-2ff6f7ffd60f/go.mod h1:/YcGZj5zSblf github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -306,6 +307,7 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -874,6 +876,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 398b2e7..da277a2 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -7,14 +7,17 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" "github.com/aokumasan/nifcloud-additional-storage-csi-driver/pkg/cloud" csi "github.com/container-storage-interface/spec/lib/go/csi" awsdriver "github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver" gcpcommon "github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/pkg/common" + "golang.org/x/sys/unix" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/klog" "k8s.io/kubernetes/pkg/util/resizefs" "k8s.io/utils/exec" @@ -323,7 +326,64 @@ func (n *nodeService) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu } func (n *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { - return nil, status.Error(codes.Unimplemented, "NodeGetVolumeStats is not implemented yet") + klog.V(4).Infof("NodeGetVolumeStats: called with args %+v", *req) + if len(req.VolumeId) == 0 { + return nil, status.Error(codes.InvalidArgument, "NodeGetVolumeStats volume ID was empty") + } + if len(req.VolumePath) == 0 { + return nil, status.Error(codes.InvalidArgument, "NodeGetVolumeStats volume path was empty") + } + + exists, err := n.mounter.ExistsPath(req.VolumePath) + if err != nil { + return nil, status.Errorf(codes.Internal, "unknown error when stat on %s: %v", req.VolumePath, err) + } + if !exists { + return nil, status.Errorf(codes.NotFound, "path %s does not exist", req.VolumePath) + } + + isBlock, err := isBlockDevice(req.VolumePath) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to determine whether %s is block device: %v", req.VolumePath, err) + } + + if isBlock { + bcap, err := n.getBlockSizeBytes(req.VolumePath) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get block capacity on path %s: %v", req.VolumePath, err) + } + + return &csi.NodeGetVolumeStatsResponse{ + Usage: []*csi.VolumeUsage{ + { + Unit: csi.VolumeUsage_BYTES, + Total: bcap, + }, + }, + }, nil + } + + available, capacity, used, inodes, inodesFree, inodesUsed, err := getFsInfo(req.VolumePath) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get FsInfo due to error: %v", err) + } + + return &csi.NodeGetVolumeStatsResponse{ + Usage: []*csi.VolumeUsage{ + { + Unit: csi.VolumeUsage_BYTES, + Available: resource.NewQuantity(available, resource.BinarySI).AsDec().UnscaledBig().Int64(), + Total: resource.NewQuantity(capacity, resource.BinarySI).AsDec().UnscaledBig().Int64(), + Used: resource.NewQuantity(used, resource.BinarySI).AsDec().UnscaledBig().Int64(), + }, + { + Unit: csi.VolumeUsage_INODES, + Available: resource.NewQuantity(inodesFree, resource.BinarySI).AsDec().UnscaledBig().Int64(), + Total: resource.NewQuantity(inodes, resource.BinarySI).AsDec().UnscaledBig().Int64(), + Used: resource.NewQuantity(inodesUsed, resource.BinarySI).AsDec().UnscaledBig().Int64(), + }, + }, + }, nil } func (n *nodeService) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { @@ -454,6 +514,22 @@ func (n *nodeService) nodePublishVolumeForFileSystem(req *csi.NodePublishVolumeR return nil } +func (n *nodeService) getBlockSizeBytes(devicePath string) (int64, error) { + cmd := n.mounter.(*NodeMounter).Exec.Command("blockdev", "--getsize64", devicePath) + output, err := cmd.Output() + if err != nil { + return -1, fmt.Errorf("error when getting size of block volume at path %s: output: %s, err: %v", devicePath, string(output), err) + } + + strOut := strings.TrimSpace(string(output)) + gotSizeBytes, err := strconv.ParseInt(strOut, 10, 64) + if err != nil { + return -1, fmt.Errorf("failed to parse size %s as int", strOut) + } + + return gotSizeBytes, nil +} + func (n *nodeService) findDevicePath(scsiID string) (string, error) { if !strings.HasPrefix(scsiID, "SCSI") { return "", fmt.Errorf("invalid SCSI ID %q was specified. SCSI ID must be start with SCSI (0:?)", scsiID) @@ -576,3 +652,36 @@ func (n *nodeService) rescanStorageDevice(dev string) error { return nil } + +func getFsInfo(path string) (int64, int64, int64, int64, int64, int64, error) { + statfs := &unix.Statfs_t{} + err := unix.Statfs(path, statfs) + if err != nil { + return 0, 0, 0, 0, 0, 0, err + } + + // Available is blocks available * fragment size + available := int64(statfs.Bavail) * int64(statfs.Bsize) + + // Capacity is total block count * fragment size + capacity := int64(statfs.Blocks) * int64(statfs.Bsize) + + // Usage is block being used * fragment size (aka block size). + usage := (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize) + + inodes := int64(statfs.Files) + inodesFree := int64(statfs.Ffree) + inodesUsed := inodes - inodesFree + + return available, capacity, usage, inodes, inodesFree, inodesUsed, nil +} + +func isBlockDevice(fullPath string) (bool, error) { + var st unix.Stat_t + err := unix.Stat(fullPath, &st) + if err != nil { + return false, err + } + + return (st.Mode & unix.S_IFMT) == unix.S_IFBLK, nil +}