Skip to content

Commit d0686c7

Browse files
committed
Map PV access modes to CSI access modes
1 parent e7ee9ab commit d0686c7

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

accessmodes/access_modes.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
Copyright 2021 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 accessmodes
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/container-storage-interface/spec/lib/go/csi"
23+
v1 "k8s.io/api/core/v1"
24+
)
25+
26+
// ToCSIAccessMode maps PersistentVolume access modes in Kubernetes to CSI
27+
// access modes.
28+
func ToCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
29+
m := uniqueAccessModes(pvAccessModes)
30+
31+
switch {
32+
case m[v1.ReadWriteMany]:
33+
// ReadWriteMany takes precedence, regardless of what other
34+
// modes are set.
35+
return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
36+
37+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
38+
// This is not possible in the CSI spec.
39+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
40+
41+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOncePod]:
42+
// This is not possible in the CSI spec.
43+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOncePod on the same PersistentVolume")
44+
45+
case m[v1.ReadWriteOnce] && m[v1.ReadWriteOncePod]:
46+
// This is not possible in the CSI spec.
47+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadWriteOnce and ReadWriteOncePod on the same PersistentVolume")
48+
49+
case m[v1.ReadOnlyMany]:
50+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
51+
52+
case m[v1.ReadWriteOnce]:
53+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
54+
55+
// This mapping exists to enable CSI drivers that lack the
56+
// SINGLE_NODE_MULTI_WRITER capability to work with the
57+
// ReadWriteOncePod access mode.
58+
case m[v1.ReadWriteOncePod]:
59+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
60+
61+
default:
62+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
63+
}
64+
}
65+
66+
// ToSingleNodeMultiWriterCapableCSIAccessMode maps PersistentVolume access
67+
// modes in Kubernetes to CSI access modes for drivers that support the
68+
// SINGLE_NODE_MULTI_WRITER capability.
69+
func ToSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
70+
m := uniqueAccessModes(pvAccessModes)
71+
72+
switch {
73+
case m[v1.ReadWriteMany]:
74+
// ReadWriteMany trumps everything, regardless of what other
75+
// modes are set.
76+
return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
77+
78+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
79+
// This is not possible in the CSI spec.
80+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
81+
82+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOncePod]:
83+
// This is not possible in the CSI spec.
84+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOncePod on the same PersistentVolume")
85+
86+
case m[v1.ReadWriteOnce] && m[v1.ReadWriteOncePod]:
87+
// This is not possible in the CSI spec.
88+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadWriteOnce and ReadWriteOncePod on the same PersistentVolume")
89+
90+
case m[v1.ReadOnlyMany]:
91+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
92+
93+
case m[v1.ReadWriteOnce]:
94+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, nil
95+
96+
case m[v1.ReadWriteOncePod]:
97+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, nil
98+
99+
default:
100+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
101+
}
102+
}
103+
104+
func uniqueAccessModes(pvAccessModes []v1.PersistentVolumeAccessMode) map[v1.PersistentVolumeAccessMode]bool {
105+
m := map[v1.PersistentVolumeAccessMode]bool{}
106+
for _, mode := range pvAccessModes {
107+
m[mode] = true
108+
}
109+
return m
110+
}

accessmodes/access_modes_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
Copyright 2021 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 accessmodes
18+
19+
import (
20+
"testing"
21+
22+
"github.com/container-storage-interface/spec/lib/go/csi"
23+
v1 "k8s.io/api/core/v1"
24+
)
25+
26+
func TestToCSIAccessMode(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
pvAccessModes []v1.PersistentVolumeAccessMode
30+
expectedCSIAccessMode csi.VolumeCapability_AccessMode_Mode
31+
expectError bool
32+
}{
33+
{
34+
name: "RWX",
35+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
36+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
37+
},
38+
{
39+
name: "ROX + RWO",
40+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
41+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
42+
expectError: true,
43+
},
44+
{
45+
name: "ROX + RWOP",
46+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOncePod},
47+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
48+
expectError: true,
49+
},
50+
{
51+
name: "RWO + RWOP",
52+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteOncePod},
53+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
54+
expectError: true,
55+
},
56+
{
57+
name: "ROX",
58+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
59+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
60+
},
61+
{
62+
name: "RWO",
63+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
64+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
65+
},
66+
{
67+
name: "RWOP",
68+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
69+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
70+
},
71+
{
72+
name: "empty",
73+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
74+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
75+
expectError: true,
76+
},
77+
}
78+
79+
for _, test := range tests {
80+
t.Run(test.name, func(t *testing.T) {
81+
csiAccessMode, err := ToCSIAccessMode(test.pvAccessModes)
82+
83+
if err == nil && test.expectError {
84+
t.Errorf("test %s: expected error, got none", test.name)
85+
}
86+
if err != nil && !test.expectError {
87+
t.Errorf("test %s: got error: %s", test.name, err)
88+
}
89+
if !test.expectError && csiAccessMode != test.expectedCSIAccessMode {
90+
t.Errorf("test %s: unexpected access mode: %+v", test.name, csiAccessMode)
91+
}
92+
})
93+
}
94+
}
95+
96+
func TestToSingleNodeMultiWriterCapableCSIAccessMode(t *testing.T) {
97+
tests := []struct {
98+
name string
99+
pvAccessModes []v1.PersistentVolumeAccessMode
100+
expectedCSIAccessMode csi.VolumeCapability_AccessMode_Mode
101+
expectError bool
102+
}{
103+
{
104+
name: "RWX",
105+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
106+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
107+
},
108+
{
109+
name: "ROX + RWO",
110+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
111+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
112+
expectError: true,
113+
},
114+
{
115+
name: "ROX + RWOP",
116+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOncePod},
117+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
118+
expectError: true,
119+
},
120+
{
121+
name: "RWO + RWOP",
122+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteOncePod},
123+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
124+
expectError: true,
125+
},
126+
{
127+
name: "ROX",
128+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
129+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
130+
},
131+
{
132+
name: "RWO",
133+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
134+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
135+
},
136+
{
137+
name: "RWOP",
138+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
139+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
140+
},
141+
{
142+
name: "empty",
143+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
144+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
145+
expectError: true,
146+
},
147+
}
148+
149+
for _, test := range tests {
150+
t.Run(test.name, func(t *testing.T) {
151+
csiAccessMode, err := ToSingleNodeMultiWriterCapableCSIAccessMode(test.pvAccessModes)
152+
153+
if err == nil && test.expectError {
154+
t.Errorf("test %s: expected error, got none", test.name)
155+
}
156+
if err != nil && !test.expectError {
157+
t.Errorf("test %s: got error: %s", test.name, err)
158+
}
159+
if !test.expectError && csiAccessMode != test.expectedCSIAccessMode {
160+
t.Errorf("test %s: unexpected access mode: %+v", test.name, csiAccessMode)
161+
}
162+
})
163+
}
164+
}

0 commit comments

Comments
 (0)