Skip to content

Commit d0f9045

Browse files
yogev-lblev-lb
authored andcommitted
node: implement NodeGetVolumeStats
for FS mount expose bytes and inodes. for block expose only bytes - see container-storage-interface/spec#371 thread for reason why we can't access the lightos API issue: LBM1-17861
1 parent fe0da73 commit d0f9045

File tree

2,187 files changed

+593386
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,187 files changed

+593386
-38
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c
1818
google.golang.org/grpc v1.38.0
1919
gopkg.in/yaml.v2 v2.4.0
20+
k8s.io/kubernetes v1.21.4
2021
k8s.io/mount-utils v0.21.4
2122
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9
2223
)

go.sum

+795-2
Large diffs are not rendered by default.

pkg/driver/node.go

+158-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ package driver
66

77
import (
88
"context"
9+
"fmt"
910
"io/ioutil"
1011
"os"
12+
"os/exec"
1113
"path/filepath"
14+
"strconv"
1215
"strings"
1316
"time"
1417

@@ -18,6 +21,7 @@ import (
1821
"github.com/sirupsen/logrus"
1922
"google.golang.org/grpc/codes"
2023
"google.golang.org/grpc/status"
24+
"k8s.io/kubernetes/pkg/volume"
2125
mountutils "k8s.io/mount-utils"
2226

2327
"github.com/lightbitslabs/los-csi/pkg/driver/backend"
@@ -600,6 +604,13 @@ func (d *Driver) NodeGetCapabilities(
600604
},
601605
},
602606
},
607+
{
608+
Type: &csi.NodeServiceCapability_Rpc{
609+
Rpc: &csi.NodeServiceCapability_RPC{
610+
Type: csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
611+
},
612+
},
613+
},
603614
}
604615

605616
return &csi.NodeGetCapabilitiesResponse{Capabilities: capabilities}, nil
@@ -616,7 +627,153 @@ func (d *Driver) NodeGetInfo(
616627
func (d *Driver) NodeGetVolumeStats(
617628
ctx context.Context, req *csi.NodeGetVolumeStatsRequest,
618629
) (*csi.NodeGetVolumeStatsResponse, error) {
619-
return nil, status.Error(codes.Unimplemented, "")
630+
vid, err := ParseCSIResourceID(req.VolumeId)
631+
if err != nil {
632+
return nil, mkEinval("volume_id", err.Error())
633+
}
634+
635+
log := d.log.WithFields(logrus.Fields{
636+
"op": "NodeGetVolumeStats",
637+
"mgmt-ep": vid.mgmtEPs,
638+
"vol-uuid": vid.uuid,
639+
"vol-path": req.VolumePath,
640+
"project": vid.projName,
641+
})
642+
643+
targetPath := req.GetVolumePath()
644+
log.Infof("called for volume %q in project %q, targetPath: %q", vid.uuid, vid.projName, targetPath)
645+
if targetPath == "" {
646+
err = fmt.Errorf("targetpath %v is empty", targetPath)
647+
648+
return nil, mkEinval("targetPath", err.Error())
649+
}
650+
651+
stat, err := os.Stat(targetPath)
652+
if err != nil {
653+
return nil, mkEinval("targetPath", fmt.Sprintf("failed to get stat for targetpath %q: %v", targetPath, err))
654+
}
655+
656+
if stat.Mode().IsDir() {
657+
return FilesystemNodeGetVolumeStats(ctx, log, targetPath)
658+
} else if (stat.Mode() & os.ModeDevice) == os.ModeDevice {
659+
return blockNodeGetVolumeStats(ctx, log, targetPath)
660+
}
661+
662+
return nil, mkEinval("targetPath", fmt.Sprintf("targetpath %q is not a block device", targetPath))
663+
}
664+
665+
// IsMountPoint checks if the given path is mountpoint or not.
666+
func IsMountPoint(p string) (bool, error) {
667+
dummyMount := mountutils.New("")
668+
notMnt, err := dummyMount.IsLikelyNotMountPoint(p)
669+
if err != nil {
670+
return false, err
671+
}
672+
673+
return !notMnt, nil
674+
}
675+
676+
// FilesystemNodeGetVolumeStats can be used for getting the metrics as
677+
// requested by the NodeGetVolumeStats CSI procedure.
678+
func FilesystemNodeGetVolumeStats(ctx context.Context, log *logrus.Entry, targetPath string) (*csi.NodeGetVolumeStatsResponse, error) {
679+
isMnt, err := IsMountPoint(targetPath)
680+
if err != nil {
681+
if os.IsNotExist(err) {
682+
return nil, status.Errorf(codes.InvalidArgument, "targetpath %s does not exist", targetPath)
683+
}
684+
685+
return nil, status.Error(codes.Internal, err.Error())
686+
}
687+
if !isMnt {
688+
return nil, status.Errorf(codes.InvalidArgument, "targetpath %s is not mounted", targetPath)
689+
}
690+
691+
metricsProvider := volume.NewMetricsStatFS(targetPath)
692+
volMetrics, volMetErr := metricsProvider.GetMetrics()
693+
if volMetErr != nil {
694+
return nil, status.Error(codes.Internal, volMetErr.Error())
695+
}
696+
697+
available, ok := (*(volMetrics.Available)).AsInt64()
698+
if !ok {
699+
log.Errorf("failed to fetch available bytes")
700+
}
701+
capacity, ok := (*(volMetrics.Capacity)).AsInt64()
702+
if !ok {
703+
log.Errorf("failed to fetch capacity bytes")
704+
705+
return nil, status.Error(codes.Unknown, "failed to fetch capacity bytes")
706+
}
707+
used, ok := (*(volMetrics.Used)).AsInt64()
708+
if !ok {
709+
log.Errorf("failed to fetch used bytes")
710+
}
711+
inodes, ok := (*(volMetrics.Inodes)).AsInt64()
712+
if !ok {
713+
log.Errorf("failed to fetch available inodes")
714+
715+
return nil, status.Error(codes.Unknown, "failed to fetch available inodes")
716+
}
717+
inodesFree, ok := (*(volMetrics.InodesFree)).AsInt64()
718+
if !ok {
719+
log.Errorf("failed to fetch free inodes")
720+
}
721+
722+
inodesUsed, ok := (*(volMetrics.InodesUsed)).AsInt64()
723+
if !ok {
724+
log.Errorf("failed to fetch used inodes")
725+
}
726+
727+
return &csi.NodeGetVolumeStatsResponse{
728+
Usage: []*csi.VolumeUsage{
729+
{
730+
Available: available,
731+
Total: capacity,
732+
Used: used,
733+
Unit: csi.VolumeUsage_BYTES,
734+
},
735+
{
736+
Available: inodesFree,
737+
Total: inodes,
738+
Used: inodesUsed,
739+
Unit: csi.VolumeUsage_INODES,
740+
},
741+
},
742+
}, nil
743+
}
744+
745+
// blockNodeGetVolumeStats gets the metrics for a `volumeMode: Block` type of
746+
// volume. At the moment, only the size of the block-device can be returned, as
747+
// there are no secrets in the NodeGetVolumeStats request that enables us to
748+
// connect to the Lightbits cluster.
749+
//
750+
// TODO: https://github.com/container-storage-interface/spec/issues/371#issuecomment-756834471
751+
func blockNodeGetVolumeStats(ctx context.Context, log *logrus.Entry, targetPath string) (*csi.NodeGetVolumeStatsResponse, error) {
752+
args := []string{"--noheadings", "--bytes", "--output=SIZE", targetPath}
753+
lsblkSize, err := exec.CommandContext(ctx, "/bin/lsblk", args...).Output()
754+
if err != nil {
755+
err = fmt.Errorf("lsblk %v returned an error: %w", args, err)
756+
log.WithError(err).Error("blockNodeGetVolumeStats failed")
757+
758+
return nil, status.Error(codes.Internal, err.Error())
759+
}
760+
761+
size, err := strconv.ParseInt(strings.TrimSpace(string(lsblkSize)), 10, 64)
762+
if err != nil {
763+
err = fmt.Errorf("failed to convert %q to bytes: %w", lsblkSize, err)
764+
log.WithError(err).Error("blockNodeGetVolumeStats failed")
765+
766+
return nil, status.Error(codes.Internal, err.Error())
767+
}
768+
769+
return &csi.NodeGetVolumeStatsResponse{
770+
Usage: []*csi.VolumeUsage{
771+
{
772+
Total: size,
773+
Unit: csi.VolumeUsage_BYTES,
774+
},
775+
},
776+
}, nil
620777
}
621778

622779
func (d *Driver) NodeExpandVolume(

pkg/lb/client.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ func (s VolumeProtection) String() string {
7777

7878
type Volume struct {
7979
// "core" volume properties. q.v. IsSameAs().
80-
Name string
81-
UUID guuid.UUID
82-
ReplicaCount uint32
83-
Capacity uint64
84-
Compression bool
85-
SnapshotUUID guuid.UUID
80+
Name string
81+
UUID guuid.UUID
82+
ReplicaCount uint32
83+
Capacity uint64
84+
LogicalUsedStorage uint64
85+
Compression bool
86+
SnapshotUUID guuid.UUID
8687

8788
ACL []string
8889

pkg/lb/lbgrpc/client.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -659,16 +659,17 @@ func (c *Client) lbVolumeFromGRPC(
659659
}
660660

661661
return &lb.Volume{
662-
Name: vol.Name,
663-
UUID: volUUID,
664-
State: lbVolumeStateFromGRPC(vol.State),
665-
Protection: lbVolumeProtectionFromGRPC(vol.ProtectionState),
666-
ReplicaCount: vol.ReplicaCount,
667-
ACL: strlist.CopyUniqueSorted(vol.Acl.GetValues()),
668-
Capacity: vol.Size,
669-
Compression: compress,
670-
ETag: vol.ETag,
671-
ProjectName: vol.ProjectName,
662+
Name: vol.Name,
663+
UUID: volUUID,
664+
State: lbVolumeStateFromGRPC(vol.State),
665+
Protection: lbVolumeProtectionFromGRPC(vol.ProtectionState),
666+
ReplicaCount: vol.ReplicaCount,
667+
ACL: strlist.CopyUniqueSorted(vol.Acl.GetValues()),
668+
Capacity: vol.Size,
669+
LogicalUsedStorage: vol.Statistics.LogicalUsedStorage,
670+
Compression: compress,
671+
ETag: vol.ETag,
672+
ProjectName: vol.ProjectName,
672673
}, nil
673674
}
674675

vendor/github.com/gogo/protobuf/AUTHORS

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/gogo/protobuf/CONTRIBUTORS

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/gogo/protobuf/LICENSE

+35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/gogo/protobuf/proto/Makefile

+43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)