Skip to content

Commit ba25c69

Browse files
author
Alexandr Stefurishin
committed
add tests
Signed-off-by: Alexandr Stefurishin <[email protected]>
1 parent 61a51e8 commit ba25c69

File tree

9 files changed

+400
-20
lines changed

9 files changed

+400
-20
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ require (
9696
go.uber.org/zap v1.19.0 // indirect
9797
golang.org/x/crypto v0.28.0 // indirect
9898
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
99+
golang.org/x/mod v0.22.0
99100
golang.org/x/oauth2 v0.22.0 // indirect
100101
golang.org/x/sync v0.8.0 // indirect
101102
golang.org/x/sys v0.26.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
451451
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
452452
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
453453
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
454+
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
455+
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
454456
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
455457
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
456458
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

pkg/nfs/controllerserver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
401401
dstPath := filepath.Join(snapInternalVolPath, snapshot.archiveName())
402402

403403
klog.V(2).Infof("tar %v -> %v", srcPath, dstPath)
404-
err = tarPack(dstPath, srcPath, true)
404+
err = TarPack(srcPath, dstPath, true)
405405
if err != nil {
406406
return nil, status.Errorf(codes.Internal, "failed to create archive for snapshot: %v", err)
407407
}
@@ -557,7 +557,7 @@ func (cs *ControllerServer) copyFromSnapshot(ctx context.Context, req *csi.Creat
557557
dstPath := getInternalVolumePath(cs.Driver.workingMountDir, dstVol)
558558
klog.V(2).Infof("copy volume from snapshot %v -> %v", snapPath, dstPath)
559559

560-
err = tarUnpack(snapPath, dstPath, true)
560+
err = TarUnpack(snapPath, dstPath, true)
561561
if err != nil {
562562
return status.Errorf(codes.Internal, "failed to copy volume for snapshot: %v", err)
563563
}

pkg/nfs/tar.go

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,33 @@ import (
88
"io"
99
"io/fs"
1010
"os"
11+
"path"
1112
"path/filepath"
1213
"strings"
1314
)
1415

15-
func tarPack(dstFilePath, srcPath string, enableCompression bool) error {
16+
func TarPack(srcDirPath string, dstPath string, enableCompression bool) error {
1617
// normalize all paths to be absolute and clean
17-
dstFilePath, err := filepath.Abs(dstFilePath)
18+
dstPath, err := filepath.Abs(dstPath)
1819
if err != nil {
1920
return fmt.Errorf("normalizing destination path: %w", err)
2021
}
2122

22-
srcPath, err = filepath.Abs(srcPath)
23+
srcDirPath, err = filepath.Abs(srcDirPath)
2324
if err != nil {
2425
return fmt.Errorf("normalizing source path: %w", err)
2526
}
2627

27-
if strings.Index(dstFilePath, srcPath) == 0 {
28-
return fmt.Errorf("destination file %s cannot be under source directory %s", dstFilePath, srcPath)
28+
if strings.Index(path.Dir(dstPath), srcDirPath) == 0 {
29+
return fmt.Errorf("destination file %s cannot be under source directory %s", dstPath, srcDirPath)
2930
}
3031

31-
tarFile, err := os.Create(dstFilePath)
32+
tarFile, err := os.Create(dstPath)
3233
if err != nil {
3334
return fmt.Errorf("creating destination file: %w", err)
3435
}
3536
defer func() {
36-
err = errors.Join(err, closeAndWrapErr(tarFile, "closing destination file %s: %w", dstFilePath))
37+
err = errors.Join(err, closeAndWrapErr(tarFile, "closing destination file %s: %w", dstPath))
3738
}()
3839

3940
var tarDst io.Writer = tarFile
@@ -52,11 +53,13 @@ func tarPack(dstFilePath, srcPath string, enableCompression bool) error {
5253

5354
// recursively visit every file and write it
5455
if err = filepath.Walk(
55-
srcPath,
56+
srcDirPath,
5657
func(srcSubPath string, fileInfo fs.FileInfo, walkErr error) error {
57-
return tarVisitFileToPack(tarWriter, srcPath, srcSubPath, fileInfo, walkErr)
58+
return tarVisitFileToPack(tarWriter, srcDirPath, srcSubPath, fileInfo, walkErr)
5859
},
5960
); err != nil {
61+
ee := err.Error()
62+
_ = ee
6063
return fmt.Errorf("walking source directory: %w", err)
6164
}
6265

@@ -115,24 +118,24 @@ func tarVisitFileToPack(
115118
return nil
116119
}
117120

118-
func tarUnpack(archivePath, dstPath string, enableCompression bool) (err error) {
121+
func TarUnpack(srcPath, dstDirPath string, enableCompression bool) (err error) {
119122
// normalize all paths to be absolute and clean
120-
archivePath, err = filepath.Abs(archivePath)
123+
srcPath, err = filepath.Abs(srcPath)
121124
if err != nil {
122125
return fmt.Errorf("normalizing archive path: %w", err)
123126
}
124127

125-
dstPath, err = filepath.Abs(dstPath)
128+
dstDirPath, err = filepath.Abs(dstDirPath)
126129
if err != nil {
127130
return fmt.Errorf("normalizing archive destination path: %w", err)
128131
}
129132

130-
tarFile, err := os.Open(archivePath)
133+
tarFile, err := os.Open(srcPath)
131134
if err != nil {
132-
return fmt.Errorf("opening archive %s: %w", archivePath, err)
135+
return fmt.Errorf("opening archive %s: %w", srcPath, err)
133136
}
134137
defer func() {
135-
err = errors.Join(err, closeAndWrapErr(tarFile, "closing archive %s: %w", archivePath))
138+
err = errors.Join(err, closeAndWrapErr(tarFile, "closing archive %s: %w", srcPath))
136139
}()
137140

138141
var tarDst io.Reader = tarFile
@@ -146,7 +149,7 @@ func tarUnpack(archivePath, dstPath string, enableCompression bool) (err error)
146149
err = errors.Join(err, closeAndWrapErr(gzipReader, "closing gzip reader: %w"))
147150
}()
148151

149-
tarDst = tar.NewReader(gzipReader)
152+
tarDst = gzipReader
150153
}
151154

152155
tarReader := tar.NewReader(tarDst)
@@ -158,12 +161,12 @@ func tarUnpack(archivePath, dstPath string, enableCompression bool) (err error)
158161
break
159162
}
160163
if err != nil {
161-
return fmt.Errorf("reading tar header of %s: %w", archivePath, err)
164+
return fmt.Errorf("reading tar header of %s: %w", srcPath, err)
162165
}
163166

164167
fileInfo := tarHeader.FileInfo()
165168

166-
filePath := filepath.Join(dstPath, tarHeader.Name)
169+
filePath := filepath.Join(dstDirPath, tarHeader.Name)
167170

168171
fileDirPath := filePath
169172
if !fileInfo.Mode().IsDir() {

test/tar/tar_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package tar
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"maps"
7+
"math"
8+
"os"
9+
"os/exec"
10+
"path"
11+
"slices"
12+
"strings"
13+
"testing"
14+
15+
"github.com/kubernetes-csi/csi-driver-nfs/pkg/nfs"
16+
"golang.org/x/mod/sumdb/dirhash"
17+
)
18+
19+
const (
20+
code packApi = '0'
21+
cli packApi = '1'
22+
)
23+
24+
type packApi byte
25+
26+
const archiveFileExt = ".tar.gz"
27+
28+
func TestPackUnpack(t *testing.T) {
29+
inputPath := t.TempDir()
30+
generateFileSystem(t, inputPath)
31+
32+
outputPath := t.TempDir()
33+
34+
// produced file names (without extensions) have a suffix,
35+
// which determine the last operation:
36+
// "0" means that it was produced from code
37+
// "1" means that it was produced from CLI
38+
// e.g.: "testdata011.tar.gz" - was packed from code,
39+
// then unpacked from cli and packed again from cli
40+
41+
pathsBySuffix := make(map[string]string)
42+
43+
// number of pack/unpack operations
44+
opNum := 4
45+
46+
// generate all operation combinations
47+
fileNum := int(math.Pow(2, float64(opNum)))
48+
for i := 0; i < fileNum; i++ {
49+
binStr := fmt.Sprintf("%b", i)
50+
51+
// left-pad with zeroes
52+
binStr = strings.Repeat("0", opNum-len(binStr)) + binStr
53+
54+
// copy slices to satisfy type system
55+
ops := make([]packApi, opNum)
56+
for opIdx := 0; opIdx < opNum; opIdx++ {
57+
ops[opIdx] = packApi(binStr[opIdx])
58+
}
59+
60+
// produce folders and archives
61+
produce(t, pathsBySuffix, inputPath, outputPath, ops...)
62+
}
63+
64+
// byte-compare archives with the same last step
65+
paths := slices.Collect(maps.Values(pathsBySuffix))
66+
assertUnpackedFilesEqual(t, inputPath, paths)
67+
}
68+
69+
func produce(
70+
t *testing.T,
71+
results map[string]string,
72+
inputDirPath string,
73+
outputDirPath string,
74+
ops ...packApi,
75+
) {
76+
baseName := path.Base(inputDirPath)
77+
78+
for i := 0; i < len(ops); i++ {
79+
packing := i%2 == 0
80+
81+
srcPath := inputDirPath
82+
if i > 0 {
83+
prevSuffix := string(ops[:i])
84+
srcPath = path.Join(outputDirPath, baseName+prevSuffix)
85+
if !packing {
86+
srcPath += archiveFileExt
87+
}
88+
}
89+
90+
suffix := string(ops[:i+1])
91+
dstPath := path.Join(outputDirPath, baseName+suffix)
92+
if packing {
93+
dstPath += archiveFileExt
94+
}
95+
96+
if _, ok := results[suffix]; ok {
97+
continue
98+
}
99+
100+
switch {
101+
case packing && ops[i] == code:
102+
// packing from code
103+
if err := nfs.TarPack(srcPath, dstPath, true); err != nil {
104+
t.Fatalf("packing '%s' with TarPack into '%s': %v", srcPath, dstPath, err)
105+
}
106+
case packing && ops[i] == cli:
107+
// packing from CLI
108+
if out, err := exec.Command("tar", "-C", srcPath, "-czvf", dstPath, ".").CombinedOutput(); err != nil {
109+
t.Log("TAR OUTPUT:", string(out))
110+
t.Fatalf("packing '%s' with tar into '%s': %v", srcPath, dstPath, err)
111+
}
112+
case !packing && ops[i] == code:
113+
// unpacking from code
114+
if err := nfs.TarUnpack(srcPath, dstPath, true); err != nil {
115+
t.Fatalf("unpacking '%s' with TarUnpack into '%s': %v", srcPath, dstPath, err)
116+
}
117+
case !packing && ops[i] == cli:
118+
// unpacking from CLI
119+
// tar requires destination directory to exist
120+
if err := os.MkdirAll(dstPath, 0755); err != nil {
121+
t.Fatalf("making dir '%s' for unpacking with tar: %v", dstPath, err)
122+
}
123+
if out, err := exec.Command("tar", "-xzvf", srcPath, "-C", dstPath).CombinedOutput(); err != nil {
124+
t.Log("TAR OUTPUT:", string(out))
125+
t.Fatalf("unpacking '%s' with tar into '%s': %v", srcPath, dstPath, err)
126+
}
127+
default:
128+
t.Fatalf("unknown suffix: %s", string(ops[i]))
129+
}
130+
131+
results[suffix] = dstPath
132+
}
133+
}
134+
135+
func assertUnpackedFilesEqual(t *testing.T, originalDir string, paths []string) {
136+
originalDirHash, err := dirhash.HashDir(originalDir, "_", dirhash.DefaultHash)
137+
if err != nil {
138+
t.Fatal("failed hashing original dir ", err)
139+
}
140+
141+
for _, p := range paths {
142+
if strings.HasSuffix(p, archiveFileExt) {
143+
// archive, not a directory
144+
continue
145+
}
146+
147+
// unpacked directory
148+
hs, err := dirhash.HashDir(p, "_", dirhash.DefaultHash)
149+
if err != nil {
150+
t.Fatal("failed hashing dir ", err)
151+
}
152+
153+
if hs != originalDirHash {
154+
t.Errorf("expected '%s' to have the same hash as '%s', got different", originalDir, p)
155+
}
156+
}
157+
}
158+
159+
func generateFileSystem(t *testing.T, inputPath string) {
160+
// empty directory
161+
if err := os.MkdirAll(path.Join(inputPath, "empty_dir"), 0755); err != nil {
162+
t.Fatalf("generating empty directory: %v", err)
163+
}
164+
165+
// deep empty directories
166+
deepEmptyDirPath := path.Join(inputPath, "deep_empty_dir", strings.Repeat("/0/1/2", 20))
167+
if err := os.MkdirAll(deepEmptyDirPath, 0755); err != nil {
168+
t.Fatalf("generating deep empty directory '%s': %v", deepEmptyDirPath, err)
169+
}
170+
171+
// empty file
172+
f, err := os.Create(path.Join(inputPath, "empty_file"))
173+
if err != nil {
174+
t.Fatalf("generating empty file: %v", err)
175+
}
176+
f.Close()
177+
178+
// big (100MB) file
179+
bigFilePath := path.Join(inputPath, "big_file")
180+
for i := byte(0); i < 100; i++ {
181+
// write 1MB
182+
err := os.WriteFile(bigFilePath, bytes.Repeat([]byte{i}, 1024*1024), 0755)
183+
if err != nil {
184+
t.Fatalf("generating empty file: %v", err)
185+
}
186+
}
187+
}

vendor/golang.org/x/mod/LICENSE

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/golang.org/x/mod/PATENTS

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)