Skip to content

Commit 6c67c32

Browse files
nixpanicmergify[bot]
authored andcommitted
sidecar: add CSI procedure calls for NodeGetVolumeStats
Signed-off-by: Niels de Vos <ndevos@ibm.com>
1 parent 69ccc6c commit 6c67c32

File tree

3 files changed

+251
-0
lines changed

3 files changed

+251
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright 2025 The Kubernetes-CSI-Addons Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package volume
18+
19+
const (
20+
VolumeConditionHealthy = "VolumeConditionHealthy"
21+
VolumeConditionAbnormal = "VolumeConditionAbnormal"
22+
)
23+
24+
type VolumeCondition interface {
25+
IsHealthy() bool
26+
GetReason() string
27+
GetMessage() string
28+
}
29+
30+
type volumeCondition struct {
31+
healthy bool
32+
message string
33+
}
34+
35+
// assert that volumeCondition implements VolumeCondition
36+
var _ VolumeCondition = volumeCondition{}
37+
38+
func (vc volumeCondition) IsHealthy() bool {
39+
return vc.healthy
40+
}
41+
42+
func (vc volumeCondition) GetReason() string {
43+
if vc.healthy {
44+
return VolumeConditionHealthy
45+
}
46+
47+
return VolumeConditionAbnormal
48+
}
49+
50+
func (vc volumeCondition) GetMessage() string {
51+
return vc.message
52+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright 2025 The Kubernetes-CSI-Addons Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package volume
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/container-storage-interface/spec/lib/go/csi"
24+
"google.golang.org/grpc"
25+
"google.golang.org/grpc/credentials/insecure"
26+
27+
"github.com/csi-addons/kubernetes-csi-addons/sidecar/internal/volume-condition/platform"
28+
)
29+
30+
// Driver provides the API for communicating with a CSI-driver.
31+
type Driver interface {
32+
// GetDrivername returns the name of the CSI-driver.
33+
GetDrivername() string
34+
// SupportsVolumeCondition can be used to check if the CSI-driver
35+
// supports reporting the VolumeCondition (if the node has the
36+
// VOLUME_CONDITION capability).
37+
SupportsVolumeCondition() bool
38+
// GetVolumeCondition requests the VolumeCondition from the
39+
// CSI-driver.
40+
GetVolumeCondition(CSIVolume) (VolumeCondition, error)
41+
}
42+
43+
type csiDriver struct {
44+
name string
45+
46+
nodeClient csi.NodeClient
47+
48+
supportVolumeCondition bool
49+
supportsNodeStageVolume bool
50+
}
51+
52+
// FindDriver tries to connect to the CSI-driver with the given name. If
53+
// a connection is made, it verifies the identity of the driver and its
54+
// capabilities.
55+
func FindDriver(ctx context.Context, name string) (Driver, error) {
56+
endpoint := platform.GetPlatform().GetCSISocket(name)
57+
conn, err := grpc.NewClient(
58+
endpoint,
59+
grpc.WithTransportCredentials(insecure.NewCredentials()))
60+
if err != nil {
61+
return nil, fmt.Errorf("failed to connect to endpoint %s: %w", endpoint, err)
62+
}
63+
64+
// verify that the requested drivername is indeed connected on the socket
65+
identityClient := csi.NewIdentityClient(conn)
66+
res, err := identityClient.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{})
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to connect to get info from CSI driver %q: %w", name, err)
69+
} else if res.GetName() != name {
70+
return nil, fmt.Errorf("CSI driver %q incorrectly identifies itself as %q", name, res.GetName())
71+
}
72+
73+
drv := &csiDriver{
74+
name: name,
75+
nodeClient: csi.NewNodeClient(conn),
76+
}
77+
78+
err = drv.detectCapabilities(ctx)
79+
if err != nil {
80+
return nil, fmt.Errorf("failed to detect the capabilities of CSI driver %q: %w", name, err)
81+
}
82+
83+
return drv, nil
84+
}
85+
86+
func (drv *csiDriver) GetDrivername() string {
87+
return drv.name
88+
}
89+
90+
func (drv *csiDriver) SupportsVolumeCondition() bool {
91+
return drv.supportVolumeCondition
92+
}
93+
94+
func (drv *csiDriver) GetVolumeCondition(v CSIVolume) (VolumeCondition, error) {
95+
var (
96+
err error
97+
volumePath string
98+
)
99+
100+
if drv.supportsNodeStageVolume {
101+
volumePath, err = platform.GetPlatform().GetStagingPath(drv.name, v.GetVolumeID())
102+
if err != nil {
103+
return nil, fmt.Errorf("failed to get staging path: %w", err)
104+
}
105+
} else {
106+
volumePath, err = platform.GetPlatform().GetPublishPath(drv.name, v.GetVolumeID())
107+
if err != nil {
108+
return nil, fmt.Errorf("failed to get publish path: %w", err)
109+
}
110+
}
111+
112+
req := &csi.NodeGetVolumeStatsRequest{
113+
VolumeId: v.GetVolumeID(),
114+
VolumePath: volumePath,
115+
}
116+
117+
res, err := drv.nodeClient.NodeGetVolumeStats(context.TODO(), req)
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to call NodeGetVolumeStats: %w", err)
120+
}
121+
122+
if res.GetVolumeCondition() == nil {
123+
return nil, fmt.Errorf("VolumeCondition unknown")
124+
}
125+
126+
vc := &volumeCondition{
127+
healthy: !res.GetVolumeCondition().GetAbnormal(),
128+
message: res.GetVolumeCondition().GetMessage(),
129+
}
130+
131+
return vc, err
132+
}
133+
134+
// detectCapabilities calls the NodeGetCapabilities gRPC procedure to detect
135+
// the capabilities of the CSI-driver.
136+
func (drv *csiDriver) detectCapabilities(ctx context.Context) error {
137+
res, err := drv.nodeClient.NodeGetCapabilities(ctx, &csi.NodeGetCapabilitiesRequest{})
138+
if err != nil {
139+
return fmt.Errorf("failed to get capabilities of driver %q: %v", drv.name, err)
140+
}
141+
142+
for _, capability := range res.GetCapabilities() {
143+
switch capability.GetRpc().GetType() {
144+
case csi.NodeServiceCapability_RPC_VOLUME_CONDITION:
145+
drv.supportVolumeCondition = true
146+
case csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME:
147+
drv.supportsNodeStageVolume = true
148+
}
149+
}
150+
151+
return nil
152+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2025 The Kubernetes-CSI-Addons Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package volume
18+
19+
// CSIVolume describes an attached volume.
20+
type CSIVolume interface {
21+
// GetDriver returns the name of the CSI-driver that is responsible
22+
// for this volume.
23+
GetDriver() string
24+
// GetVolumeID returns the ID (volume-handle) of the volume.
25+
GetVolumeID() string
26+
}
27+
28+
type csiVolume struct {
29+
drivername string
30+
volumeID string
31+
}
32+
33+
// NewCSIVolume creates a new CSIVolume with the given drivername and volumeID.
34+
func NewCSIVolume(drivername, volumeID string) CSIVolume {
35+
return &csiVolume{
36+
drivername: drivername,
37+
volumeID: volumeID,
38+
}
39+
}
40+
41+
func (vol *csiVolume) GetDriver() string {
42+
return vol.drivername
43+
}
44+
45+
func (vol *csiVolume) GetVolumeID() string {
46+
return vol.volumeID
47+
}

0 commit comments

Comments
 (0)