Skip to content

Commit 8105a9d

Browse files
committed
feat: inline volume support
fix fix
1 parent d0687af commit 8105a9d

15 files changed

+525
-7
lines changed
60 Bytes
Binary file not shown.

charts/latest/csi-driver-smb/templates/csi-smb-driver.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ metadata:
66
spec:
77
attachRequired: false
88
podInfoOnMount: true
9+
volumeLifecycleModes:
10+
- Persistent
11+
- Ephemeral

charts/latest/csi-driver-smb/templates/rbac-csi-smb.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,29 @@ roleRef:
9797
kind: ClusterRole
9898
name: {{ .Values.rbac.name }}-external-resizer-role
9999
apiGroup: rbac.authorization.k8s.io
100+
---
101+
kind: ClusterRole
102+
apiVersion: rbac.authorization.k8s.io/v1
103+
metadata:
104+
name: csi-{{ .Values.rbac.name }}-node-secret-role
105+
{{ include "smb.labels" . | indent 2 }}
106+
rules:
107+
- apiGroups: [""]
108+
resources: ["secrets"]
109+
verbs: ["get"]
110+
111+
---
112+
kind: ClusterRoleBinding
113+
apiVersion: rbac.authorization.k8s.io/v1
114+
metadata:
115+
name: csi-{{ .Values.rbac.name }}-node-secret-binding
116+
{{ include "smb.labels" . | indent 2 }}
117+
subjects:
118+
- kind: ServiceAccount
119+
name: {{ .Values.serviceAccount.node }}
120+
namespace: {{ .Release.Namespace }}
121+
roleRef:
122+
kind: ClusterRole
123+
name: csi-{{ .Values.rbac.name }}-node-secret-role
124+
apiGroup: rbac.authorization.k8s.io
100125
{{ end }}

deploy/csi-smb-driver.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ metadata:
66
spec:
77
attachRequired: false
88
podInfoOnMount: true
9+
volumeLifecycleModes:
10+
- Persistent
11+
- Ephemeral
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
kind: Pod
3+
apiVersion: v1
4+
metadata:
5+
name: nginx-smb-inline-volume
6+
spec:
7+
nodeSelector:
8+
"kubernetes.io/os": linux
9+
containers:
10+
- image: mcr.microsoft.com/mirror/docker/library/nginx:1.23
11+
name: nginx-smb
12+
command:
13+
- "/bin/bash"
14+
- "-c"
15+
- set -euo pipefail; while true; do echo $(date) >> /mnt/smb/outfile; sleep 1; done
16+
volumeMounts:
17+
- name: persistent-storage
18+
mountPath: "/mnt/smb"
19+
readOnly: false
20+
volumes:
21+
- name: persistent-storage
22+
csi:
23+
driver: smb.csi.k8s.io
24+
volumeAttributes:
25+
source: //smb-server.default.svc.cluster.local/share # required
26+
secretName: smbcreds # required, secretNamespace is the same as the pod
27+
mountOptions: "dir_mode=0777,file_mode=0777,cache=strict,actimeo=30,nosharesock" # optional

deploy/rbac-csi-smb.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,25 @@ roleRef:
8787
kind: ClusterRole
8888
name: smb-external-resizer-role
8989
apiGroup: rbac.authorization.k8s.io
90+
---
91+
kind: ClusterRole
92+
apiVersion: rbac.authorization.k8s.io/v1
93+
metadata:
94+
name: csi-smb-node-secret-role
95+
rules:
96+
- apiGroups: [""]
97+
resources: ["secrets"]
98+
verbs: ["get"]
99+
---
100+
kind: ClusterRoleBinding
101+
apiVersion: rbac.authorization.k8s.io/v1
102+
metadata:
103+
name: csi-smb-node-secret-binding
104+
subjects:
105+
- kind: ServiceAccount
106+
name: csi-smb-node-sa
107+
namespace: kube-system
108+
roleRef:
109+
kind: ClusterRole
110+
name: csi-smb-node-secret-role
111+
apiGroup: rbac.authorization.k8s.io

pkg/smb/nodeserver.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ import (
3636

3737
"golang.org/x/net/context"
3838

39-
volumehelper "github.com/kubernetes-csi/csi-driver-smb/pkg/util"
39+
"github.com/kubernetes-csi/csi-driver-smb/pkg/util"
4040
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
4141
)
4242

4343
// NodePublishVolume mount the volume from staging to target path
44-
func (d *Driver) NodePublishVolume(_ context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
45-
if req.GetVolumeCapability() == nil {
44+
func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
45+
volCap := req.GetVolumeCapability()
46+
if volCap == nil {
4647
return nil, status.Error(codes.InvalidArgument, "Volume capability missing in request")
4748
}
4849
volumeID := req.GetVolumeId()
@@ -55,6 +56,20 @@ func (d *Driver) NodePublishVolume(_ context.Context, req *csi.NodePublishVolume
5556
return nil, status.Error(codes.InvalidArgument, "Target path not provided")
5657
}
5758

59+
context := req.GetVolumeContext()
60+
if context != nil && strings.EqualFold(context[ephemeralField], trueValue) {
61+
// ephemeral volume
62+
util.SetKeyValueInMap(context, secretNamespaceField, context[podNamespaceField])
63+
klog.V(2).Infof("NodePublishVolume: ephemeral volume(%s) mount on %s", volumeID, target)
64+
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
65+
StagingTargetPath: target,
66+
VolumeContext: context,
67+
VolumeCapability: volCap,
68+
VolumeId: volumeID,
69+
})
70+
return &csi.NodePublishVolumeResponse{}, err
71+
}
72+
5873
source := req.GetStagingTargetPath()
5974
if len(source) == 0 {
6075
return nil, status.Error(codes.InvalidArgument, "Staging target not provided")
@@ -110,7 +125,7 @@ func (d *Driver) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpublishVo
110125
}
111126

112127
// NodeStageVolume mount the volume to a staging path
113-
func (d *Driver) NodeStageVolume(_ context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
128+
func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
114129
volumeID := req.GetVolumeId()
115130
if len(volumeID) == 0 {
116131
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
@@ -132,7 +147,7 @@ func (d *Driver) NodeStageVolume(_ context.Context, req *csi.NodeStageVolumeRequ
132147
secrets := req.GetSecrets()
133148
gidPresent := checkGidPresentInMountFlags(mountFlags)
134149

135-
var source, subDir string
150+
var source, subDir, secretName, secretNamespace string
136151
subDirReplaceMap := map[string]string{}
137152
for k, v := range context {
138153
switch strings.ToLower(k) {
@@ -146,6 +161,10 @@ func (d *Driver) NodeStageVolume(_ context.Context, req *csi.NodeStageVolumeRequ
146161
subDirReplaceMap[pvcNameMetadata] = v
147162
case pvNameKey:
148163
subDirReplaceMap[pvNameMetadata] = v
164+
case secretNameField:
165+
secretName = v
166+
case secretNamespaceField:
167+
secretNamespace = v
149168
}
150169
}
151170

@@ -171,6 +190,15 @@ func (d *Driver) NodeStageVolume(_ context.Context, req *csi.NodeStageVolumeRequ
171190
}
172191
}
173192

193+
if (username == "" || password == "") && (secretName != "" && secretNamespace != "") {
194+
klog.V(2).Infof("NodeStageVolume: getting username and password from secret %s in namespace %s", secretName, secretNamespace)
195+
var err error
196+
username, password, domain, err = d.GetUserNamePasswordFromSecret(ctx, secretName, secretNamespace)
197+
if err != nil {
198+
return nil, status.Error(codes.Internal, fmt.Sprintf("Error getting username and password from secret %s in namespace %s: %v", secretName, secretNamespace, err))
199+
}
200+
}
201+
174202
// in guest login, username and password options are not needed
175203
requireUsernamePwdOption := !hasGuestMountOptions(mountFlags)
176204

@@ -236,7 +264,7 @@ func (d *Driver) NodeStageVolume(_ context.Context, req *csi.NodeStageVolumeRequ
236264
return Mount(d.mounter, source, targetPath, "cifs", mountOptions, sensitiveMountOptions, volumeID)
237265
}
238266
timeoutFunc := func() error { return fmt.Errorf("time out") }
239-
if err := volumehelper.WaitUntilTimeout(90*time.Second, execFunc, timeoutFunc); err != nil {
267+
if err := util.WaitUntilTimeout(90*time.Second, execFunc, timeoutFunc); err != nil {
240268
return nil, status.Error(codes.Internal, fmt.Sprintf("volume(%s) mount %q on %q failed with %v", volumeID, source, targetPath, err))
241269
}
242270
klog.V(2).Infof("volume(%s) mount %q on %q succeeded", volumeID, source, targetPath)

pkg/smb/smb.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ limitations under the License.
1717
package smb
1818

1919
import (
20+
"context"
21+
"errors"
2022
"fmt"
23+
"net"
24+
"os"
25+
"path/filepath"
2126
"strings"
2227
"time"
2328

2429
"github.com/container-storage-interface/spec/lib/go/csi"
2530

31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
"k8s.io/client-go/kubernetes"
33+
"k8s.io/client-go/rest"
34+
"k8s.io/client-go/tools/clientcmd"
35+
certutil "k8s.io/client-go/util/cert"
2636
"k8s.io/klog/v2"
2737
mount "k8s.io/mount-utils"
2838

@@ -40,8 +50,12 @@ const (
4050
subDirField = "subdir"
4151
domainField = "domain"
4252
mountOptionsField = "mountoptions"
53+
secretNameField = "secretname"
54+
secretNamespaceField = "secretnamespace"
4355
paramOnDelete = "ondelete"
4456
defaultDomainName = "AZURE"
57+
ephemeralField = "csi.storage.k8s.io/ephemeral"
58+
podNamespaceField = "csi.storage.k8s.io/pod.namespace"
4559
pvcNameKey = "csi.storage.k8s.io/pvc/name"
4660
pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace"
4761
pvNameKey = "csi.storage.k8s.io/pv/name"
@@ -56,6 +70,7 @@ const (
5670
dirMode = "dir_mode"
5771
defaultFileMode = "0777"
5872
defaultDirMode = "0777"
73+
trueValue = "true"
5974
)
6075

6176
var supportedOnDeleteValues = []string{"", "delete", retain, archive}
@@ -74,6 +89,7 @@ type DriverOptions struct {
7489
DefaultOnDeletePolicy string
7590
RemoveArchivedVolumePath bool
7691
EnableWindowsHostProcess bool
92+
Kubeconfig string
7793
}
7894

7995
// Driver implements all interfaces of CSI drivers
@@ -102,6 +118,8 @@ type Driver struct {
102118
defaultOnDeletePolicy string
103119
removeArchivedVolumePath bool
104120
enableWindowsHostProcess bool
121+
kubeconfig string
122+
kubeClient kubernetes.Interface
105123
}
106124

107125
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
@@ -116,6 +134,7 @@ func NewDriver(options *DriverOptions) *Driver {
116134
driver.removeArchivedVolumePath = options.RemoveArchivedVolumePath
117135
driver.workingMountDir = options.WorkingMountDir
118136
driver.enableWindowsHostProcess = options.EnableWindowsHostProcess
137+
driver.kubeconfig = options.Kubeconfig
119138
driver.volumeLocks = newVolumeLocks()
120139

121140
driver.krb5CacheDirectory = options.Krb5CacheDirectory
@@ -138,6 +157,15 @@ func NewDriver(options *DriverOptions) *Driver {
138157
if driver.volDeletionCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
139158
klog.Fatalf("%v", err)
140159
}
160+
161+
kubeCfg, err := getKubeConfig(driver.kubeconfig, driver.enableWindowsHostProcess)
162+
if err == nil && kubeCfg != nil {
163+
if driver.kubeClient, err = kubernetes.NewForConfig(kubeCfg); err != nil {
164+
klog.Warningf("NewForConfig failed with error: %v", err)
165+
}
166+
} else {
167+
klog.Warningf("get kubeconfig(%s) failed with error: %v", driver.kubeconfig, err)
168+
}
141169
return &driver
142170
}
143171

@@ -189,6 +217,24 @@ func (d *Driver) Run(endpoint, _ string, testMode bool) {
189217
s.Wait()
190218
}
191219

220+
// GetUserNamePasswordFromSecret get storage account key from k8s secret
221+
// return <username, password, domain, error>
222+
func (d *Driver) GetUserNamePasswordFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, error) {
223+
if d.kubeClient == nil {
224+
return "", "", "", fmt.Errorf("could not username and password from secret(%s): KubeClient is nil", secretName)
225+
}
226+
227+
secret, err := d.kubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
228+
if err != nil {
229+
return "", "", "", fmt.Errorf("could not get secret(%v): %v", secretName, err)
230+
}
231+
232+
username := strings.TrimSpace(string(secret.Data[usernameField][:]))
233+
password := strings.TrimSpace(string(secret.Data[passwordField][:]))
234+
domain := strings.TrimSpace(string(secret.Data[domainField][:]))
235+
return username, password, domain, nil
236+
}
237+
192238
func IsCorruptedDir(dir string) bool {
193239
_, pathErr := mount.PathExists(dir)
194240
return pathErr != nil && mount.IsCorruptedMnt(pathErr)
@@ -279,3 +325,61 @@ func getRootDir(path string) string {
279325
parts := strings.Split(path, "/")
280326
return parts[0]
281327
}
328+
329+
func getKubeConfig(kubeconfig string, enableWindowsHostProcess bool) (config *rest.Config, err error) {
330+
if kubeconfig != "" {
331+
if config, err = clientcmd.BuildConfigFromFlags("", kubeconfig); err != nil {
332+
return nil, err
333+
}
334+
} else {
335+
if config, err = inClusterConfig(enableWindowsHostProcess); err != nil {
336+
return nil, err
337+
}
338+
}
339+
return config, err
340+
}
341+
342+
// inClusterConfig is copied from https://github.com/kubernetes/client-go/blob/b46677097d03b964eab2d67ffbb022403996f4d4/rest/config.go#L507-L541
343+
// When using Windows HostProcess containers, the path "/var/run/secrets/kubernetes.io/serviceaccount/" is under host, not container.
344+
// Then the token and ca.crt files would be not found.
345+
// An environment variable $CONTAINER_SANDBOX_MOUNT_POINT is set upon container creation and provides the absolute host path to the container volume.
346+
// See https://kubernetes.io/docs/tasks/configure-pod-container/create-hostprocess-pod/#volume-mounts for more details.
347+
func inClusterConfig(enableWindowsHostProcess bool) (*rest.Config, error) {
348+
var (
349+
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
350+
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
351+
)
352+
if enableWindowsHostProcess {
353+
containerSandboxMountPath := os.Getenv("CONTAINER_SANDBOX_MOUNT_POINT")
354+
if len(containerSandboxMountPath) == 0 {
355+
return nil, errors.New("unable to load in-cluster configuration, containerSandboxMountPath must be defined")
356+
}
357+
tokenFile = filepath.Join(containerSandboxMountPath, tokenFile)
358+
rootCAFile = filepath.Join(containerSandboxMountPath, rootCAFile)
359+
}
360+
361+
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
362+
if len(host) == 0 || len(port) == 0 {
363+
return nil, rest.ErrNotInCluster
364+
}
365+
366+
token, err := os.ReadFile(tokenFile)
367+
if err != nil {
368+
return nil, err
369+
}
370+
371+
tlsClientConfig := rest.TLSClientConfig{}
372+
373+
if _, err := certutil.NewPool(rootCAFile); err != nil {
374+
klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
375+
} else {
376+
tlsClientConfig.CAFile = rootCAFile
377+
}
378+
379+
return &rest.Config{
380+
Host: "https://" + net.JoinHostPort(host, port),
381+
TLSClientConfig: tlsClientConfig,
382+
BearerToken: string(token),
383+
BearerTokenFile: tokenFile,
384+
}, nil
385+
}

0 commit comments

Comments
 (0)