Skip to content

Commit c0f1eb7

Browse files
committed
Add integration tests for GetClosestVolumeIDFromTargetPath
1 parent 715e202 commit c0f1eb7

File tree

3 files changed

+217
-33
lines changed

3 files changed

+217
-33
lines changed

integrationtests/utils.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,16 @@ type VirtualHardDisk struct {
201201
InitialSize int64
202202
}
203203

204-
func diskInit(t *testing.T) (*VirtualHardDisk, func()) {
204+
func getTestPluginPath() (string, int) {
205205
s1 := rand.NewSource(time.Now().UTC().UnixNano())
206206
r1 := rand.New(s1)
207207

208-
testId := r1.Intn(10000000)
209-
testPluginPath := fmt.Sprintf("C:\\var\\lib\\kubelet\\plugins\\testplugin-%d.csi.io\\", testId)
208+
testId := r1.Intn(1000000)
209+
return fmt.Sprintf("C:\\var\\lib\\kubelet\\plugins\\testplugin-%d.csi.io\\", testId), testId
210+
}
211+
212+
func diskInit(t *testing.T) (*VirtualHardDisk, func()) {
213+
testPluginPath, testId := getTestPluginPath()
210214
mountPath := fmt.Sprintf("%smount-%d", testPluginPath, testId)
211215
vhdxPath := fmt.Sprintf("%sdisk-%d.vhdx", testPluginPath, testId)
212216

@@ -240,7 +244,8 @@ func diskInit(t *testing.T) (*VirtualHardDisk, func()) {
240244
if diskNumUnparsed, err = runPowershellCmd(t, cmd); err != nil {
241245
t.Fatalf("Error: %v. Command: %s", err, cmd)
242246
}
243-
if diskNum, err = strconv.ParseUint(strings.TrimRight(diskNumUnparsed, "\r\n"), 10, 32); err != nil {
247+
diskNumUnparsed = strings.TrimSpace(diskNumUnparsed)
248+
if diskNum, err = strconv.ParseUint(diskNumUnparsed, 10, 32); err != nil {
244249
t.Fatalf("Error: %v", err)
245250
}
246251

integrationtests/volume_v2alpha1_test.go

Lines changed: 170 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package integrationtests
33
import (
44
"context"
55
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
610
"testing"
711

812
diskv1 "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1"
@@ -11,23 +15,10 @@ import (
1115
v2alpha1client "github.com/kubernetes-csi/csi-proxy/client/groups/volume/v2alpha1"
1216
)
1317

14-
func v2alpha1VolumeTests(t *testing.T) {
15-
var volumeClient *v2alpha1client.Client
16-
var diskClient *diskv1client.Client
17-
var err error
18-
19-
if volumeClient, err = v2alpha1client.NewClient(); err != nil {
20-
t.Fatalf("Client new error: %v", err)
21-
}
22-
defer volumeClient.Close()
23-
24-
if diskClient, err = diskv1client.NewClient(); err != nil {
25-
t.Fatalf("DiskClient new error: %v", err)
26-
}
27-
defer diskClient.Close()
28-
18+
// volumeInit initializes a volume, it creates a VHD, initializes it,
19+
// creates a partition with the max size and formats the volume corresponding to that partition
20+
func volumeInit(volumeClient *v2alpha1client.Client, t *testing.T) (*VirtualHardDisk, string, func()) {
2921
vhd, vhdCleanup := diskInit(t)
30-
defer vhdCleanup()
3122

3223
listRequest := &v2alpha1.ListVolumesOnDiskRequest{
3324
DiskNumber: vhd.DiskNumber,
@@ -42,6 +33,7 @@ func v2alpha1VolumeTests(t *testing.T) {
4233
t.Fatalf("Number of volumes not equal to 1: %d", volumeIDsLen)
4334
}
4435
volumeID := listResponse.VolumeIds[0]
36+
t.Logf("VolumeId %v", volumeID)
4537

4638
isVolumeFormattedRequest := &v2alpha1.IsVolumeFormattedRequest{
4739
VolumeId: volumeID,
@@ -69,8 +61,146 @@ func v2alpha1VolumeTests(t *testing.T) {
6961
if !isVolumeFormattedResponse.Formatted {
7062
t.Fatal("Volume should be formatted. Unexpected !!")
7163
}
64+
return vhd, volumeID, vhdCleanup
65+
}
66+
67+
func v2alpha1GetClosestVolumeFromTargetPathTests(diskClient *diskv1client.Client, volumeClient *v2alpha1client.Client, t *testing.T) {
68+
t.Run("DriveLetterVolume", func(t *testing.T) {
69+
vhd, _, vhdCleanup := volumeInit(volumeClient, t)
70+
defer vhdCleanup()
71+
72+
// vhd.Mount dir exists, because there are no volumes above it should return the C:\ volume
73+
var request *v2alpha1.GetClosestVolumeIDFromTargetPathRequest
74+
var response *v2alpha1.GetClosestVolumeIDFromTargetPathResponse
75+
request = &v2alpha1.GetClosestVolumeIDFromTargetPathRequest{
76+
TargetPath: vhd.Mount,
77+
}
78+
response, err := volumeClient.GetClosestVolumeIDFromTargetPath(context.TODO(), request)
79+
if err != nil {
80+
t.Fatalf("GetClosestVolumeIDFromTargetPath request error, err=%v", err)
81+
}
82+
83+
// the C drive volume
84+
cmd := exec.Command("powershell", "/c", `(Get-Partition -DriveLetter C | Get-Volume).UniqueId`)
85+
targetb, err := cmd.Output()
86+
if err != nil {
87+
t.Fatalf("Failed to get the C: drive volume")
88+
}
89+
cDriveVolume := strings.TrimSpace(string(targetb))
90+
91+
if response.VolumeId != cDriveVolume {
92+
t.Fatalf("The volume from GetClosestVolumeIDFromTargetPath doesn't match the C: drive volume")
93+
}
94+
})
95+
t.Run("AncestorVolumeFromNestedDirectory", func(t *testing.T) {
96+
var err error
97+
vhd, volumeID, vhdCleanup := volumeInit(volumeClient, t)
98+
defer vhdCleanup()
99+
100+
// Mount the volume
101+
mountVolumeRequest := &v2alpha1.MountVolumeRequest{
102+
VolumeId: volumeID,
103+
TargetPath: vhd.Mount,
104+
}
105+
_, err = volumeClient.MountVolume(context.TODO(), mountVolumeRequest)
106+
if err != nil {
107+
t.Fatalf("Volume id %s mount to path %s failed. Error: %v", volumeID, vhd.Mount, err)
108+
}
109+
110+
// Unmount the volume
111+
defer func() {
112+
unmountVolumeRequest := &v2alpha1.UnmountVolumeRequest{
113+
VolumeId: volumeID,
114+
TargetPath: vhd.Mount,
115+
}
116+
_, err = volumeClient.UnmountVolume(context.TODO(), unmountVolumeRequest)
117+
if err != nil {
118+
t.Fatalf("Volume id %s mount to path %s failed. Error: %v", volumeID, vhd.Mount, err)
119+
}
120+
}()
121+
122+
nestedDirectory := filepath.Join(vhd.Mount, "foo/bar")
123+
err = os.MkdirAll(nestedDirectory, os.ModeDir)
124+
if err != nil {
125+
t.Fatalf("Failed to create directory=%s", nestedDirectory)
126+
}
127+
128+
// the volume returned should be the VHD volume
129+
var request *v2alpha1.GetClosestVolumeIDFromTargetPathRequest
130+
var response *v2alpha1.GetClosestVolumeIDFromTargetPathResponse
131+
request = &v2alpha1.GetClosestVolumeIDFromTargetPathRequest{
132+
TargetPath: nestedDirectory,
133+
}
134+
response, err = volumeClient.GetClosestVolumeIDFromTargetPath(context.TODO(), request)
135+
if err != nil {
136+
t.Fatalf("GetClosestVolumeIDFromTargetPath request error, err=%v", err)
137+
}
138+
139+
if response.VolumeId != volumeID {
140+
t.Fatalf("The volume from GetClosestVolumeIDFromTargetPath doesn't match the VHD volume=%s", volumeID)
141+
}
142+
})
143+
144+
t.Run("SymlinkToVolume", func(t *testing.T) {
145+
var err error
146+
vhd, volumeID, vhdCleanup := volumeInit(volumeClient, t)
147+
defer vhdCleanup()
148+
149+
// Mount the volume
150+
mountVolumeRequest := &v2alpha1.MountVolumeRequest{
151+
VolumeId: volumeID,
152+
TargetPath: vhd.Mount,
153+
}
154+
_, err = volumeClient.MountVolume(context.TODO(), mountVolumeRequest)
155+
if err != nil {
156+
t.Fatalf("Volume id %s mount to path %s failed. Error: %v", volumeID, vhd.Mount, err)
157+
}
158+
159+
// Unmount the volume
160+
defer func() {
161+
unmountVolumeRequest := &v2alpha1.UnmountVolumeRequest{
162+
VolumeId: volumeID,
163+
TargetPath: vhd.Mount,
164+
}
165+
_, err = volumeClient.UnmountVolume(context.TODO(), unmountVolumeRequest)
166+
if err != nil {
167+
t.Fatalf("Volume id %s mount to path %s failed. Error: %v", volumeID, vhd.Mount, err)
168+
}
169+
}()
170+
171+
testPluginPath, _ := getTestPluginPath()
172+
err = os.MkdirAll(testPluginPath, os.ModeDir)
173+
if err != nil {
174+
t.Fatalf("Failed to create directory=%s", testPluginPath)
175+
}
176+
177+
sourceSymlink := filepath.Join(testPluginPath, "source")
178+
err = os.Symlink(vhd.Mount, sourceSymlink)
179+
if err != nil {
180+
t.Fatalf("Failed to create the symlink=%s", sourceSymlink)
181+
}
182+
183+
// the volume returned should be the VHD volume
184+
var request *v2alpha1.GetClosestVolumeIDFromTargetPathRequest
185+
var response *v2alpha1.GetClosestVolumeIDFromTargetPathResponse
186+
request = &v2alpha1.GetClosestVolumeIDFromTargetPathRequest{
187+
TargetPath: sourceSymlink,
188+
}
189+
response, err = volumeClient.GetClosestVolumeIDFromTargetPath(context.TODO(), request)
190+
if err != nil {
191+
t.Fatalf("GetClosestVolumeIDFromTargetPath request error, err=%v", err)
192+
}
193+
194+
if response.VolumeId != volumeID {
195+
t.Fatalf("The volume from GetClosestVolumeIDFromTargetPath doesn't match the VHD volume=%s", volumeID)
196+
}
197+
})
198+
}
199+
200+
func v2alpha1MountVolumeTests(diskClient *diskv1client.Client, volumeClient *v2alpha1client.Client, t *testing.T) {
201+
vhd, volumeID, vhdCleanup := volumeInit(volumeClient, t)
202+
defer vhdCleanup()
72203

73-
t.Logf("VolumeId %v", volumeID)
74204
volumeStatsRequest := &v2alpha1.GetVolumeStatsRequest{
75205
VolumeId: volumeID,
76206
}
@@ -167,3 +297,26 @@ func v2alpha1VolumeTests(t *testing.T) {
167297
t.Fatalf("Volume id %s mount to path %s failed. Error: %v", volumeID, vhd.Mount, err)
168298
}
169299
}
300+
301+
func v2alpha1VolumeTests(t *testing.T) {
302+
var volumeClient *v2alpha1client.Client
303+
var diskClient *diskv1client.Client
304+
var err error
305+
306+
if volumeClient, err = v2alpha1client.NewClient(); err != nil {
307+
t.Fatalf("Client new error: %v", err)
308+
}
309+
defer volumeClient.Close()
310+
311+
if diskClient, err = diskv1client.NewClient(); err != nil {
312+
t.Fatalf("DiskClient new error: %v", err)
313+
}
314+
defer diskClient.Close()
315+
316+
t.Run("MountVolume", func(t *testing.T) {
317+
v2alpha1MountVolumeTests(diskClient, volumeClient, t)
318+
})
319+
t.Run("GetClosestVolumeFromTargetPath", func(t *testing.T) {
320+
v2alpha1GetClosestVolumeFromTargetPathTests(diskClient, volumeClient, t)
321+
})
322+
}

pkg/os/volume/api.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package volume
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"os"
@@ -46,8 +47,14 @@ type VolumeAPI struct{}
4647
var _ API = &VolumeAPI{}
4748

4849
var (
49-
// VolumeRegexp matches a Volume
50-
VolumeRegexp = regexp.MustCompile(`^Volume\{[\w-]*\}`)
50+
// VolumeRegexp matches a Windows Volume
51+
// example: Volume{452e318a-5cde-421e-9831-b9853c521012}
52+
//
53+
// The field UniqueId has an additional prefix which is NOT included in the regex
54+
// however the regex can match UniqueId too
55+
// PS C:\disks> (Get-Disk -Number 1 | Get-Partition | Get-Volume).UniqueId
56+
// \\?\Volume{452e318a-5cde-421e-9831-b9853c521012}\
57+
VolumeRegexp = regexp.MustCompile(`Volume\{[\w-]*\}`)
5158
)
5259

5360
// New - Construct a new Volume API Implementation.
@@ -272,9 +279,7 @@ func getTarget(mount string) (string, error) {
272279
return getTarget(volumeString)
273280
}
274281

275-
volumeString = "\\\\?\\" + volumeString
276-
277-
return volumeString, nil
282+
return ensureVolumePrefix(volumeString), nil
278283
}
279284

280285
// GetVolumeIDFromTargetPath returns the volume id of a given target path.
@@ -294,8 +299,16 @@ func (VolumeAPI) GetClosestVolumeIDFromTargetPath(targetPath string) (string, er
294299
func findClosestVolume(path string) (string, error) {
295300
candidatePath := path
296301

297-
// run in a bounded loop to avoid doing an infinite loop
302+
// Run in a bounded loop to avoid doing an infinite loop
298303
// while trying to follow symlinks
304+
//
305+
// The maximum path length in Windows is 260, it could be possible to end
306+
// up in a sceneario where we do more than 256 iterations (e.g. by following symlinks from
307+
// a place high in the hierarchy to a nested sibling location many times)
308+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#:~:text=In%20editions%20of%20Windows%20before,required%20to%20remove%20the%20limit.
309+
//
310+
// The number of iterations is 256, which is similar to the number of iterations in filepath-securejoin
311+
// https://github.com/cyphar/filepath-securejoin/blob/64536a8a66ae59588c981e2199f1dcf410508e07/join.go#L51
299312
for i := 0; i < 256; i += 1 {
300313
fi, err := os.Lstat(candidatePath)
301314
if err != nil {
@@ -308,11 +321,10 @@ func findClosestVolume(path string) (string, error) {
308321
if err != nil {
309322
return "", err
310323
}
311-
// if it has the form Volume{volumeid}\ then it's a volume
324+
// if it has the form Volume{volumeid} then it's a volume
312325
if VolumeRegexp.Match([]byte(target)) {
313326
// symlinks that are pointing to Volumes don't have this prefix
314-
target = "\\\\?\\" + target
315-
return target, nil
327+
return ensureVolumePrefix(target), nil
316328
}
317329
// otherwise follow the symlink
318330
candidatePath = target
@@ -337,15 +349,29 @@ func findClosestVolume(path string) (string, error) {
337349
return "", fmt.Errorf("Failed to find the closest volume for path=%s", path)
338350
}
339351

352+
// ensureVolumePrefix makes sure that the volume has the Volume prefix
353+
func ensureVolumePrefix(volume string) string {
354+
prefix := "\\\\?\\"
355+
if !strings.HasPrefix(volume, prefix) {
356+
volume = prefix + volume
357+
}
358+
return volume
359+
}
360+
340361
// dereferenceSymlink dereferences the symlink `path` and returns the stdout.
341362
func dereferenceSymlink(path string) (string, error) {
342363
cmd := exec.Command("powershell", "/c", fmt.Sprintf(`(Get-Item -Path %s).Target`, path))
343364
klog.V(8).Infof("About to execute: %q", cmd.String())
344-
targetb, err := cmd.Output()
345-
if err != nil {
365+
var outbuf, errbuf bytes.Buffer
366+
cmd.Stderr = &errbuf
367+
cmd.Stdout = &outbuf
368+
if err := cmd.Run(); err != nil {
346369
return "", err
347370
}
348-
output := strings.TrimSpace(string(targetb))
371+
if len(errbuf.String()) != 0 {
372+
return "", fmt.Errorf("Unexpected stderr output in command=%v stdeerr=%v", cmd.String(), errbuf.String())
373+
}
374+
output := strings.TrimSpace(outbuf.String())
349375
klog.V(8).Infof("Stdout: %s", output)
350376
return output, nil
351377
}

0 commit comments

Comments
 (0)