Skip to content

Commit a5fbc2e

Browse files
authored
Merge pull request #543 from leonardoce/tar-opts
Allow ignoring unreadable files in `tar`-based snapshots
2 parents a54b4ef + d4baffe commit a5fbc2e

File tree

7 files changed

+228
-11
lines changed

7 files changed

+228
-11
lines changed

pkg/hostpath/README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,25 @@ $ csc identity plugin-info --endpoint tcp://127.0.0.1:10000
2121
"csi-hostpath" "0.1.0"
2222
```
2323

24-
#### Create a volume
24+
#### Create a block volume
2525
```
26-
$ csc controller new --endpoint tcp://127.0.0.1:10000 --cap 1,block CSIVolumeName
26+
$ csc controller new --endpoint tcp://127.0.0.1:10000 --cap 1,block --req-bytes 1048576 --lim-bytes 1048576 CSIVolumeName
2727
CSIVolumeID
2828
```
2929

30+
#### Create mounted volume
31+
```
32+
$ csc controller new --endpoint tcp://127.0.0.1:10000 --cap MULTI_NODE_MULTI_WRITER,mount,xfs,uid=500,gid=500 CSIVolumeName
33+
CSIVolumeID
34+
```
35+
36+
#### List volumes
37+
```
38+
csc controller list-volumes --endpoint tcp://127.0.0.1:10000
39+
CSIVolumeID 0
40+
CSIVolumeID 0
41+
```
42+
3043
#### Delete a volume
3144
```
3245
$ csc controller del --endpoint tcp://127.0.0.1:10000 CSIVolumeID
@@ -56,3 +69,19 @@ CSIVolumeID
5669
$ csc node get-info --endpoint tcp://127.0.0.1:10000
5770
CSINode
5871
```
72+
73+
### Create snapshot
74+
```
75+
$ csc controller create-snapshot --endpoint tcp://127.0.0.1:10000 --params ignoreFailedRead=true --source-volume CSIVolumeID CSISnapshotName
76+
CSISnapshotID
77+
```
78+
79+
### Delete snapshot
80+
```
81+
csc controller delete-snapshot --endpoint tcp://127.0.0.1:10000 CSISnapshotID
82+
```
83+
84+
### List snapshots
85+
```
86+
csc controller list-snapshots --endpoint tcp://127.0.0.1:10000
87+
```

pkg/hostpath/controllerserver.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,12 @@ func (hp *hostPath) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotR
585585
snapshotID := uuid.NewUUID().String()
586586
creationTime := timestamppb.Now()
587587
file := hp.getSnapshotPath(snapshotID)
588+
opts, err := optionsFromParameters(hostPathVolume, req.Parameters)
589+
if err != nil {
590+
return nil, status.Errorf(codes.InvalidArgument, "invalid volume snapshot class parameters: %s", err.Error())
591+
}
588592

589-
if err := hp.createSnapshotFromVolume(hostPathVolume, file); err != nil {
593+
if err := hp.createSnapshotFromVolume(hostPathVolume, file, opts...); err != nil {
590594
return nil, err
591595
}
592596

pkg/hostpath/groupcontrollerserver.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,15 @@ func (hp *hostPath) CreateVolumeGroupSnapshot(ctx context.Context, req *csi.Crea
122122
return nil, err
123123
}
124124

125+
opts, err := optionsFromParameters(hostPathVolume, req.Parameters)
126+
if err != nil {
127+
return nil, status.Errorf(codes.InvalidArgument, "invalid volume group snapshot class parameters: %s", err.Error())
128+
}
129+
125130
snapshotID := uuid.NewUUID().String()
126131
file := hp.getSnapshotPath(snapshotID)
127132

128-
if err := hp.createSnapshotFromVolume(hostPathVolume, file); err != nil {
133+
if err := hp.createSnapshotFromVolume(hostPathVolume, file, opts...); err != nil {
129134
return nil, err
130135
}
131136

pkg/hostpath/hostpath.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,17 +385,25 @@ func (hp *hostPath) getAttachCount() int64 {
385385
return count
386386
}
387387

388-
func (hp *hostPath) createSnapshotFromVolume(vol state.Volume, file string) error {
389-
var cmd []string
388+
func (hp *hostPath) createSnapshotFromVolume(vol state.Volume, file string, opts ...string) error {
389+
var args []string
390+
var cmdName string
390391
if vol.VolAccessType == state.BlockAccess {
391392
klog.V(4).Infof("Creating snapshot of Raw Block Mode Volume")
392-
cmd = []string{"cp", vol.VolPath, file}
393+
cmdName = "cp"
394+
args = []string{vol.VolPath, file}
393395
} else {
394-
klog.V(4).Infof("Creating snapshot of Filsystem Mode Volume")
395-
cmd = []string{"tar", "czf", file, "-C", vol.VolPath, "."}
396+
klog.V(4).Infof("Creating snapshot of Filesystem Mode Volume")
397+
cmdName = "tar"
398+
opts = append(
399+
[]string{"czf", file, "-C", vol.VolPath},
400+
opts...,
401+
)
402+
args = []string{"."}
396403
}
397404
executor := utilexec.New()
398-
out, err := executor.Command(cmd[0], cmd[1:]...).CombinedOutput()
405+
optsAndArgs := append(opts, args...)
406+
out, err := executor.Command(cmdName, optsAndArgs...).CombinedOutput()
399407
if err != nil {
400408
return fmt.Errorf("failed create snapshot: %w: %s", err, out)
401409
}

pkg/hostpath/options.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package hostpath
18+
19+
import (
20+
"fmt"
21+
"strconv"
22+
23+
"github.com/kubernetes-csi/csi-driver-host-path/pkg/state"
24+
)
25+
26+
// ignoreFailedReadParameterName is a parameter that, when set to true,
27+
// causes the `--ignore-failed-read` option to be passed to `tar`.
28+
const ignoreFailedReadParameterName = "ignoreFailedRead"
29+
30+
func optionsFromParameters(vol state.Volume, parameters map[string]string) ([]string, error) {
31+
// We do not support options for snapshots of block volumes
32+
if vol.VolAccessType == state.BlockAccess {
33+
return nil, nil
34+
}
35+
36+
ignoreFailedReadString := parameters[ignoreFailedReadParameterName]
37+
if len(ignoreFailedReadString) == 0 {
38+
return nil, nil
39+
}
40+
41+
if ok, err := strconv.ParseBool(ignoreFailedReadString); err != nil {
42+
return nil, fmt.Errorf(
43+
"invalid value for %q, expected boolean but was %q",
44+
ignoreFailedReadParameterName,
45+
ignoreFailedReadString,
46+
)
47+
} else if ok {
48+
return []string{"--ignore-failed-read"}, nil
49+
}
50+
51+
return nil, nil
52+
}

pkg/hostpath/options_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package hostpath
18+
19+
import (
20+
"reflect"
21+
"testing"
22+
23+
"github.com/kubernetes-csi/csi-driver-host-path/pkg/state"
24+
)
25+
26+
func TestOptionsFromParameters(t *testing.T) {
27+
mountedVolume := state.Volume{
28+
VolAccessType: state.MountAccess,
29+
}
30+
blockVolume := state.Volume{
31+
VolAccessType: state.BlockAccess,
32+
}
33+
34+
cases := []struct {
35+
testName string
36+
volume state.Volume
37+
params map[string]string
38+
result []string
39+
success bool
40+
}{
41+
{
42+
testName: "no options passed (mounted)",
43+
params: nil,
44+
result: nil,
45+
success: true,
46+
volume: mountedVolume,
47+
},
48+
{
49+
testName: "no options passed (block)",
50+
params: nil,
51+
result: nil,
52+
success: true,
53+
volume: blockVolume,
54+
},
55+
{
56+
testName: "ignoreFailedRead=false (mounted)",
57+
params: map[string]string{
58+
ignoreFailedReadParameterName: "false",
59+
},
60+
result: nil,
61+
success: true,
62+
volume: mountedVolume,
63+
},
64+
{
65+
testName: "ignoreFailedRead=true (mounted)",
66+
params: map[string]string{
67+
ignoreFailedReadParameterName: "true",
68+
},
69+
result: []string{
70+
"--ignore-failed-read",
71+
},
72+
success: true,
73+
volume: mountedVolume,
74+
},
75+
{
76+
testName: "invalid ignoreFailedRead (mounted)",
77+
params: map[string]string{
78+
ignoreFailedReadParameterName: "ABC",
79+
},
80+
result: nil,
81+
success: false,
82+
volume: mountedVolume,
83+
},
84+
{
85+
testName: "ignoreFailedRead=true (block)",
86+
params: map[string]string{
87+
ignoreFailedReadParameterName: "true",
88+
},
89+
result: nil,
90+
success: true,
91+
volume: blockVolume,
92+
},
93+
{
94+
testName: "invalid ignoreFailedRead (block)",
95+
params: map[string]string{
96+
ignoreFailedReadParameterName: "ABC",
97+
},
98+
result: nil,
99+
success: true,
100+
volume: blockVolume,
101+
},
102+
}
103+
104+
for _, tt := range cases {
105+
t.Run(tt.testName, func(t *testing.T) {
106+
result, err := optionsFromParameters(tt.volume, tt.params)
107+
if tt.success && err != nil {
108+
t.Fatalf("expected success, found error: %s", err.Error())
109+
}
110+
if !tt.success && err == nil {
111+
t.Fatalf("expected failure, but succeeded with %q", result)
112+
}
113+
114+
if tt.success && !reflect.DeepEqual(tt.result, result) {
115+
t.Fatalf("expected %q but received %q", tt.result, result)
116+
}
117+
})
118+
}
119+
}

pkg/state/strings.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (s *Strings) Empty() bool {
4040
return len(*s) == 0
4141
}
4242

43-
// Remove removes the first occurence of the string, if present.
43+
// Remove removes the first occurrence of the string, if present.
4444
func (s *Strings) Remove(str string) {
4545
for i, str2 := range *s {
4646
if str == str2 {

0 commit comments

Comments
 (0)