Skip to content

Commit 02cf55d

Browse files
authored
Merge pull request #245 from alexander-ding/enh/migrate-v2
feat: migrate smb API group to library model
2 parents 90ea3ad + 4f70284 commit 02cf55d

File tree

5 files changed

+487
-16
lines changed

5 files changed

+487
-16
lines changed

integrationtests/smb_test.go

+82-16
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,98 @@
11
package integrationtests
22

33
import (
4+
"context"
45
"fmt"
56
"io/ioutil"
67
"math/rand"
78
"os"
89
"os/exec"
910
"strings"
11+
"testing"
1012
"time"
1113

12-
"testing"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
17+
fs "github.com/kubernetes-csi/csi-proxy/pkg/filesystem"
18+
fsapi "github.com/kubernetes-csi/csi-proxy/pkg/filesystem/api"
19+
"github.com/kubernetes-csi/csi-proxy/pkg/smb"
20+
smbapi "github.com/kubernetes-csi/csi-proxy/pkg/smb/api"
1321
)
1422

23+
func TestSmbAPIGroup(t *testing.T) {
24+
t.Run("v1alpha1SmbTests", func(t *testing.T) {
25+
v1alpha1SmbTests(t)
26+
})
27+
t.Run("v1beta1SmbTests", func(t *testing.T) {
28+
v1beta1SmbTests(t)
29+
})
30+
t.Run("v1beta2SmbTests", func(t *testing.T) {
31+
v1beta2SmbTests(t)
32+
})
33+
t.Run("v1SmbTests", func(t *testing.T) {
34+
v1SmbTests(t)
35+
})
36+
}
37+
38+
func TestSmb(t *testing.T) {
39+
fsClient, err := fs.New(fsapi.New())
40+
require.Nil(t, err)
41+
client, err := smb.New(smbapi.New(), fsClient)
42+
require.Nil(t, err)
43+
44+
username := randomString(5)
45+
password := randomString(10) + "!"
46+
sharePath := fmt.Sprintf("C:\\smbshare%s", randomString(5))
47+
smbShare := randomString(6)
48+
49+
localPath := fmt.Sprintf("C:\\localpath%s", randomString(5))
50+
51+
if err = setupUser(username, password); err != nil {
52+
t.Fatalf("TestSmbAPIGroup %v", err)
53+
}
54+
defer removeUser(t, username)
55+
56+
if err = setupSmbShare(smbShare, sharePath, username); err != nil {
57+
t.Fatalf("TestSmbAPIGroup %v", err)
58+
}
59+
defer removeSmbShare(t, smbShare)
60+
61+
hostname, err := os.Hostname()
62+
assert.Nil(t, err)
63+
64+
username = "domain\\" + username
65+
remotePath := "\\\\" + hostname + "\\" + smbShare
66+
// simulate Mount SMB operations around staging a volume on a node
67+
mountSmbShareReq := &smb.NewSmbGlobalMappingRequest{
68+
RemotePath: remotePath,
69+
Username: username,
70+
Password: password,
71+
}
72+
_, err = client.NewSmbGlobalMapping(context.Background(), mountSmbShareReq)
73+
if err != nil {
74+
t.Fatalf("TestSmbAPIGroup %v", err)
75+
}
76+
77+
err = getSmbGlobalMapping(remotePath)
78+
assert.Nil(t, err)
79+
80+
err = writeReadFile(remotePath)
81+
assert.Nil(t, err)
82+
83+
unmountSmbShareReq := &smb.RemoveSmbGlobalMappingRequest{
84+
RemotePath: remotePath,
85+
}
86+
_, err = client.RemoveSmbGlobalMapping(context.Background(), unmountSmbShareReq)
87+
if err != nil {
88+
t.Fatalf("TestSmbAPIGroup %v", err)
89+
}
90+
err = getSmbGlobalMapping(remotePath)
91+
assert.NotNil(t, err)
92+
err = writeReadFile(localPath)
93+
assert.NotNil(t, err)
94+
}
95+
1596
const letterset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
1697

1798
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -121,18 +202,3 @@ func writeReadFile(path string) error {
121202
}
122203
return nil
123204
}
124-
125-
func TestSmbAPIGroup(t *testing.T) {
126-
t.Run("v1alpha1SmbTests", func(t *testing.T) {
127-
v1alpha1SmbTests(t)
128-
})
129-
t.Run("v1beta1SmbTests", func(t *testing.T) {
130-
v1beta1SmbTests(t)
131-
})
132-
t.Run("v1beta2SmbTests", func(t *testing.T) {
133-
v1beta2SmbTests(t)
134-
})
135-
t.Run("v1SmbTests", func(t *testing.T) {
136-
v1SmbTests(t)
137-
})
138-
}

pkg/smb/api/api.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/kubernetes-csi/csi-proxy/pkg/utils"
8+
)
9+
10+
type API interface {
11+
IsSmbMapped(remotePath string) (bool, error)
12+
NewSmbLink(remotePath, localPath string) error
13+
NewSmbGlobalMapping(remotePath, username, password string) error
14+
RemoveSmbGlobalMapping(remotePath string) error
15+
}
16+
17+
type smbAPI struct{}
18+
19+
var _ API = &smbAPI{}
20+
21+
func New() API {
22+
return smbAPI{}
23+
}
24+
25+
func (smbAPI) IsSmbMapped(remotePath string) (bool, error) {
26+
cmdLine := `$(Get-SmbGlobalMapping -RemotePath $Env:smbremotepath -ErrorAction Stop).Status `
27+
cmdEnv := fmt.Sprintf("smbremotepath=%s", remotePath)
28+
out, err := utils.RunPowershellCmd(cmdLine, cmdEnv)
29+
if err != nil {
30+
return false, fmt.Errorf("error checking smb mapping. cmd %s, output: %s, err: %v", remotePath, string(out), err)
31+
}
32+
33+
if len(out) == 0 || !strings.EqualFold(strings.TrimSpace(string(out)), "OK") {
34+
return false, nil
35+
}
36+
return true, nil
37+
}
38+
39+
// NewSmbLink - creates a directory symbolic link to the remote share.
40+
// The os.Symlink was having issue for cases where the destination was an SMB share - the container
41+
// runtime would complain stating "Access Denied". Because of this, we had to perform
42+
// this operation with powershell commandlet creating an directory softlink.
43+
// Since os.Symlink is currently being used in working code paths, no attempt is made in
44+
// alpha to merge the paths.
45+
// TODO (for beta release): Merge the link paths - os.Symlink and Powershell link path.
46+
func (smbAPI) NewSmbLink(remotePath, localPath string) error {
47+
if !strings.HasSuffix(remotePath, "\\") {
48+
// Golang has issues resolving paths mapped to file shares if they do not end in a trailing \
49+
// so add one if needed.
50+
remotePath = remotePath + "\\"
51+
}
52+
53+
cmdLine := `New-Item -ItemType SymbolicLink $Env:smblocalPath -Target $Env:smbremotepath`
54+
output, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("smbremotepath=%s", remotePath), fmt.Sprintf("smblocalpath=%s", localPath))
55+
if err != nil {
56+
return fmt.Errorf("error linking %s to %s. output: %s, err: %v", remotePath, localPath, string(output), err)
57+
}
58+
59+
return nil
60+
}
61+
62+
func (smbAPI) NewSmbGlobalMapping(remotePath, username, password string) error {
63+
// use PowerShell Environment Variables to store user input string to prevent command line injection
64+
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
65+
cmdLine := fmt.Sprintf(`$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` +
66+
`;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` +
67+
`;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential -RequirePrivacy $true`)
68+
69+
if output, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("smbuser=%s", username),
70+
fmt.Sprintf("smbpassword=%s", password),
71+
fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil {
72+
return fmt.Errorf("NewSmbGlobalMapping failed. output: %q, err: %v", string(output), err)
73+
}
74+
return nil
75+
}
76+
77+
func (smbAPI) RemoveSmbGlobalMapping(remotePath string) error {
78+
cmd := `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`
79+
if output, err := utils.RunPowershellCmd(cmd, fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil {
80+
return fmt.Errorf("UnmountSmbShare failed. output: %q, err: %v", string(output), err)
81+
}
82+
return nil
83+
}

pkg/smb/smb.go

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package smb
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
fs "github.com/kubernetes-csi/csi-proxy/pkg/filesystem"
9+
smbapi "github.com/kubernetes-csi/csi-proxy/pkg/smb/api"
10+
"k8s.io/klog/v2"
11+
)
12+
13+
type Smb struct {
14+
hostAPI smbapi.API
15+
fs fs.Interface
16+
}
17+
18+
type Interface interface {
19+
NewSmbGlobalMapping(context.Context, *NewSmbGlobalMappingRequest) (*NewSmbGlobalMappingResponse, error)
20+
RemoveSmbGlobalMapping(context.Context, *RemoveSmbGlobalMappingRequest) (*RemoveSmbGlobalMappingResponse, error)
21+
}
22+
23+
// check that Smb implements the Interface
24+
var _ Interface = &Smb{}
25+
26+
func normalizeWindowsPath(path string) string {
27+
normalizedPath := strings.Replace(path, "/", "\\", -1)
28+
return normalizedPath
29+
}
30+
31+
func getRootMappingPath(path string) (string, error) {
32+
items := strings.Split(path, "\\")
33+
parts := []string{}
34+
for _, s := range items {
35+
if len(s) > 0 {
36+
parts = append(parts, s)
37+
if len(parts) == 2 {
38+
break
39+
}
40+
}
41+
}
42+
if len(parts) != 2 {
43+
klog.Errorf("remote path (%s) is invalid", path)
44+
return "", fmt.Errorf("remote path (%s) is invalid", path)
45+
}
46+
// parts[0] is a smb host name
47+
// parts[1] is a smb share name
48+
return strings.ToLower("\\\\" + parts[0] + "\\" + parts[1]), nil
49+
}
50+
51+
func New(hostAPI smbapi.API, fsClient fs.Interface) (*Smb, error) {
52+
return &Smb{
53+
hostAPI: hostAPI,
54+
fs: fsClient,
55+
}, nil
56+
}
57+
58+
func (s *Smb) NewSmbGlobalMapping(context context.Context, request *NewSmbGlobalMappingRequest) (*NewSmbGlobalMappingResponse, error) {
59+
klog.V(2).Infof("calling NewSmbGlobalMapping with remote path %q", request.RemotePath)
60+
response := &NewSmbGlobalMappingResponse{}
61+
remotePath := normalizeWindowsPath(request.RemotePath)
62+
localPath := request.LocalPath
63+
64+
if remotePath == "" {
65+
klog.Errorf("remote path is empty")
66+
return response, fmt.Errorf("remote path is empty")
67+
}
68+
69+
mappingPath, err := getRootMappingPath(remotePath)
70+
if err != nil {
71+
return response, err
72+
}
73+
74+
isMapped, err := s.hostAPI.IsSmbMapped(mappingPath)
75+
if err != nil {
76+
isMapped = false
77+
}
78+
79+
if isMapped {
80+
klog.V(4).Infof("Remote %s already mapped. Validating...", mappingPath)
81+
82+
validResp, err := s.fs.PathValid(context, &fs.PathValidRequest{Path: mappingPath})
83+
if err != nil {
84+
klog.Warningf("PathValid(%s) failed with %v, ignore error", mappingPath, err)
85+
}
86+
87+
if !validResp.Valid {
88+
klog.V(4).Infof("RemotePath %s is not valid, removing now", mappingPath)
89+
err := s.hostAPI.RemoveSmbGlobalMapping(mappingPath)
90+
if err != nil {
91+
klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", mappingPath, err)
92+
return response, err
93+
}
94+
isMapped = false
95+
} else {
96+
klog.V(4).Infof("RemotePath %s is valid", mappingPath)
97+
}
98+
}
99+
100+
if !isMapped {
101+
klog.V(4).Infof("Remote %s not mapped. Mapping now!", mappingPath)
102+
err = s.hostAPI.NewSmbGlobalMapping(mappingPath, request.Username, request.Password)
103+
if err != nil {
104+
klog.Errorf("failed NewSmbGlobalMapping %v", err)
105+
return response, err
106+
}
107+
}
108+
109+
if len(localPath) != 0 {
110+
klog.V(4).Infof("ValidatePathWindows: '%s'", localPath)
111+
err = fs.ValidatePathWindows(localPath)
112+
if err != nil {
113+
klog.Errorf("failed validate plugin path %v", err)
114+
return response, err
115+
}
116+
err = s.hostAPI.NewSmbLink(remotePath, localPath)
117+
if err != nil {
118+
klog.Errorf("failed NewSmbLink %v", err)
119+
return response, fmt.Errorf("creating link %s to %s failed with error: %v", localPath, remotePath, err)
120+
}
121+
}
122+
123+
klog.V(2).Infof("NewSmbGlobalMapping on remote path %q is completed", request.RemotePath)
124+
return response, nil
125+
}
126+
127+
func (s *Smb) RemoveSmbGlobalMapping(context context.Context, request *RemoveSmbGlobalMappingRequest) (*RemoveSmbGlobalMappingResponse, error) {
128+
klog.V(2).Infof("calling RemoveSmbGlobalMapping with remote path %q", request.RemotePath)
129+
response := &RemoveSmbGlobalMappingResponse{}
130+
remotePath := normalizeWindowsPath(request.RemotePath)
131+
132+
if remotePath == "" {
133+
klog.Errorf("remote path is empty")
134+
return response, fmt.Errorf("remote path is empty")
135+
}
136+
137+
mappingPath, err := getRootMappingPath(remotePath)
138+
if err != nil {
139+
return response, err
140+
}
141+
142+
err = s.hostAPI.RemoveSmbGlobalMapping(mappingPath)
143+
if err != nil {
144+
klog.Errorf("failed RemoveSmbGlobalMapping %v", err)
145+
return response, err
146+
}
147+
148+
klog.V(2).Infof("RemoveSmbGlobalMapping on remote path %q is completed", request.RemotePath)
149+
return response, nil
150+
}

0 commit comments

Comments
 (0)