Skip to content

Commit 69ccc6c

Browse files
nixpanicmergify[bot]
authored andcommitted
sidecar: add functions for Kuberneres Platform detection
Signed-off-by: Niels de Vos <ndevos@ibm.com>
1 parent 7056195 commit 69ccc6c

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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 platform
18+
19+
import (
20+
"crypto/sha256"
21+
"encoding/json"
22+
"fmt"
23+
"os"
24+
"path/filepath"
25+
"strings"
26+
27+
"k8s.io/klog/v2"
28+
"k8s.io/mount-utils"
29+
)
30+
31+
// kubelet contains the paths for Kubernetes based platforms
32+
type kubelet struct {
33+
basedir string
34+
pluginPath string
35+
csiPath string
36+
mounter mount.Interface
37+
}
38+
39+
// assert that kubelet implements the Platform interface.
40+
var _ Platform = &kubelet{}
41+
42+
// getKubelet() returns a *kubelet with default paths. Other functions could do
43+
// some detection or inspection, and return a *kubelet for microk8s or other
44+
// variations.
45+
func getKubelet() Platform {
46+
return &kubelet{
47+
basedir: "/var/lib/kubelet",
48+
pluginPath: "/plugins",
49+
csiPath: "/kubernetes.io/csi",
50+
mounter: mount.NewWithoutSystemd("/bin/mount"),
51+
}
52+
}
53+
54+
// GetStagingPath checks the known kubelet path where the volume may be mounted.
55+
// The staging path looks like:
56+
// /var/lib/kubelet
57+
// - /plugins
58+
// - /kubernetes.io/csi
59+
// - /openshift-storage.rbd.csi.ceph.com
60+
// - /07d25c3a330223ce3dd44b6bbd5911523785b0cf44f1624fc9b949c0fbdd9d00
61+
// - /globalmount
62+
// - /0001-0011-openshift-storage-0000000000000001-316a945f-00b5-45e7-a9e1-56658da698b9
63+
//
64+
// Where 07d25c3a330223ce3dd44b6bbd5911523785b0cf44f1624fc9b949c0fbdd9d00 = sha256sum(volumeID)
65+
func (k *kubelet) GetStagingPath(driver, volumeID string) (string, error) {
66+
hash := sha256.Sum256([]byte(volumeID))
67+
68+
stagingPath := filepath.Join(
69+
k.basedir,
70+
k.pluginPath,
71+
k.csiPath,
72+
driver,
73+
fmt.Sprintf("%x", hash),
74+
"globalmount",
75+
)
76+
77+
if k.isMountPoint(stagingPath) {
78+
return stagingPath, nil
79+
}
80+
81+
// special case for Ceph-CSI: stagingPath ending with "/globalmount" is not a
82+
// mountpoint. Add the volumeID to the path and check again.
83+
stagingPath = filepath.Join(stagingPath, volumeID)
84+
if k.isMountPoint(stagingPath) {
85+
return stagingPath, nil
86+
}
87+
88+
// TODO: maybe need to check if it is a directory or blockdevice?
89+
// fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/%s/%s", "spec-0", "dev")
90+
// fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/staging/%s", "spec-0")
91+
92+
return "", fmt.Errorf("could not find a mountpoint at %q", stagingPath)
93+
94+
}
95+
96+
func (k *kubelet) GetPublishPath(driver, volumeID string) (string, error) {
97+
path, _, err := k.getPublishDetails(driver, volumeID)
98+
if err != nil {
99+
return "", fmt.Errorf("failed to find publish path for volume handle %q of driver %q: %w", volumeID, driver, err)
100+
}
101+
102+
return path, nil
103+
}
104+
105+
func (k *kubelet) GetCSISocket(driver string) string {
106+
// TODO: should probably check if it is UNIX domain socket
107+
return filepath.Join(
108+
"unix://",
109+
k.basedir,
110+
k.pluginPath,
111+
driver,
112+
"csi.sock",
113+
)
114+
}
115+
116+
func (k *kubelet) ResolvePersistentVolumeName(driver, volumeID string) (string, error) {
117+
_, pvName, err := k.getPublishDetails(driver, volumeID)
118+
if err != nil {
119+
return "", fmt.Errorf("failed to get persistent volume name for volume handle %q of driver %q: %w", volumeID, driver, err)
120+
}
121+
122+
return pvName, nil
123+
}
124+
125+
func (k *kubelet) isMountPoint(path string) bool {
126+
isMnt, err := k.mounter.IsMountPoint(path)
127+
if err != nil {
128+
klog.Errorf("failed to check if %q is a mountpoint: %v", path, err)
129+
return false
130+
}
131+
132+
return isMnt
133+
}
134+
135+
// getPublishDetails tries to find the directory where the volume is mounted.
136+
//
137+
// Kubelet uses /var/lib/kubelet/pods/*/volumes/*/vol_data.json for storing
138+
// details about the mountpoint. The JSON file can be parsed and checked if the
139+
// drivername and volumeID match what we are looking for.
140+
//
141+
// The publish path is the in the same directory as where the vol_data.json
142+
// resides. The volume is mounted on a directory called "mount".
143+
//
144+
// TODO: blockmode
145+
// fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/publish/%s/%s", "spec-0", testPodUID)
146+
func (k *kubelet) getPublishDetails(driver, volumeID string) (string, string, error) {
147+
volDatas, err := filepath.Glob(
148+
filepath.Join(
149+
k.basedir,
150+
"pods",
151+
"*", // UUID of a Pod
152+
"volumes/kubernetes.io~csi/",
153+
"*", // name of a PersistentVolume
154+
"vol_data.json",
155+
),
156+
)
157+
if err != nil {
158+
return "", "", fmt.Errorf("could not find vol_data.json: %v", err)
159+
}
160+
161+
for _, filename := range volDatas {
162+
data, err := os.ReadFile(filename)
163+
if err != nil {
164+
klog.Errorf("failed to read file %q: %v", filename, err)
165+
continue
166+
}
167+
168+
vd := struct {
169+
Driver string `json:"driverName"`
170+
PersistentVolumeName string `json:"specVolID"`
171+
VolumeID string `json:"volumeHandle"`
172+
}{}
173+
err = json.Unmarshal(data, &vd)
174+
if err != nil {
175+
klog.Errorf("failed to parse JSON from %q: %v", filename, err)
176+
continue
177+
}
178+
179+
if vd.Driver != driver || vd.VolumeID != volumeID {
180+
continue
181+
}
182+
183+
// TODO: is this correct for block-volumes too?
184+
return strings.ReplaceAll(filename, "vol_data.json", "mount"), vd.PersistentVolumeName, nil
185+
}
186+
187+
return "", "", fmt.Errorf("could not find a vol_data.json for driver %q and volumeID %q", driver, volumeID)
188+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 platform
18+
19+
// Platform provides functions that a Driver can use to find details about the
20+
// deployment. Different platforms use different directories and paths to
21+
// communicate with drivers, and locations where volumes are mounted.
22+
type Platform interface {
23+
// GetStagingPath returns the volume staging path that a platform uses.
24+
// Not all drivers use the staging path, in which case no staging path
25+
// can be found and an error is returned.
26+
GetStagingPath(driver, volumeID string) (string, error)
27+
28+
// GetPublishPath returns the path where the volume is mounted and
29+
// made available for apps/pods to use.
30+
GetPublishPath(driver, volumeID string) (string, error)
31+
32+
// GetCSISocket returns the UNIX Domain Socket for a particular
33+
// CSI-driver.
34+
GetCSISocket(driver string) string
35+
36+
// ResolvePersistentVolumeName tries to identify the name of the
37+
// PersistentVolume, based on the volumeID.
38+
ResolvePersistentVolumeName(driver, volumeID string) (string, error)
39+
}
40+
41+
// singletonPlatform is used to create the Platform utility object once. While
42+
// the application runs, the platform will not suddenly change.
43+
var singletonPlatform Platform = nil
44+
45+
// GetPlatform returns the object with utility functions for the current running
46+
// variant of Kubernetes.
47+
func GetPlatform() Platform {
48+
if singletonPlatform != nil {
49+
return singletonPlatform
50+
}
51+
52+
// TODO: switch on detecting different platforms (kind, microk8s?)
53+
singletonPlatform = getKubelet()
54+
55+
return singletonPlatform
56+
}

0 commit comments

Comments
 (0)