Skip to content

Commit 53fc811

Browse files
feat: migrate smb API group to library model
1 parent 158866f commit 53fc811

File tree

6 files changed

+578
-112
lines changed

6 files changed

+578
-112
lines changed

integrationtests/smb_test.go

+63-112
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,89 @@
11
package integrationtests
22

33
import (
4+
"context"
45
"fmt"
5-
"io/ioutil"
6-
"math/rand"
76
"os"
8-
"os/exec"
9-
"strings"
10-
"time"
11-
127
"testing"
13-
)
148

15-
const letterset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
1611

17-
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
12+
fs "github.com/kubernetes-csi/csi-proxy/pkg/filesystem"
13+
fsapi "github.com/kubernetes-csi/csi-proxy/pkg/filesystem/api"
14+
"github.com/kubernetes-csi/csi-proxy/pkg/smb"
15+
smbapi "github.com/kubernetes-csi/csi-proxy/pkg/smb/api"
16+
)
1817

19-
func stringWithCharset(length int, charset string) string {
20-
b := make([]byte, length)
21-
for i := range b {
22-
b[i] = charset[seededRand.Intn(len(charset))]
23-
}
24-
return string(b)
18+
func TestSmbAPIGroup(t *testing.T) {
19+
t.Run("v1alpha1SmbTests", func(t *testing.T) {
20+
v1alpha1SmbTests(t)
21+
})
22+
t.Run("v1beta1SmbTests", func(t *testing.T) {
23+
v1beta1SmbTests(t)
24+
})
25+
t.Run("v1beta2SmbTests", func(t *testing.T) {
26+
v1beta2SmbTests(t)
27+
})
28+
t.Run("v1SmbTests", func(t *testing.T) {
29+
v1SmbTests(t)
30+
})
2531
}
2632

27-
// RandomString generates a random string with specified length
28-
func randomString(length int) string {
29-
return stringWithCharset(length, letterset)
30-
}
33+
func TestSmb(t *testing.T) {
34+
fsClient, err := fs.New(fsapi.New())
35+
require.Nil(t, err)
36+
client, err := smb.New(smbapi.New(), fsClient)
37+
require.Nil(t, err)
3138

32-
func setupUser(username, password string) error {
33-
cmdLine := fmt.Sprintf(`$PWord = ConvertTo-SecureString $Env:password -AsPlainText -Force` +
34-
`;New-Localuser -name $Env:username -accountneverexpires -password $PWord`)
35-
cmd := exec.Command("powershell", "/c", cmdLine)
36-
cmd.Env = append(os.Environ(),
37-
fmt.Sprintf("username=%s", username),
38-
fmt.Sprintf("password=%s", password))
39-
if output, err := cmd.CombinedOutput(); err != nil {
40-
return fmt.Errorf("setupUser failed: %v, output: %q", err, string(output))
41-
}
42-
return nil
43-
}
39+
username := randomString(5)
40+
password := randomString(10) + "!"
41+
sharePath := fmt.Sprintf("C:\\smbshare%s", randomString(5))
42+
smbShare := randomString(6)
4443

45-
func removeUser(t *testing.T, username string) {
46-
cmdLine := fmt.Sprintf(`Remove-Localuser -name $Env:username`)
47-
cmd := exec.Command("powershell", "/c", cmdLine)
48-
cmd.Env = append(os.Environ(),
49-
fmt.Sprintf("username=%s", username))
50-
if output, err := cmd.CombinedOutput(); err != nil {
51-
t.Fatalf("setupUser failed: %v, output: %q", err, string(output))
52-
}
53-
}
44+
localPath := fmt.Sprintf("C:\\localpath%s", randomString(5))
5445

55-
func setupSmbShare(shareName, localPath, username string) error {
56-
if err := os.MkdirAll(localPath, 0755); err != nil {
57-
return fmt.Errorf("setupSmbShare failed to create local path %q: %v", localPath, err)
46+
if err = setupUser(username, password); err != nil {
47+
t.Fatalf("TestSmbAPIGroup %v", err)
5848
}
59-
cmdLine := fmt.Sprintf(`New-SMBShare -Name $Env:sharename -Path $Env:path -fullaccess $Env:username`)
60-
cmd := exec.Command("powershell", "/c", cmdLine)
61-
cmd.Env = append(os.Environ(),
62-
fmt.Sprintf("sharename=%s", shareName),
63-
fmt.Sprintf("path=%s", localPath),
64-
fmt.Sprintf("username=%s", username))
65-
if output, err := cmd.CombinedOutput(); err != nil {
66-
return fmt.Errorf("setupSmbShare failed: %v, output: %q", err, string(output))
67-
}
68-
69-
return nil
70-
}
49+
defer removeUser(t, username)
7150

72-
func removeSmbShare(t *testing.T, shareName string) {
73-
cmdLine := fmt.Sprintf(`Remove-SMBShare -Name $Env:sharename -Force`)
74-
cmd := exec.Command("powershell", "/c", cmdLine)
75-
cmd.Env = append(os.Environ(),
76-
fmt.Sprintf("sharename=%s", shareName))
77-
if output, err := cmd.CombinedOutput(); err != nil {
78-
t.Fatalf("setupSmbShare failed: %v, output: %q", err, string(output))
51+
if err = setupSmbShare(smbShare, sharePath, username); err != nil {
52+
t.Fatalf("TestSmbAPIGroup %v", err)
7953
}
80-
return
81-
}
54+
defer removeSmbShare(t, smbShare)
8255

83-
func getSmbGlobalMapping(remotePath string) error {
84-
// use PowerShell Environment Variables to store user input string to prevent command line injection
85-
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
86-
cmdLine := fmt.Sprintf(`(Get-SmbGlobalMapping -RemotePath $Env:smbremotepath).Status`)
56+
hostname, err := os.Hostname()
57+
assert.Nil(t, err)
8758

88-
cmd := exec.Command("powershell", "/c", cmdLine)
89-
cmd.Env = append(os.Environ(),
90-
fmt.Sprintf("smbremotepath=%s", remotePath))
91-
output, err := cmd.CombinedOutput()
92-
if err != nil {
93-
return fmt.Errorf("Get-SmbGlobalMapping failed: %v, output: %q", err, string(output))
59+
username = "domain\\" + username
60+
remotePath := "\\\\" + hostname + "\\" + smbShare
61+
// simulate Mount SMB operations around staging a volume on a node
62+
mountSmbShareReq := &smb.NewSmbGlobalMappingRequest{
63+
RemotePath: remotePath,
64+
Username: username,
65+
Password: password,
9466
}
95-
if !strings.Contains(string(output), "OK") {
96-
return fmt.Errorf("Get-SmbGlobalMapping return status %q instead of OK", string(output))
97-
}
98-
return nil
99-
}
100-
101-
func writeReadFile(path string) error {
102-
fileName := path + "\\hello.txt"
103-
f, err := os.Create(fileName)
67+
_, err = client.NewSmbGlobalMapping(context.Background(), mountSmbShareReq)
10468
if err != nil {
105-
return fmt.Errorf("create file %q failed: %v", fileName, err)
106-
}
107-
defer f.Close()
108-
fileContent := "Hello World"
109-
if _, err = f.WriteString(fileContent); err != nil {
110-
return fmt.Errorf("write to file %q failed: %v", fileName, err)
69+
t.Fatalf("TestSmbAPIGroup %v", err)
11170
}
112-
if err = f.Sync(); err != nil {
113-
return fmt.Errorf("sync file %q failed: %v", fileName, err)
71+
72+
err = getSmbGlobalMapping(remotePath)
73+
assert.Nil(t, err)
74+
75+
err = writeReadFile(remotePath)
76+
assert.Nil(t, err)
77+
78+
unmountSmbShareReq := &smb.RemoveSmbGlobalMappingRequest{
79+
RemotePath: remotePath,
11480
}
115-
dat, err := ioutil.ReadFile(fileName)
81+
_, err = client.RemoveSmbGlobalMapping(context.Background(), unmountSmbShareReq)
11682
if err != nil {
117-
return fmt.Errorf("read file %q failed: %v", fileName, err)
118-
}
119-
if fileContent != string(dat) {
120-
return fmt.Errorf("read content of file %q failed: expected %q, got %q", fileName, fileContent, string(dat))
83+
t.Fatalf("TestSmbAPIGroup %v", err)
12184
}
122-
return nil
123-
}
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-
})
85+
err = getSmbGlobalMapping(remotePath)
86+
assert.NotNil(t, err)
87+
err = writeReadFile(localPath)
88+
assert.NotNil(t, err)
13889
}

integrationtests/utils.go

+110
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,113 @@ func volumeInit(volumeClient volume.Interface, t *testing.T) (*VirtualHardDisk,
346346
}
347347
return vhd, volumeID, vhdCleanup
348348
}
349+
350+
const letterset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
351+
352+
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
353+
354+
func stringWithCharset(length int, charset string) string {
355+
b := make([]byte, length)
356+
for i := range b {
357+
b[i] = charset[seededRand.Intn(len(charset))]
358+
}
359+
return string(b)
360+
}
361+
362+
// RandomString generates a random string with specified length
363+
func randomString(length int) string {
364+
return stringWithCharset(length, letterset)
365+
}
366+
367+
func setupUser(username, password string) error {
368+
cmdLine := fmt.Sprintf(`$PWord = ConvertTo-SecureString $Env:password -AsPlainText -Force` +
369+
`;New-Localuser -name $Env:username -accountneverexpires -password $PWord`)
370+
cmd := exec.Command("powershell", "/c", cmdLine)
371+
cmd.Env = append(os.Environ(),
372+
fmt.Sprintf("username=%s", username),
373+
fmt.Sprintf("password=%s", password))
374+
if output, err := cmd.CombinedOutput(); err != nil {
375+
return fmt.Errorf("setupUser failed: %v, output: %q", err, string(output))
376+
}
377+
return nil
378+
}
379+
380+
func removeUser(t *testing.T, username string) {
381+
cmdLine := fmt.Sprintf(`Remove-Localuser -name $Env:username`)
382+
cmd := exec.Command("powershell", "/c", cmdLine)
383+
cmd.Env = append(os.Environ(),
384+
fmt.Sprintf("username=%s", username))
385+
if output, err := cmd.CombinedOutput(); err != nil {
386+
t.Fatalf("setupUser failed: %v, output: %q", err, string(output))
387+
}
388+
}
389+
390+
func setupSmbShare(shareName, localPath, username string) error {
391+
if err := os.MkdirAll(localPath, 0755); err != nil {
392+
return fmt.Errorf("setupSmbShare failed to create local path %q: %v", localPath, err)
393+
}
394+
cmdLine := fmt.Sprintf(`New-SMBShare -Name $Env:sharename -Path $Env:path -fullaccess $Env:username`)
395+
cmd := exec.Command("powershell", "/c", cmdLine)
396+
cmd.Env = append(os.Environ(),
397+
fmt.Sprintf("sharename=%s", shareName),
398+
fmt.Sprintf("path=%s", localPath),
399+
fmt.Sprintf("username=%s", username))
400+
if output, err := cmd.CombinedOutput(); err != nil {
401+
return fmt.Errorf("setupSmbShare failed: %v, output: %q", err, string(output))
402+
}
403+
404+
return nil
405+
}
406+
407+
func removeSmbShare(t *testing.T, shareName string) {
408+
cmdLine := fmt.Sprintf(`Remove-SMBShare -Name $Env:sharename -Force`)
409+
cmd := exec.Command("powershell", "/c", cmdLine)
410+
cmd.Env = append(os.Environ(),
411+
fmt.Sprintf("sharename=%s", shareName))
412+
if output, err := cmd.CombinedOutput(); err != nil {
413+
t.Fatalf("setupSmbShare failed: %v, output: %q", err, string(output))
414+
}
415+
return
416+
}
417+
418+
func getSmbGlobalMapping(remotePath string) error {
419+
// use PowerShell Environment Variables to store user input string to prevent command line injection
420+
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
421+
cmdLine := fmt.Sprintf(`(Get-SmbGlobalMapping -RemotePath $Env:smbremotepath).Status`)
422+
423+
cmd := exec.Command("powershell", "/c", cmdLine)
424+
cmd.Env = append(os.Environ(),
425+
fmt.Sprintf("smbremotepath=%s", remotePath))
426+
output, err := cmd.CombinedOutput()
427+
if err != nil {
428+
return fmt.Errorf("Get-SmbGlobalMapping failed: %v, output: %q", err, string(output))
429+
}
430+
if !strings.Contains(string(output), "OK") {
431+
return fmt.Errorf("Get-SmbGlobalMapping return status %q instead of OK", string(output))
432+
}
433+
return nil
434+
}
435+
436+
func writeReadFile(path string) error {
437+
fileName := path + "\\hello.txt"
438+
f, err := os.Create(fileName)
439+
if err != nil {
440+
return fmt.Errorf("create file %q failed: %v", fileName, err)
441+
}
442+
defer f.Close()
443+
fileContent := "Hello World"
444+
if _, err = f.WriteString(fileContent); err != nil {
445+
return fmt.Errorf("write to file %q failed: %v", fileName, err)
446+
}
447+
if err = f.Sync(); err != nil {
448+
return fmt.Errorf("sync file %q failed: %v", fileName, err)
449+
}
450+
dat, err := ioutil.ReadFile(fileName)
451+
if err != nil {
452+
return fmt.Errorf("read file %q failed: %v", fileName, err)
453+
}
454+
if fileContent != string(dat) {
455+
return fmt.Errorf("read content of file %q failed: expected %q, got %q", fileName, fileContent, string(dat))
456+
}
457+
return nil
458+
}

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+
}

0 commit comments

Comments
 (0)