Skip to content

Commit c34d0ad

Browse files
nixpanicmergify[bot]
authored andcommitted
sidecar: add Node interface for listing volumes
Signed-off-by: Niels de Vos <ndevos@ibm.com>
1 parent 6c67c32 commit c34d0ad

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 node
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
24+
corev1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/client-go/kubernetes"
27+
"k8s.io/klog/v2"
28+
29+
"github.com/csi-addons/kubernetes-csi-addons/sidecar/internal/volume-condition/volume"
30+
)
31+
32+
// Node can handle requests for the local worker node.
33+
type Node interface {
34+
// ListCSIVolumes returns a slice of CSI-volumes that are attached to the
35+
// local worker node.
36+
ListCSIVolumes(ctx context.Context) ([]volume.CSIVolume, error)
37+
}
38+
39+
type node struct {
40+
clientset *kubernetes.Clientset
41+
42+
// nodename is used to filter the attachments by node
43+
nodename string
44+
}
45+
46+
// assert that node implements the Node interface.
47+
var _ Node = &node{}
48+
49+
// NewNode creates a new Node that represents the local worker node.
50+
func NewNode(ctx context.Context, clientset *kubernetes.Clientset, nodename string) (Node, error) {
51+
// verify that my local Node exists, will be fetched again in .ListCSIVolumes()
52+
_, err := clientset.CoreV1().Nodes().Get(ctx, nodename, metav1.GetOptions{})
53+
if err != nil {
54+
return nil, fmt.Errorf("could not get my local Node %q: %v", nodename, err)
55+
}
56+
57+
return &node{
58+
clientset: clientset,
59+
nodename: nodename,
60+
}, nil
61+
}
62+
63+
func (n *node) ListCSIVolumes(ctx context.Context) ([]volume.CSIVolume, error) {
64+
localNode, err := n.clientset.CoreV1().Nodes().Get(ctx, n.nodename, metav1.GetOptions{})
65+
if err != nil {
66+
return nil, fmt.Errorf("could not get Node %q: %v", n.nodename, err)
67+
}
68+
69+
vols := []volume.CSIVolume{}
70+
for _, attached := range localNode.Status.VolumesAttached {
71+
var vol volume.CSIVolume
72+
vol, err = newAttachedCSIVolume(attached.Name)
73+
if err != nil {
74+
klog.Infof("skipping non-CSI volume: %v", err)
75+
continue
76+
}
77+
78+
vols = append(vols, vol)
79+
}
80+
81+
return vols, nil
82+
}
83+
84+
// newAttachedCSIVolume parses a UniqueVolumeName that can be obtained from the list of attached
85+
// volumes on a node.
86+
// UniqueVolumeName is a string like "kubernetes.io/csi/ebs.csi.aws.com^vol-0c577c9c55718d592"
87+
// "vol-0c577c9c55718d592" is the volumeHandle (as in PV.Spec.CSI.volumeHandle)
88+
func newAttachedCSIVolume(name corev1.UniqueVolumeName) (volume.CSIVolume, error) {
89+
const (
90+
csiDriverPrefix = "kubernetes.io/csi/"
91+
driverVolumeSeparator = "^"
92+
)
93+
94+
driverVolume, found := strings.CutPrefix(string(name), csiDriverPrefix)
95+
if !found {
96+
return nil, fmt.Errorf("attached volume %q is not a CSI volume", name)
97+
}
98+
99+
parts := strings.Split(driverVolume, driverVolumeSeparator)
100+
if len(parts) != 2 {
101+
return nil, fmt.Errorf("format of attached volume %q is not supported: %s", name, driverVolume)
102+
}
103+
104+
return volume.NewCSIVolume(parts[0], parts[1]), nil
105+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 node
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
corev1 "k8s.io/api/core/v1"
24+
)
25+
26+
func TestNewAttachedCSIVolume(t *testing.T) {
27+
cases := []struct {
28+
name string
29+
volumeName corev1.UniqueVolumeName
30+
csiDriver string // CSIVolume.GetDriver()
31+
csiVolume string // CSIVolume.GetVolumeID()
32+
expectError bool
33+
}{
34+
{
35+
name: "happy path",
36+
volumeName: "kubernetes.io/csi/ebs.csi.aws.com^vol-0c577c9c55718d592",
37+
csiDriver: "ebs.csi.aws.com",
38+
csiVolume: "vol-0c577c9c55718d592",
39+
expectError: false,
40+
},
41+
{
42+
name: "not a csi driver",
43+
volumeName: corev1.UniqueVolumeName("kubernetes.io/native/ebs.csi.aws.com^vol-0c577c9c55718d592"),
44+
csiDriver: "ebs.csi.aws.com",
45+
csiVolume: "vol-0c577c9c55718d592",
46+
expectError: true,
47+
},
48+
{
49+
name: "repeated path component separator",
50+
volumeName: corev1.UniqueVolumeName("kubernetes.io/csi^ebs.csi.aws.com^vol-0c577c9c55718d592"),
51+
csiDriver: "ebs.csi.aws.com",
52+
csiVolume: "vol-0c577c9c55718d592",
53+
expectError: true,
54+
},
55+
{
56+
name: "incorrect UniqueVolumeName format",
57+
volumeName: corev1.UniqueVolumeName("kubernetes.io/csi/ebs.csi.aws.com~vol-0c577c9c55718d592"),
58+
csiDriver: "ebs.csi.aws.com",
59+
csiVolume: "vol-0c577c9c55718d592",
60+
expectError: true,
61+
},
62+
}
63+
64+
for _, tt := range cases {
65+
t.Run(tt.name, func(t *testing.T) {
66+
volume, err := newAttachedCSIVolume(tt.volumeName)
67+
if tt.expectError {
68+
assert.NotNil(t, err)
69+
return
70+
} else {
71+
assert.Nil(t, err)
72+
}
73+
74+
assert.Equal(t, tt.csiDriver, volume.GetDriver())
75+
assert.Equal(t, tt.csiVolume, volume.GetVolumeID())
76+
})
77+
}
78+
}

0 commit comments

Comments
 (0)