Skip to content

Commit a659c3d

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

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

accessmodes/access_modes.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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.ReadOnlyMany]:
46+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
47+
48+
case m[v1.ReadWriteOnce]:
49+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
50+
51+
// This mapping exists to enable CSI drivers that lack the
52+
// SINGLE_NODE_MULTI_WRITER capability to work with the
53+
// ReadWriteOncePod access mode.
54+
case m[v1.ReadWriteOncePod]:
55+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
56+
57+
default:
58+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
59+
}
60+
}
61+
62+
// ToSingleNodeMultiWriterCapableCSIAccessMode maps PersistentVolume access
63+
// modes in Kubernetes to CSI access modes for drivers that support the
64+
// SINGLE_NODE_MULTI_WRITER capability.
65+
func ToSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
66+
m := uniqueAccessModes(pvAccessModes)
67+
68+
switch {
69+
case m[v1.ReadWriteMany]:
70+
// ReadWriteMany trumps everything, regardless of what other
71+
// modes are set.
72+
return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
73+
74+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
75+
// This is not possible in the CSI spec.
76+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
77+
78+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOncePod]:
79+
// This is not possible in the CSI spec.
80+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOncePod on the same PersistentVolume")
81+
82+
case m[v1.ReadOnlyMany]:
83+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
84+
85+
case m[v1.ReadWriteOnce]:
86+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, nil
87+
88+
case m[v1.ReadWriteOncePod]:
89+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, nil
90+
91+
default:
92+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
93+
}
94+
}
95+
96+
func uniqueAccessModes(pvAccessModes []v1.PersistentVolumeAccessMode) map[v1.PersistentVolumeAccessMode]bool {
97+
m := map[v1.PersistentVolumeAccessMode]bool{}
98+
for _, mode := range pvAccessModes {
99+
m[mode] = true
100+
}
101+
return m
102+
}

accessmodes/access_modes_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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: "ROX",
52+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
53+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
54+
},
55+
{
56+
name: "RWO",
57+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
58+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
59+
},
60+
{
61+
name: "RWOP",
62+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
63+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
64+
},
65+
{
66+
name: "empty",
67+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
68+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
69+
expectError: true,
70+
},
71+
}
72+
73+
for _, test := range tests {
74+
t.Run(test.name, func(t *testing.T) {
75+
csiAccessMode, err := ToCSIAccessMode(test.pvAccessModes)
76+
77+
if err == nil && test.expectError {
78+
t.Errorf("test %s: expected error, got none", test.name)
79+
}
80+
if err != nil && !test.expectError {
81+
t.Errorf("test %s: got error: %s", test.name, err)
82+
}
83+
if !test.expectError && csiAccessMode != test.expectedCSIAccessMode {
84+
t.Errorf("test %s: unexpected access mode: %+v", test.name, csiAccessMode)
85+
}
86+
})
87+
}
88+
}
89+
90+
func TestToSingleNodeMultiWriterCapableCSIAccessMode(t *testing.T) {
91+
tests := []struct {
92+
name string
93+
pvAccessModes []v1.PersistentVolumeAccessMode
94+
expectedCSIAccessMode csi.VolumeCapability_AccessMode_Mode
95+
expectError bool
96+
}{
97+
{
98+
name: "RWX",
99+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
100+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
101+
},
102+
{
103+
name: "ROX + RWO",
104+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
105+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
106+
expectError: true,
107+
},
108+
{
109+
name: "ROX + RWOP",
110+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOncePod},
111+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
112+
expectError: true,
113+
},
114+
{
115+
name: "ROX",
116+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
117+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
118+
},
119+
{
120+
name: "RWO",
121+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
122+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
123+
},
124+
{
125+
name: "RWOP",
126+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
127+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
128+
},
129+
{
130+
name: "empty",
131+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
132+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
133+
expectError: true,
134+
},
135+
}
136+
137+
for _, test := range tests {
138+
t.Run(test.name, func(t *testing.T) {
139+
csiAccessMode, err := ToSingleNodeMultiWriterCapableCSIAccessMode(test.pvAccessModes)
140+
141+
if err == nil && test.expectError {
142+
t.Errorf("test %s: expected error, got none", test.name)
143+
}
144+
if err != nil && !test.expectError {
145+
t.Errorf("test %s: got error: %s", test.name, err)
146+
}
147+
if !test.expectError && csiAccessMode != test.expectedCSIAccessMode {
148+
t.Errorf("test %s: unexpected access mode: %+v", test.name, csiAccessMode)
149+
}
150+
})
151+
}
152+
}

0 commit comments

Comments
 (0)