Skip to content

Commit 3d2a497

Browse files
committed
Map PV access modes to CSI access modes
Additionally enforce ReadWriteOncePod can only be used by itself
1 parent ab035f2 commit 3d2a497

File tree

2 files changed

+262
-0
lines changed

2 files changed

+262
-0
lines changed

accessmodes/access_modes.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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. Which mapping is used depends if the driver supports the
28+
// SINGLE_NODE_MULTI_WRITER capability.
29+
func ToCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode, supportsSingleNodeMultiWriter bool) (csi.VolumeCapability_AccessMode_Mode, error) {
30+
if supportsSingleNodeMultiWriter {
31+
return toSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes)
32+
}
33+
return toCSIAccessMode(pvAccessModes)
34+
}
35+
36+
// toCSIAccessMode maps PersistentVolume access modes in Kubernetes to CSI
37+
// access modes.
38+
//
39+
// +------------------+-------------------------+----------------------------------------+
40+
// | K8s AccessMode | CSI AccessMode | Additional Details |
41+
// +------------------+-------------------------+----------------------------------------+
42+
// | ReadWriteMany | MULTI_NODE_MULTI_WRITER | |
43+
// | ReadOnlyMany | MULTI_NODE_READER_ONLY | Cannot be combined with ReadWriteOnce |
44+
// | ReadWriteOnce | SINGLE_NODE_WRITER | Cannot be combined with ReadOnlyMany |
45+
// | ReadWriteOncePod | SINGLE_NODE_WRITER | Cannot be combined with any AccessMode |
46+
// +------------------+-------------------------+----------------------------------------+
47+
func toCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
48+
m := uniqueAccessModes(pvAccessModes)
49+
50+
switch {
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+
if len(m) > 1 {
56+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("Kubernetes does not support use of ReadWriteOncePod with other access modes on the same PersistentVolume")
57+
}
58+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
59+
60+
case m[v1.ReadWriteMany]:
61+
// ReadWriteMany takes precedence, regardless of what other
62+
// modes are set.
63+
return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
64+
65+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
66+
// This is not possible in the CSI spec.
67+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
68+
69+
case m[v1.ReadOnlyMany]:
70+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
71+
72+
case m[v1.ReadWriteOnce]:
73+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
74+
75+
default:
76+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
77+
}
78+
}
79+
80+
// toSingleNodeMultiWriterCapableCSIAccessMode maps PersistentVolume access
81+
// modes in Kubernetes to CSI access modes for drivers that support the
82+
// SINGLE_NODE_MULTI_WRITER capability.
83+
//
84+
// +------------------+---------------------------+----------------------------------------+
85+
// | K8s AccessMode | CSI AccessMode | Additional Details |
86+
// +------------------+---------------------------+----------------------------------------+
87+
// | ReadWriteMany | MULTI_NODE_MULTI_WRITER | |
88+
// | ReadOnlyMany | MULTI_NODE_READER_ONLY | Cannot be combined with ReadWriteOnce |
89+
// | ReadWriteOnce | SINGLE_NODE_MULTI_WRITER | Cannot be combined with ReadOnlyMany |
90+
// | ReadWriteOncePod | SINGLE_NODE_SINGLE_WRITER | Cannot be combined with any AccessMode |
91+
// +------------------+---------------------------+----------------------------------------+
92+
func toSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
93+
m := uniqueAccessModes(pvAccessModes)
94+
95+
switch {
96+
case m[v1.ReadWriteOncePod]:
97+
if len(m) > 1 {
98+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("Kubernetes does not support use of ReadWriteOncePod with other access modes on the same PersistentVolume")
99+
}
100+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, nil
101+
102+
case m[v1.ReadWriteMany]:
103+
// ReadWriteMany trumps everything, regardless of what other
104+
// modes are set.
105+
return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
106+
107+
case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
108+
// This is not possible in the CSI spec.
109+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
110+
111+
case m[v1.ReadOnlyMany]:
112+
return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
113+
114+
case m[v1.ReadWriteOnce]:
115+
return csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, nil
116+
117+
default:
118+
return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
119+
}
120+
}
121+
122+
func uniqueAccessModes(pvAccessModes []v1.PersistentVolumeAccessMode) map[v1.PersistentVolumeAccessMode]bool {
123+
m := map[v1.PersistentVolumeAccessMode]bool{}
124+
for _, mode := range pvAccessModes {
125+
m[mode] = true
126+
}
127+
return m
128+
}

accessmodes/access_modes_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
supportsSingleNodeMultiWriter bool
33+
}{
34+
{
35+
name: "RWX",
36+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
37+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
38+
},
39+
{
40+
name: "ROX + RWO",
41+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
42+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
43+
expectError: true,
44+
},
45+
{
46+
name: "ROX + RWOP",
47+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOncePod},
48+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
49+
expectError: true,
50+
},
51+
{
52+
name: "ROX",
53+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
54+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
55+
},
56+
{
57+
name: "RWO",
58+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
59+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
60+
},
61+
{
62+
name: "RWOP",
63+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
64+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
65+
},
66+
{
67+
name: "empty",
68+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
69+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
70+
expectError: true,
71+
},
72+
{
73+
name: "RWX with SINGLE_NODE_MULTI_WRITER capable driver",
74+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
75+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
76+
supportsSingleNodeMultiWriter: true,
77+
},
78+
{
79+
name: "ROX + RWO with SINGLE_NODE_MULTI_WRITER capable driver",
80+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
81+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
82+
expectError: true,
83+
supportsSingleNodeMultiWriter: true,
84+
},
85+
{
86+
name: "ROX + RWOP with SINGLE_NODE_MULTI_WRITER capable driver",
87+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOncePod},
88+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
89+
expectError: true,
90+
supportsSingleNodeMultiWriter: true,
91+
},
92+
{
93+
name: "ROX with SINGLE_NODE_MULTI_WRITER capable driver",
94+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
95+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
96+
supportsSingleNodeMultiWriter: true,
97+
},
98+
{
99+
name: "RWO with SINGLE_NODE_MULTI_WRITER capable driver",
100+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
101+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
102+
supportsSingleNodeMultiWriter: true,
103+
},
104+
{
105+
name: "RWOP with SINGLE_NODE_MULTI_WRITER capable driver",
106+
pvAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
107+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
108+
supportsSingleNodeMultiWriter: true,
109+
},
110+
{
111+
name: "empty with SINGLE_NODE_MULTI_WRITER capable driver",
112+
pvAccessModes: []v1.PersistentVolumeAccessMode{},
113+
expectedCSIAccessMode: csi.VolumeCapability_AccessMode_UNKNOWN,
114+
expectError: true,
115+
supportsSingleNodeMultiWriter: true,
116+
},
117+
}
118+
119+
for _, test := range tests {
120+
t.Run(test.name, func(t *testing.T) {
121+
csiAccessMode, err := ToCSIAccessMode(test.pvAccessModes, test.supportsSingleNodeMultiWriter)
122+
123+
if err == nil && test.expectError {
124+
t.Errorf("test %s: expected error, got none", test.name)
125+
}
126+
if err != nil && !test.expectError {
127+
t.Errorf("test %s: got error: %s", test.name, err)
128+
}
129+
if !test.expectError && csiAccessMode != test.expectedCSIAccessMode {
130+
t.Errorf("test %s: unexpected access mode: %+v", test.name, csiAccessMode)
131+
}
132+
})
133+
}
134+
}

0 commit comments

Comments
 (0)