Skip to content

Commit

Permalink
Merge pull request #245 from alexander-ding/enh/migrate-v2
Browse files Browse the repository at this point in the history
feat: migrate smb API group to library model
  • Loading branch information
k8s-ci-robot authored Oct 13, 2022
2 parents 90ea3ad + 4f70284 commit 02cf55d
Show file tree
Hide file tree
Showing 5 changed files with 487 additions and 16 deletions.
98 changes: 82 additions & 16 deletions integrationtests/smb_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,98 @@
package integrationtests

import (
"context"
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"strings"
"testing"
"time"

"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

fs "github.com/kubernetes-csi/csi-proxy/pkg/filesystem"
fsapi "github.com/kubernetes-csi/csi-proxy/pkg/filesystem/api"
"github.com/kubernetes-csi/csi-proxy/pkg/smb"
smbapi "github.com/kubernetes-csi/csi-proxy/pkg/smb/api"
)

func TestSmbAPIGroup(t *testing.T) {
t.Run("v1alpha1SmbTests", func(t *testing.T) {
v1alpha1SmbTests(t)
})
t.Run("v1beta1SmbTests", func(t *testing.T) {
v1beta1SmbTests(t)
})
t.Run("v1beta2SmbTests", func(t *testing.T) {
v1beta2SmbTests(t)
})
t.Run("v1SmbTests", func(t *testing.T) {
v1SmbTests(t)
})
}

func TestSmb(t *testing.T) {
fsClient, err := fs.New(fsapi.New())
require.Nil(t, err)
client, err := smb.New(smbapi.New(), fsClient)
require.Nil(t, err)

username := randomString(5)
password := randomString(10) + "!"
sharePath := fmt.Sprintf("C:\\smbshare%s", randomString(5))
smbShare := randomString(6)

localPath := fmt.Sprintf("C:\\localpath%s", randomString(5))

if err = setupUser(username, password); err != nil {
t.Fatalf("TestSmbAPIGroup %v", err)
}
defer removeUser(t, username)

if err = setupSmbShare(smbShare, sharePath, username); err != nil {
t.Fatalf("TestSmbAPIGroup %v", err)
}
defer removeSmbShare(t, smbShare)

hostname, err := os.Hostname()
assert.Nil(t, err)

username = "domain\\" + username
remotePath := "\\\\" + hostname + "\\" + smbShare
// simulate Mount SMB operations around staging a volume on a node
mountSmbShareReq := &smb.NewSmbGlobalMappingRequest{
RemotePath: remotePath,
Username: username,
Password: password,
}
_, err = client.NewSmbGlobalMapping(context.Background(), mountSmbShareReq)
if err != nil {
t.Fatalf("TestSmbAPIGroup %v", err)
}

err = getSmbGlobalMapping(remotePath)
assert.Nil(t, err)

err = writeReadFile(remotePath)
assert.Nil(t, err)

unmountSmbShareReq := &smb.RemoveSmbGlobalMappingRequest{
RemotePath: remotePath,
}
_, err = client.RemoveSmbGlobalMapping(context.Background(), unmountSmbShareReq)
if err != nil {
t.Fatalf("TestSmbAPIGroup %v", err)
}
err = getSmbGlobalMapping(remotePath)
assert.NotNil(t, err)
err = writeReadFile(localPath)
assert.NotNil(t, err)
}

const letterset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
Expand Down Expand Up @@ -121,18 +202,3 @@ func writeReadFile(path string) error {
}
return nil
}

func TestSmbAPIGroup(t *testing.T) {
t.Run("v1alpha1SmbTests", func(t *testing.T) {
v1alpha1SmbTests(t)
})
t.Run("v1beta1SmbTests", func(t *testing.T) {
v1beta1SmbTests(t)
})
t.Run("v1beta2SmbTests", func(t *testing.T) {
v1beta2SmbTests(t)
})
t.Run("v1SmbTests", func(t *testing.T) {
v1SmbTests(t)
})
}
83 changes: 83 additions & 0 deletions pkg/smb/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package api

import (
"fmt"
"strings"

"github.com/kubernetes-csi/csi-proxy/pkg/utils"
)

type API interface {
IsSmbMapped(remotePath string) (bool, error)
NewSmbLink(remotePath, localPath string) error
NewSmbGlobalMapping(remotePath, username, password string) error
RemoveSmbGlobalMapping(remotePath string) error
}

type smbAPI struct{}

var _ API = &smbAPI{}

func New() API {
return smbAPI{}
}

func (smbAPI) IsSmbMapped(remotePath string) (bool, error) {
cmdLine := `$(Get-SmbGlobalMapping -RemotePath $Env:smbremotepath -ErrorAction Stop).Status `
cmdEnv := fmt.Sprintf("smbremotepath=%s", remotePath)
out, err := utils.RunPowershellCmd(cmdLine, cmdEnv)
if err != nil {
return false, fmt.Errorf("error checking smb mapping. cmd %s, output: %s, err: %v", remotePath, string(out), err)
}

if len(out) == 0 || !strings.EqualFold(strings.TrimSpace(string(out)), "OK") {
return false, nil
}
return true, nil
}

// NewSmbLink - creates a directory symbolic link to the remote share.
// The os.Symlink was having issue for cases where the destination was an SMB share - the container
// runtime would complain stating "Access Denied". Because of this, we had to perform
// this operation with powershell commandlet creating an directory softlink.
// Since os.Symlink is currently being used in working code paths, no attempt is made in
// alpha to merge the paths.
// TODO (for beta release): Merge the link paths - os.Symlink and Powershell link path.
func (smbAPI) NewSmbLink(remotePath, localPath string) error {
if !strings.HasSuffix(remotePath, "\\") {
// Golang has issues resolving paths mapped to file shares if they do not end in a trailing \
// so add one if needed.
remotePath = remotePath + "\\"
}

cmdLine := `New-Item -ItemType SymbolicLink $Env:smblocalPath -Target $Env:smbremotepath`
output, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("smbremotepath=%s", remotePath), fmt.Sprintf("smblocalpath=%s", localPath))
if err != nil {
return fmt.Errorf("error linking %s to %s. output: %s, err: %v", remotePath, localPath, string(output), err)
}

return nil
}

func (smbAPI) NewSmbGlobalMapping(remotePath, username, password string) error {
// use PowerShell Environment Variables to store user input string to prevent command line injection
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
cmdLine := fmt.Sprintf(`$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` +
`;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` +
`;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential -RequirePrivacy $true`)

if output, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("smbuser=%s", username),
fmt.Sprintf("smbpassword=%s", password),
fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil {
return fmt.Errorf("NewSmbGlobalMapping failed. output: %q, err: %v", string(output), err)
}
return nil
}

func (smbAPI) RemoveSmbGlobalMapping(remotePath string) error {
cmd := `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`
if output, err := utils.RunPowershellCmd(cmd, fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil {
return fmt.Errorf("UnmountSmbShare failed. output: %q, err: %v", string(output), err)
}
return nil
}
150 changes: 150 additions & 0 deletions pkg/smb/smb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package smb

import (
"context"
"fmt"
"strings"

fs "github.com/kubernetes-csi/csi-proxy/pkg/filesystem"
smbapi "github.com/kubernetes-csi/csi-proxy/pkg/smb/api"
"k8s.io/klog/v2"
)

type Smb struct {
hostAPI smbapi.API
fs fs.Interface
}

type Interface interface {
NewSmbGlobalMapping(context.Context, *NewSmbGlobalMappingRequest) (*NewSmbGlobalMappingResponse, error)
RemoveSmbGlobalMapping(context.Context, *RemoveSmbGlobalMappingRequest) (*RemoveSmbGlobalMappingResponse, error)
}

// check that Smb implements the Interface
var _ Interface = &Smb{}

func normalizeWindowsPath(path string) string {
normalizedPath := strings.Replace(path, "/", "\\", -1)
return normalizedPath
}

func getRootMappingPath(path string) (string, error) {
items := strings.Split(path, "\\")
parts := []string{}
for _, s := range items {
if len(s) > 0 {
parts = append(parts, s)
if len(parts) == 2 {
break
}
}
}
if len(parts) != 2 {
klog.Errorf("remote path (%s) is invalid", path)
return "", fmt.Errorf("remote path (%s) is invalid", path)
}
// parts[0] is a smb host name
// parts[1] is a smb share name
return strings.ToLower("\\\\" + parts[0] + "\\" + parts[1]), nil
}

func New(hostAPI smbapi.API, fsClient fs.Interface) (*Smb, error) {
return &Smb{
hostAPI: hostAPI,
fs: fsClient,
}, nil
}

func (s *Smb) NewSmbGlobalMapping(context context.Context, request *NewSmbGlobalMappingRequest) (*NewSmbGlobalMappingResponse, error) {
klog.V(2).Infof("calling NewSmbGlobalMapping with remote path %q", request.RemotePath)
response := &NewSmbGlobalMappingResponse{}
remotePath := normalizeWindowsPath(request.RemotePath)
localPath := request.LocalPath

if remotePath == "" {
klog.Errorf("remote path is empty")
return response, fmt.Errorf("remote path is empty")
}

mappingPath, err := getRootMappingPath(remotePath)
if err != nil {
return response, err
}

isMapped, err := s.hostAPI.IsSmbMapped(mappingPath)
if err != nil {
isMapped = false
}

if isMapped {
klog.V(4).Infof("Remote %s already mapped. Validating...", mappingPath)

validResp, err := s.fs.PathValid(context, &fs.PathValidRequest{Path: mappingPath})
if err != nil {
klog.Warningf("PathValid(%s) failed with %v, ignore error", mappingPath, err)
}

if !validResp.Valid {
klog.V(4).Infof("RemotePath %s is not valid, removing now", mappingPath)
err := s.hostAPI.RemoveSmbGlobalMapping(mappingPath)
if err != nil {
klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", mappingPath, err)
return response, err
}
isMapped = false
} else {
klog.V(4).Infof("RemotePath %s is valid", mappingPath)
}
}

if !isMapped {
klog.V(4).Infof("Remote %s not mapped. Mapping now!", mappingPath)
err = s.hostAPI.NewSmbGlobalMapping(mappingPath, request.Username, request.Password)
if err != nil {
klog.Errorf("failed NewSmbGlobalMapping %v", err)
return response, err
}
}

if len(localPath) != 0 {
klog.V(4).Infof("ValidatePathWindows: '%s'", localPath)
err = fs.ValidatePathWindows(localPath)
if err != nil {
klog.Errorf("failed validate plugin path %v", err)
return response, err
}
err = s.hostAPI.NewSmbLink(remotePath, localPath)
if err != nil {
klog.Errorf("failed NewSmbLink %v", err)
return response, fmt.Errorf("creating link %s to %s failed with error: %v", localPath, remotePath, err)
}
}

klog.V(2).Infof("NewSmbGlobalMapping on remote path %q is completed", request.RemotePath)
return response, nil
}

func (s *Smb) RemoveSmbGlobalMapping(context context.Context, request *RemoveSmbGlobalMappingRequest) (*RemoveSmbGlobalMappingResponse, error) {
klog.V(2).Infof("calling RemoveSmbGlobalMapping with remote path %q", request.RemotePath)
response := &RemoveSmbGlobalMappingResponse{}
remotePath := normalizeWindowsPath(request.RemotePath)

if remotePath == "" {
klog.Errorf("remote path is empty")
return response, fmt.Errorf("remote path is empty")
}

mappingPath, err := getRootMappingPath(remotePath)
if err != nil {
return response, err
}

err = s.hostAPI.RemoveSmbGlobalMapping(mappingPath)
if err != nil {
klog.Errorf("failed RemoveSmbGlobalMapping %v", err)
return response, err
}

klog.V(2).Infof("RemoveSmbGlobalMapping on remote path %q is completed", request.RemotePath)
return response, nil
}
Loading

0 comments on commit 02cf55d

Please sign in to comment.