Merge pull request #102028 from chrishenzie/read-write-once-pod-access-mode
ReadWriteOncePod access mode for PVs and PVCs
This commit is contained in:
commit
01819dd322
@ -303,19 +303,22 @@ func IsStandardFinalizerName(str string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessModesAsString returns a string representation of an array of access modes.
|
// GetAccessModesAsString returns a string representation of an array of access modes.
|
||||||
// modes, when present, are always in the same order: RWO,ROX,RWX.
|
// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
|
||||||
func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string {
|
func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string {
|
||||||
modes = removeDuplicateAccessModes(modes)
|
modes = removeDuplicateAccessModes(modes)
|
||||||
modesStr := []string{}
|
modesStr := []string{}
|
||||||
if containsAccessMode(modes, core.ReadWriteOnce) {
|
if ContainsAccessMode(modes, core.ReadWriteOnce) {
|
||||||
modesStr = append(modesStr, "RWO")
|
modesStr = append(modesStr, "RWO")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, core.ReadOnlyMany) {
|
if ContainsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
modesStr = append(modesStr, "ROX")
|
modesStr = append(modesStr, "ROX")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, core.ReadWriteMany) {
|
if ContainsAccessMode(modes, core.ReadWriteMany) {
|
||||||
modesStr = append(modesStr, "RWX")
|
modesStr = append(modesStr, "RWX")
|
||||||
}
|
}
|
||||||
|
if ContainsAccessMode(modes, core.ReadWriteOncePod) {
|
||||||
|
modesStr = append(modesStr, "RWOP")
|
||||||
|
}
|
||||||
return strings.Join(modesStr, ",")
|
return strings.Join(modesStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +335,8 @@ func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode {
|
|||||||
accessModes = append(accessModes, core.ReadOnlyMany)
|
accessModes = append(accessModes, core.ReadOnlyMany)
|
||||||
case s == "RWX":
|
case s == "RWX":
|
||||||
accessModes = append(accessModes, core.ReadWriteMany)
|
accessModes = append(accessModes, core.ReadWriteMany)
|
||||||
|
case s == "RWOP":
|
||||||
|
accessModes = append(accessModes, core.ReadWriteOncePod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accessModes
|
return accessModes
|
||||||
@ -341,14 +346,14 @@ func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode {
|
|||||||
func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode {
|
func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode {
|
||||||
accessModes := []core.PersistentVolumeAccessMode{}
|
accessModes := []core.PersistentVolumeAccessMode{}
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if !containsAccessMode(accessModes, m) {
|
if !ContainsAccessMode(accessModes, m) {
|
||||||
accessModes = append(accessModes, m)
|
accessModes = append(accessModes, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accessModes
|
return accessModes
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool {
|
func ContainsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool {
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if m == mode {
|
if m == mode {
|
||||||
return true
|
return true
|
||||||
|
@ -87,25 +87,42 @@ func TestIsStandardContainerResource(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetAccessModesFromString(t *testing.T) {
|
func TestGetAccessModesFromString(t *testing.T) {
|
||||||
modes := GetAccessModesFromString("ROX")
|
modes := GetAccessModesFromString("ROX")
|
||||||
if !containsAccessMode(modes, core.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
modes = GetAccessModesFromString("ROX,RWX")
|
modes = GetAccessModesFromString("ROX,RWX")
|
||||||
if !containsAccessMode(modes, core.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
if !containsAccessMode(modes, core.ReadWriteMany) {
|
if !ContainsAccessMode(modes, core.ReadWriteMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
modes = GetAccessModesFromString("RWO,ROX,RWX")
|
modes = GetAccessModesFromString("RWO,ROX,RWX")
|
||||||
if !containsAccessMode(modes, core.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, core.ReadWriteOnce) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteOnce, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
if !containsAccessMode(modes, core.ReadWriteMany) {
|
if !ContainsAccessMode(modes, core.ReadWriteMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modes = GetAccessModesFromString("RWO,ROX,RWX,RWOP")
|
||||||
|
if !ContainsAccessMode(modes, core.ReadWriteOnce) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteOnce, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, core.ReadWriteMany) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, core.ReadWriteOncePod) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", core.ReadWriteOncePod, modes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveDuplicateAccessModes(t *testing.T) {
|
func TestRemoveDuplicateAccessModes(t *testing.T) {
|
||||||
|
@ -511,6 +511,9 @@ const (
|
|||||||
ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany"
|
ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany"
|
||||||
// can be mounted in read/write mode to many hosts
|
// can be mounted in read/write mode to many hosts
|
||||||
ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany"
|
ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany"
|
||||||
|
// can be mounted read/write mode to exactly 1 pod
|
||||||
|
// cannot be used in combination with other access modes
|
||||||
|
ReadWriteOncePod PersistentVolumeAccessMode = "ReadWriteOncePod"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PersistentVolumePhase defines the phase in which a PV is
|
// PersistentVolumePhase defines the phase in which a PV is
|
||||||
|
@ -170,19 +170,22 @@ func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessModesAsString returns a string representation of an array of access modes.
|
// GetAccessModesAsString returns a string representation of an array of access modes.
|
||||||
// modes, when present, are always in the same order: RWO,ROX,RWX.
|
// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
|
||||||
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
||||||
modes = removeDuplicateAccessModes(modes)
|
modes = removeDuplicateAccessModes(modes)
|
||||||
modesStr := []string{}
|
modesStr := []string{}
|
||||||
if containsAccessMode(modes, v1.ReadWriteOnce) {
|
if ContainsAccessMode(modes, v1.ReadWriteOnce) {
|
||||||
modesStr = append(modesStr, "RWO")
|
modesStr = append(modesStr, "RWO")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, v1.ReadOnlyMany) {
|
if ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
modesStr = append(modesStr, "ROX")
|
modesStr = append(modesStr, "ROX")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, v1.ReadWriteMany) {
|
if ContainsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
modesStr = append(modesStr, "RWX")
|
modesStr = append(modesStr, "RWX")
|
||||||
}
|
}
|
||||||
|
if ContainsAccessMode(modes, v1.ReadWriteOncePod) {
|
||||||
|
modesStr = append(modesStr, "RWOP")
|
||||||
|
}
|
||||||
return strings.Join(modesStr, ",")
|
return strings.Join(modesStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +202,8 @@ func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
|
|||||||
accessModes = append(accessModes, v1.ReadOnlyMany)
|
accessModes = append(accessModes, v1.ReadOnlyMany)
|
||||||
case s == "RWX":
|
case s == "RWX":
|
||||||
accessModes = append(accessModes, v1.ReadWriteMany)
|
accessModes = append(accessModes, v1.ReadWriteMany)
|
||||||
|
case s == "RWOP":
|
||||||
|
accessModes = append(accessModes, v1.ReadWriteOncePod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accessModes
|
return accessModes
|
||||||
@ -208,14 +213,14 @@ func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
|
|||||||
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
||||||
accessModes := []v1.PersistentVolumeAccessMode{}
|
accessModes := []v1.PersistentVolumeAccessMode{}
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if !containsAccessMode(accessModes, m) {
|
if !ContainsAccessMode(accessModes, m) {
|
||||||
accessModes = append(accessModes, m)
|
accessModes = append(accessModes, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accessModes
|
return accessModes
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if m == mode {
|
if m == mode {
|
||||||
return true
|
return true
|
||||||
|
@ -213,25 +213,42 @@ func TestIsOvercommitAllowed(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetAccessModesFromString(t *testing.T) {
|
func TestGetAccessModesFromString(t *testing.T) {
|
||||||
modes := GetAccessModesFromString("ROX")
|
modes := GetAccessModesFromString("ROX")
|
||||||
if !containsAccessMode(modes, v1.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
modes = GetAccessModesFromString("ROX,RWX")
|
modes = GetAccessModesFromString("ROX,RWX")
|
||||||
if !containsAccessMode(modes, v1.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
if !containsAccessMode(modes, v1.ReadWriteMany) {
|
if !ContainsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
modes = GetAccessModesFromString("RWO,ROX,RWX")
|
modes = GetAccessModesFromString("RWO,ROX,RWX")
|
||||||
if !containsAccessMode(modes, v1.ReadOnlyMany) {
|
if !ContainsAccessMode(modes, v1.ReadWriteOnce) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
||||||
}
|
}
|
||||||
if !containsAccessMode(modes, v1.ReadWriteMany) {
|
if !ContainsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modes = GetAccessModesFromString("RWO,ROX,RWX,RWOP")
|
||||||
|
if !ContainsAccessMode(modes, v1.ReadWriteOnce) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
|
||||||
|
}
|
||||||
|
if !ContainsAccessMode(modes, v1.ReadWriteOncePod) {
|
||||||
|
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOncePod, modes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveDuplicateAccessModes(t *testing.T) {
|
func TestRemoveDuplicateAccessModes(t *testing.T) {
|
||||||
|
@ -1608,16 +1608,17 @@ func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPat
|
|||||||
if ephemeral.VolumeClaimTemplate == nil {
|
if ephemeral.VolumeClaimTemplate == nil {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"))...)
|
opts := ValidationOptionsForPersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, nil)
|
||||||
|
allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"), opts)...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid.
|
// ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid.
|
||||||
// Checking of the object data is very minimal because only labels and annotations are used.
|
// Checking of the object data is very minimal because only labels and annotations are used.
|
||||||
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path) field.ErrorList {
|
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := validatePersistentVolumeClaimTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
|
allErrs := validatePersistentVolumeClaimTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
|
||||||
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"))...)
|
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"), opts)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1638,6 +1639,12 @@ var allowedPVCTemplateObjectMetaFields = map[string]bool{
|
|||||||
"Labels": true,
|
"Labels": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PersistentVolumeSpecValidationOptions contains the different settings for PeristentVolume validation
|
||||||
|
type PersistentVolumeSpecValidationOptions struct {
|
||||||
|
// Allow spec to contain the "ReadWiteOncePod" access mode
|
||||||
|
AllowReadWriteOncePod bool
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeName checks that a name is appropriate for a
|
// ValidatePersistentVolumeName checks that a name is appropriate for a
|
||||||
// PersistentVolumeName object.
|
// PersistentVolumeName object.
|
||||||
var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain
|
var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain
|
||||||
@ -1648,7 +1655,22 @@ var supportedReclaimPolicy = sets.NewString(string(core.PersistentVolumeReclaimD
|
|||||||
|
|
||||||
var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), string(core.PersistentVolumeFilesystem))
|
var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), string(core.PersistentVolumeFilesystem))
|
||||||
|
|
||||||
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path) field.ErrorList {
|
func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
|
||||||
|
opts := PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||||
|
}
|
||||||
|
if oldPv == nil {
|
||||||
|
// If there's no old PV, use the options based solely on feature enablement
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
if helper.ContainsAccessMode(oldPv.Spec.AccessModes, core.ReadWriteOncePod) {
|
||||||
|
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||||
|
opts.AllowReadWriteOncePod = true
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if validateInlinePersistentVolumeSpec {
|
if validateInlinePersistentVolumeSpec {
|
||||||
@ -1666,10 +1688,26 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
|
|||||||
if len(pvSpec.AccessModes) == 0 {
|
if len(pvSpec.AccessModes) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
|
||||||
|
if opts.AllowReadWriteOncePod {
|
||||||
|
expandedSupportedAccessModes.Insert(string(core.ReadWriteOncePod))
|
||||||
|
}
|
||||||
|
|
||||||
|
foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
|
||||||
for _, mode := range pvSpec.AccessModes {
|
for _, mode := range pvSpec.AccessModes {
|
||||||
if !supportedAccessModes.Has(string(mode)) {
|
if !expandedSupportedAccessModes.Has(string(mode)) {
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List()))
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, expandedSupportedAccessModes.List()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mode == core.ReadWriteOncePod {
|
||||||
|
foundReadWriteOncePod = true
|
||||||
|
} else if supportedAccessModes.Has(string(mode)) {
|
||||||
|
foundNonReadWriteOncePod = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundReadWriteOncePod && foundNonReadWriteOncePod {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validateInlinePersistentVolumeSpec {
|
if !validateInlinePersistentVolumeSpec {
|
||||||
@ -1922,17 +1960,17 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
|
func ValidatePersistentVolume(pv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
|
||||||
metaPath := field.NewPath("metadata")
|
metaPath := field.NewPath("metadata")
|
||||||
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
|
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
|
||||||
allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"))...)
|
allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"), opts)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make.
|
// ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make.
|
||||||
// newPv is updated with fields that cannot be changed.
|
// newPv is updated with fields that cannot be changed.
|
||||||
func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList {
|
func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidatePersistentVolume(newPv)
|
allErrs := ValidatePersistentVolume(newPv, opts)
|
||||||
|
|
||||||
// if oldPV does not have ControllerExpandSecretRef then allow it to be set
|
// if oldPV does not have ControllerExpandSecretRef then allow it to be set
|
||||||
if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) &&
|
if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) &&
|
||||||
@ -1965,15 +2003,51 @@ func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) f
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PersistentVolumeClaimSpecValidationOptions contains the different settings for PersistentVolumeClaim validation
|
||||||
|
type PersistentVolumeClaimSpecValidationOptions struct {
|
||||||
|
// Allow spec to contain the "ReadWiteOncePod" access mode
|
||||||
|
AllowReadWriteOncePod bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
|
||||||
|
opts := PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||||
|
}
|
||||||
|
if oldPvc == nil {
|
||||||
|
// If there's no old PVC, use the options based solely on feature enablement
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
if helper.ContainsAccessMode(oldPvc.Spec.AccessModes, core.ReadWriteOncePod) {
|
||||||
|
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||||
|
opts.AllowReadWriteOncePod = true
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
|
||||||
|
opts := PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||||
|
}
|
||||||
|
if oldClaimTemplate == nil {
|
||||||
|
// If there's no old PVC template, use the options based solely on feature enablement
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
if helper.ContainsAccessMode(oldClaimTemplate.Spec.AccessModes, core.ReadWriteOncePod) {
|
||||||
|
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||||
|
opts.AllowReadWriteOncePod = true
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeClaim validates a PersistentVolumeClaim
|
// ValidatePersistentVolumeClaim validates a PersistentVolumeClaim
|
||||||
func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim) field.ErrorList {
|
func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata"))
|
allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"))...)
|
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"), opts)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec
|
// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec
|
||||||
func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path) field.ErrorList {
|
func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
if len(spec.AccessModes) == 0 {
|
if len(spec.AccessModes) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
|
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
|
||||||
@ -1981,11 +2055,28 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
|
|||||||
if spec.Selector != nil {
|
if spec.Selector != nil {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
|
||||||
|
if opts.AllowReadWriteOncePod {
|
||||||
|
expandedSupportedAccessModes.Insert(string(core.ReadWriteOncePod))
|
||||||
|
}
|
||||||
|
|
||||||
|
foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
|
||||||
for _, mode := range spec.AccessModes {
|
for _, mode := range spec.AccessModes {
|
||||||
if mode != core.ReadWriteOnce && mode != core.ReadOnlyMany && mode != core.ReadWriteMany {
|
if !expandedSupportedAccessModes.Has(string(mode)) {
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List()))
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, expandedSupportedAccessModes.List()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == core.ReadWriteOncePod {
|
||||||
|
foundReadWriteOncePod = true
|
||||||
|
} else if supportedAccessModes.Has(string(mode)) {
|
||||||
|
foundNonReadWriteOncePod = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if foundReadWriteOncePod && foundNonReadWriteOncePod {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
|
||||||
|
}
|
||||||
|
|
||||||
storageValue, ok := spec.Resources.Requests[core.ResourceStorage]
|
storageValue, ok := spec.Resources.Requests[core.ResourceStorage]
|
||||||
if !ok {
|
if !ok {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), ""))
|
||||||
@ -2024,9 +2115,9 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim
|
// ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim
|
||||||
func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim) field.ErrorList {
|
func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata"))
|
allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc)...)
|
allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc, opts)...)
|
||||||
newPvcClone := newPvc.DeepCopy()
|
newPvcClone := newPvc.DeepCopy()
|
||||||
oldPvcClone := oldPvc.DeepCopy()
|
oldPvcClone := oldPvc.DeepCopy()
|
||||||
|
|
||||||
|
@ -68,8 +68,9 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||||||
validMode := core.PersistentVolumeFilesystem
|
validMode := core.PersistentVolumeFilesystem
|
||||||
invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
|
invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
volume *core.PersistentVolume
|
enableReadWriteOncePod bool
|
||||||
|
volume *core.PersistentVolume
|
||||||
}{
|
}{
|
||||||
"good-volume": {
|
"good-volume": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
@ -211,6 +212,54 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||||||
VolumeMode: &invalidMode,
|
VolumeMode: &invalidMode,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
"with-read-write-once-pod-feature-gate-enabled": {
|
||||||
|
isExpectedFailure: false,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
volume: testVolume("foo", "", core.PersistentVolumeSpec{
|
||||||
|
Capacity: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
|
||||||
|
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||||
|
HostPath: &core.HostPathVolumeSource{
|
||||||
|
Path: "/foo",
|
||||||
|
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"with-read-write-once-pod-feature-gate-disabled": {
|
||||||
|
isExpectedFailure: true,
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
volume: testVolume("foo", "", core.PersistentVolumeSpec{
|
||||||
|
Capacity: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
|
||||||
|
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||||
|
HostPath: &core.HostPathVolumeSource{
|
||||||
|
Path: "/foo",
|
||||||
|
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"with-read-write-once-pod-and-others-feature-gate-enabled": {
|
||||||
|
isExpectedFailure: true,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
volume: testVolume("foo", "", core.PersistentVolumeSpec{
|
||||||
|
Capacity: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
|
||||||
|
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||||
|
HostPath: &core.HostPathVolumeSource{
|
||||||
|
Path: "/foo",
|
||||||
|
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
"unexpected-namespace": {
|
"unexpected-namespace": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
|
volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
|
||||||
@ -415,7 +464,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
errs := ValidatePersistentVolume(scenario.volume)
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, scenario.enableReadWriteOncePod)()
|
||||||
|
|
||||||
|
opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
|
||||||
|
errs := ValidatePersistentVolume(scenario.volume, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -558,7 +610,8 @@ func TestValidatePersistentVolumeSpec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"))
|
opts := PersistentVolumeSpecValidationOptions{}
|
||||||
|
errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -655,7 +708,8 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume)
|
opts := ValidationOptionsForPersistentVolume(scenario.newVolume, scenario.oldVolume)
|
||||||
|
errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -665,6 +719,85 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidationOptionsForPersistentVolume(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
oldPv *core.PersistentVolume
|
||||||
|
enableReadWriteOncePod bool
|
||||||
|
expectValidationOpts PersistentVolumeSpecValidationOptions
|
||||||
|
}{
|
||||||
|
"nil old pv": {
|
||||||
|
oldPv: nil,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because feature enabled": {
|
||||||
|
oldPv: pvWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop not allowed because not used and feature disabled": {
|
||||||
|
oldPv: pvWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature enabled": {
|
||||||
|
oldPv: pvWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature disabled": {
|
||||||
|
oldPv: pvWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, tc.enableReadWriteOncePod)()
|
||||||
|
|
||||||
|
opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv)
|
||||||
|
if opts != tc.expectValidationOpts {
|
||||||
|
t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pvWithAccessModes(accessModes []core.PersistentVolumeAccessMode) *core.PersistentVolume {
|
||||||
|
return &core.PersistentVolume{
|
||||||
|
Spec: core.PersistentVolumeSpec{
|
||||||
|
AccessModes: accessModes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pvcWithAccessModes(accessModes []core.PersistentVolumeAccessMode) *core.PersistentVolumeClaim {
|
||||||
|
return &core.PersistentVolumeClaim{
|
||||||
|
Spec: core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: accessModes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pvcTemplateWithAccessModes(accessModes []core.PersistentVolumeAccessMode) *core.PersistentVolumeClaimTemplate {
|
||||||
|
return &core.PersistentVolumeClaimTemplate{
|
||||||
|
Spec: core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: accessModes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference) *core.PersistentVolume {
|
func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference) *core.PersistentVolume {
|
||||||
pvCopy := pv.DeepCopy()
|
pvCopy := pv.DeepCopy()
|
||||||
if secret != nil {
|
if secret != nil {
|
||||||
@ -729,7 +862,8 @@ func TestValidateLocalVolumes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
errs := ValidatePersistentVolume(scenario.volume)
|
opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
|
||||||
|
errs := ValidatePersistentVolume(scenario.volume, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -808,7 +942,8 @@ func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
|
opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
|
||||||
|
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -909,12 +1044,14 @@ func TestAlphaVolumeSnapshotDataSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range successTestCases {
|
for _, tc := range successTestCases {
|
||||||
if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec")); len(errs) != 0 {
|
opts := PersistentVolumeClaimSpecValidationOptions{}
|
||||||
|
if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, tc := range failedTestCases {
|
for _, tc := range failedTestCases {
|
||||||
if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec")); len(errs) == 0 {
|
opts := PersistentVolumeClaimSpecValidationOptions{}
|
||||||
|
if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) == 0 {
|
||||||
t.Errorf("expected failure: %v", errs)
|
t.Errorf("expected failure: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -969,8 +1106,9 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
|
|||||||
ten := int64(10)
|
ten := int64(10)
|
||||||
|
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
claim *core.PersistentVolumeClaim
|
enableReadWriteOncePod bool
|
||||||
|
claim *core.PersistentVolumeClaim
|
||||||
}{
|
}{
|
||||||
"good-claim": {
|
"good-claim": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
@ -1118,6 +1256,42 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
|
|||||||
return claim
|
return claim
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
"with-read-write-once-pod-feature-gate-enabled": {
|
||||||
|
isExpectedFailure: false,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"with-read-write-once-pod-feature-gate-disabled": {
|
||||||
|
isExpectedFailure: true,
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"with-read-write-once-pod-and-others-feature-gate-enabled": {
|
||||||
|
isExpectedFailure: true,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
"invalid-claim-zero-capacity": {
|
"invalid-claim-zero-capacity": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
|
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
|
||||||
@ -1292,6 +1466,8 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
|
|||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, scenario.enableReadWriteOncePod)()
|
||||||
|
|
||||||
var errs field.ErrorList
|
var errs field.ErrorList
|
||||||
if ephemeral {
|
if ephemeral {
|
||||||
volumes := []core.Volume{
|
volumes := []core.Volume{
|
||||||
@ -1310,7 +1486,8 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
|
|||||||
opts := PodValidationOptions{}
|
opts := PodValidationOptions{}
|
||||||
_, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts)
|
_, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts)
|
||||||
} else {
|
} else {
|
||||||
errs = ValidatePersistentVolumeClaim(scenario.claim)
|
opts := ValidationOptionsForPersistentVolumeClaim(scenario.claim, nil)
|
||||||
|
errs = ValidatePersistentVolumeClaim(scenario.claim, opts)
|
||||||
}
|
}
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Error("Unexpected success for scenario")
|
t.Error("Unexpected success for scenario")
|
||||||
@ -1393,8 +1570,9 @@ func TestAlphaPVVolumeModeUpdate(t *testing.T) {
|
|||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
|
||||||
// ensure we have a resource version specified for updates
|
// ensure we have a resource version specified for updates
|
||||||
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
|
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -1636,6 +1814,30 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
validClaimRWOPAccessMode := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{
|
||||||
|
core.ReadWriteOncePod,
|
||||||
|
},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VolumeName: "volume",
|
||||||
|
})
|
||||||
|
|
||||||
|
validClaimRWOPAccessModeAddAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []core.PersistentVolumeAccessMode{
|
||||||
|
core.ReadWriteOncePod,
|
||||||
|
},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VolumeName: "volume",
|
||||||
|
})
|
||||||
|
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
oldClaim *core.PersistentVolumeClaim
|
oldClaim *core.PersistentVolumeClaim
|
||||||
@ -1804,6 +2006,12 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
newClaim: validClaimStorageClass,
|
newClaim: validClaimStorageClass,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
},
|
},
|
||||||
|
"valid-update-rwop-used-and-rwop-feature-disabled": {
|
||||||
|
isExpectedFailure: false,
|
||||||
|
oldClaim: validClaimRWOPAccessMode,
|
||||||
|
newClaim: validClaimRWOPAccessModeAddAnnotation,
|
||||||
|
enableResize: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
@ -1812,7 +2020,8 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, scenario.enableResize)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, scenario.enableResize)()
|
||||||
scenario.oldClaim.ResourceVersion = "1"
|
scenario.oldClaim.ResourceVersion = "1"
|
||||||
scenario.newClaim.ResourceVersion = "1"
|
scenario.newClaim.ResourceVersion = "1"
|
||||||
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
|
opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
|
||||||
|
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim, opts)
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success for scenario: %s", name)
|
t.Errorf("Unexpected success for scenario: %s", name)
|
||||||
}
|
}
|
||||||
@ -1823,6 +2032,116 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
oldPvc *core.PersistentVolumeClaim
|
||||||
|
enableReadWriteOncePod bool
|
||||||
|
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
|
||||||
|
}{
|
||||||
|
"nil pv": {
|
||||||
|
oldPvc: nil,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because feature enabled": {
|
||||||
|
oldPvc: pvcWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop not allowed because not used and feature disabled": {
|
||||||
|
oldPvc: pvcWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature enabled": {
|
||||||
|
oldPvc: pvcWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature disabled": {
|
||||||
|
oldPvc: pvcWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, tc.enableReadWriteOncePod)()
|
||||||
|
|
||||||
|
opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc)
|
||||||
|
if opts != tc.expectValidationOpts {
|
||||||
|
t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
oldPvcTemplate *core.PersistentVolumeClaimTemplate
|
||||||
|
enableReadWriteOncePod bool
|
||||||
|
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
|
||||||
|
}{
|
||||||
|
"nil pv": {
|
||||||
|
oldPvcTemplate: nil,
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because feature enabled": {
|
||||||
|
oldPvcTemplate: pvcTemplateWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop not allowed because not used and feature disabled": {
|
||||||
|
oldPvcTemplate: pvcTemplateWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOnce}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature enabled": {
|
||||||
|
oldPvcTemplate: pvcTemplateWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: true,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rwop allowed because used and feature disabled": {
|
||||||
|
oldPvcTemplate: pvcTemplateWithAccessModes([]core.PersistentVolumeAccessMode{core.ReadWriteOncePod}),
|
||||||
|
enableReadWriteOncePod: false,
|
||||||
|
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
|
||||||
|
AllowReadWriteOncePod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, tc.enableReadWriteOncePod)()
|
||||||
|
|
||||||
|
opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate)
|
||||||
|
if opts != tc.expectValidationOpts {
|
||||||
|
t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateKeyToPath(t *testing.T) {
|
func TestValidateKeyToPath(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
kp core.KeyToPath
|
kp core.KeyToPath
|
||||||
@ -4307,7 +4626,8 @@ func TestPVCVolumeMode(t *testing.T) {
|
|||||||
"valid nil value": createTestVolModePVC(nil),
|
"valid nil value": createTestVolModePVC(nil),
|
||||||
}
|
}
|
||||||
for k, v := range successCasesPVC {
|
for k, v := range successCasesPVC {
|
||||||
if errs := ValidatePersistentVolumeClaim(v); len(errs) != 0 {
|
opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
|
||||||
|
if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 {
|
||||||
t.Errorf("expected success for %s", k)
|
t.Errorf("expected success for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4318,7 +4638,8 @@ func TestPVCVolumeMode(t *testing.T) {
|
|||||||
"empty value": createTestVolModePVC(&empty),
|
"empty value": createTestVolModePVC(&empty),
|
||||||
}
|
}
|
||||||
for k, v := range errorCasesPVC {
|
for k, v := range errorCasesPVC {
|
||||||
if errs := ValidatePersistentVolumeClaim(v); len(errs) == 0 {
|
opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
|
||||||
|
if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %s", k)
|
t.Errorf("expected failure for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4337,7 +4658,8 @@ func TestPVVolumeMode(t *testing.T) {
|
|||||||
"valid nil value": createTestVolModePV(nil),
|
"valid nil value": createTestVolModePV(nil),
|
||||||
}
|
}
|
||||||
for k, v := range successCasesPV {
|
for k, v := range successCasesPV {
|
||||||
if errs := ValidatePersistentVolume(v); len(errs) != 0 {
|
opts := ValidationOptionsForPersistentVolume(v, nil)
|
||||||
|
if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 {
|
||||||
t.Errorf("expected success for %s", k)
|
t.Errorf("expected success for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4348,7 +4670,8 @@ func TestPVVolumeMode(t *testing.T) {
|
|||||||
"empty value": createTestVolModePV(&empty),
|
"empty value": createTestVolModePV(&empty),
|
||||||
}
|
}
|
||||||
for k, v := range errorCasesPV {
|
for k, v := range errorCasesPV {
|
||||||
if errs := ValidatePersistentVolume(v); len(errs) == 0 {
|
opts := ValidationOptionsForPersistentVolume(v, nil)
|
||||||
|
if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %s", k)
|
t.Errorf("expected failure for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -16569,12 +16892,14 @@ func TestAlphaVolumePVCDataSource(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
if tc.expectedFail {
|
if tc.expectedFail {
|
||||||
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec")); len(errs) == 0 {
|
opts := PersistentVolumeClaimSpecValidationOptions{}
|
||||||
|
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
|
||||||
t.Errorf("expected failure: %v", errs)
|
t.Errorf("expected failure: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec")); len(errs) != 0 {
|
opts := PersistentVolumeClaimSpecValidationOptions{}
|
||||||
|
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,8 @@ func validateVolumeAttachmentSource(source *storage.VolumeAttachmentSource, fldP
|
|||||||
allErrs = append(allErrs, field.Required(fldPath.Child("persistentVolumeName"), "must specify non empty persistentVolumeName"))
|
allErrs = append(allErrs, field.Required(fldPath.Child("persistentVolumeName"), "must specify non empty persistentVolumeName"))
|
||||||
}
|
}
|
||||||
case source.InlineVolumeSpec != nil:
|
case source.InlineVolumeSpec != nil:
|
||||||
allErrs = append(allErrs, apivalidation.ValidatePersistentVolumeSpec(source.InlineVolumeSpec, "", true, fldPath.Child("inlineVolumeSpec"))...)
|
opts := apivalidation.PersistentVolumeSpecValidationOptions{}
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidatePersistentVolumeSpec(source.InlineVolumeSpec, "", true, fldPath.Child("inlineVolumeSpec"), opts)...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requ
|
|||||||
keys := pvIndex.store.ListIndexFuncValues("accessmodes")
|
keys := pvIndex.store.ListIndexFuncValues("accessmodes")
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
indexedModes := v1helper.GetAccessModesFromString(key)
|
indexedModes := v1helper.GetAccessModesFromString(key)
|
||||||
if volumeutil.AccessModesContainedInAll(indexedModes, requestedModes) {
|
if volumeutil.ContainsAllAccessModes(indexedModes, requestedModes) {
|
||||||
matchedModes = append(matchedModes, indexedModes)
|
matchedModes = append(matchedModes, indexedModes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ func TestAllPossibleAccessModes(t *testing.T) {
|
|||||||
t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
|
t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
|
||||||
}
|
}
|
||||||
for _, m := range possibleModes {
|
for _, m := range possibleModes {
|
||||||
if !util.AccessModesContains(m, v1.ReadWriteOnce) {
|
if !util.ContainsAccessMode(m, v1.ReadWriteOnce) {
|
||||||
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,7 +327,7 @@ func TestAllPossibleAccessModes(t *testing.T) {
|
|||||||
if len(possibleModes) != 1 {
|
if len(possibleModes) != 1 {
|
||||||
t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
|
t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
|
||||||
}
|
}
|
||||||
if !util.AccessModesContains(possibleModes[0], v1.ReadWriteMany) {
|
if !util.ContainsAccessMode(possibleModes[0], v1.ReadWriteMany) {
|
||||||
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,6 +727,12 @@ const (
|
|||||||
//
|
//
|
||||||
// Enables the PodSecurity admission plugin
|
// Enables the PodSecurity admission plugin
|
||||||
PodSecurity featuregate.Feature = "PodSecurity"
|
PodSecurity featuregate.Feature = "PodSecurity"
|
||||||
|
|
||||||
|
// owner: @chrishenzie
|
||||||
|
// alpha: v1.22
|
||||||
|
//
|
||||||
|
// Enables usage of the ReadWriteOncePod PersistentVolume access mode.
|
||||||
|
ReadWriteOncePod featuregate.Feature = "ReadWriteOncePod"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -836,6 +842,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
ExpandedDNSConfig: {Default: false, PreRelease: featuregate.Alpha},
|
ExpandedDNSConfig: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
SeccompDefault: {Default: false, PreRelease: featuregate.Alpha},
|
SeccompDefault: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
PodSecurity: {Default: false, PreRelease: featuregate.Alpha},
|
PodSecurity: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ReadWriteOncePod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
@ -425,6 +425,26 @@ func (asw *actualStateOfWorld) GetVolumeMountState(volumeName v1.UniqueVolumeNam
|
|||||||
return podObj.volumeMountStateForPod
|
return podObj.volumeMountStateForPod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) IsVolumeMountedElsewhere(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, podObj := range volumeObj.mountedPods {
|
||||||
|
if podName != podObj.podName {
|
||||||
|
// Treat uncertain mount state as mounted until certain.
|
||||||
|
if podObj.volumeMountStateForPod != operationexecutor.VolumeNotMounted {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// addVolume adds the given volume to the cache indicating the specified
|
// addVolume adds the given volume to the cache indicating the specified
|
||||||
// volume is attached to this node. If no volume name is supplied, a unique
|
// volume is attached to this node. If no volume name is supplied, a unique
|
||||||
// volume name is generated from the volumeSpec and returned on success. If a
|
// volume name is generated from the volumeSpec and returned on success. If a
|
||||||
|
@ -241,6 +241,7 @@ func Test_AddPodToVolume_Positive_ExistingVolumeNewNode(t *testing.T) {
|
|||||||
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
||||||
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates data struct with a volume
|
// Populates data struct with a volume
|
||||||
@ -321,6 +322,7 @@ func Test_AddPodToVolume_Positive_ExistingVolumeExistingNode(t *testing.T) {
|
|||||||
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
||||||
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates data struct with a volume
|
// Populates data struct with a volume
|
||||||
@ -451,6 +453,8 @@ func Test_AddTwoPodsToVolume_Positive(t *testing.T) {
|
|||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName2, volumeSpec2.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName2, volumeSpec2.Name(), asw)
|
||||||
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
|
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
|
||||||
verifyVolumeSpecNameInVolumeAsw(t, podName2, []*volume.Spec{volumeSpec2}, asw)
|
verifyVolumeSpecNameInVolumeAsw(t, podName2, []*volume.Spec{volumeSpec2}, asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName1, generatedVolumeName1, true /*expectedMountedElsewhere */, asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName2, generatedVolumeName2, true /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calls AddPodToVolume() to add pod to empty data struct
|
// Calls AddPodToVolume() to add pod to empty data struct
|
||||||
@ -488,6 +492,10 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
|||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generatedVolumeName, err := util.GetUniqueVolumeNameFromSpec(
|
||||||
|
plugin, volumeSpec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
blockplugin, err := volumePluginMgr.FindMapperPluginBySpec(volumeSpec)
|
blockplugin, err := volumePluginMgr.FindMapperPluginBySpec(volumeSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@ -538,6 +546,7 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
|||||||
false, /* expectVolumeToExist */
|
false, /* expectVolumeToExist */
|
||||||
asw)
|
asw)
|
||||||
verifyVolumeDoesntExistWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeDoesntExistWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calls MarkVolumeAsAttached() once to add volume
|
// Calls MarkVolumeAsAttached() once to add volume
|
||||||
@ -773,6 +782,21 @@ func verifyPodExistsInVolumeAsw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyVolumeMountedElsewhere(
|
||||||
|
t *testing.T,
|
||||||
|
expectedPodName volumetypes.UniquePodName,
|
||||||
|
expectedVolumeName v1.UniqueVolumeName,
|
||||||
|
expectedMountedElsewhere bool,
|
||||||
|
asw ActualStateOfWorld) {
|
||||||
|
mountedElsewhere := asw.IsVolumeMountedElsewhere(expectedVolumeName, expectedPodName)
|
||||||
|
if mountedElsewhere != expectedMountedElsewhere {
|
||||||
|
t.Fatalf(
|
||||||
|
"IsVolumeMountedElsewhere assertion failure. Expected : <%t> Actual: <%t>",
|
||||||
|
expectedMountedElsewhere,
|
||||||
|
mountedElsewhere)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func verifyPodDoesntExistInVolumeAsw(
|
func verifyPodDoesntExistInVolumeAsw(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
podToCheck volumetypes.UniquePodName,
|
podToCheck volumetypes.UniquePodName,
|
||||||
|
@ -71,7 +71,8 @@ func (persistentvolumeStrategy) PrepareForCreate(ctx context.Context, obj runtim
|
|||||||
|
|
||||||
func (persistentvolumeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (persistentvolumeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
persistentvolume := obj.(*api.PersistentVolume)
|
persistentvolume := obj.(*api.PersistentVolume)
|
||||||
errorList := validation.ValidatePersistentVolume(persistentvolume)
|
opts := validation.ValidationOptionsForPersistentVolume(persistentvolume, nil)
|
||||||
|
errorList := validation.ValidatePersistentVolume(persistentvolume, opts)
|
||||||
return append(errorList, volumevalidation.ValidatePersistentVolume(persistentvolume)...)
|
return append(errorList, volumevalidation.ValidatePersistentVolume(persistentvolume)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,9 +100,11 @@ func (persistentvolumeStrategy) PrepareForUpdate(ctx context.Context, obj, old r
|
|||||||
|
|
||||||
func (persistentvolumeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (persistentvolumeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
newPv := obj.(*api.PersistentVolume)
|
newPv := obj.(*api.PersistentVolume)
|
||||||
errorList := validation.ValidatePersistentVolume(newPv)
|
oldPv := old.(*api.PersistentVolume)
|
||||||
|
opts := validation.ValidationOptionsForPersistentVolume(newPv, oldPv)
|
||||||
|
errorList := validation.ValidatePersistentVolume(newPv, opts)
|
||||||
errorList = append(errorList, volumevalidation.ValidatePersistentVolume(newPv)...)
|
errorList = append(errorList, volumevalidation.ValidatePersistentVolume(newPv)...)
|
||||||
return append(errorList, validation.ValidatePersistentVolumeUpdate(newPv, old.(*api.PersistentVolume))...)
|
return append(errorList, validation.ValidatePersistentVolumeUpdate(newPv, oldPv, opts)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnUpdate returns warnings for the given update.
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
|
@ -72,7 +72,8 @@ func (persistentvolumeclaimStrategy) PrepareForCreate(ctx context.Context, obj r
|
|||||||
|
|
||||||
func (persistentvolumeclaimStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (persistentvolumeclaimStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
pvc := obj.(*api.PersistentVolumeClaim)
|
pvc := obj.(*api.PersistentVolumeClaim)
|
||||||
return validation.ValidatePersistentVolumeClaim(pvc)
|
opts := validation.ValidationOptionsForPersistentVolumeClaim(pvc, nil)
|
||||||
|
return validation.ValidatePersistentVolumeClaim(pvc, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
@ -98,8 +99,11 @@ func (persistentvolumeclaimStrategy) PrepareForUpdate(ctx context.Context, obj,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
errorList := validation.ValidatePersistentVolumeClaim(obj.(*api.PersistentVolumeClaim))
|
newPvc := obj.(*api.PersistentVolumeClaim)
|
||||||
return append(errorList, validation.ValidatePersistentVolumeClaimUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim))...)
|
oldPvc := old.(*api.PersistentVolumeClaim)
|
||||||
|
opts := validation.ValidationOptionsForPersistentVolumeClaim(newPvc, oldPvc)
|
||||||
|
errorList := validation.ValidatePersistentVolumeClaim(newPvc, opts)
|
||||||
|
return append(errorList, validation.ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc, opts)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnUpdate returns warnings for the given update.
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
|
@ -485,7 +485,7 @@ type awsElasticBlockStoreProvisioner struct {
|
|||||||
var _ volume.Provisioner = &awsElasticBlockStoreProvisioner{}
|
var _ volume.Provisioner = &awsElasticBlockStoreProvisioner{}
|
||||||
|
|
||||||
func (c *awsElasticBlockStoreProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *awsElasticBlockStoreProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ type azureFileProvisioner struct {
|
|||||||
var _ volume.Provisioner = &azureFileProvisioner{}
|
var _ volume.Provisioner = &azureFileProvisioner{}
|
||||||
|
|
||||||
func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(a.plugin.GetAccessModes(), a.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(a.plugin.GetAccessModes(), a.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", a.options.PVC.Spec.AccessModes, a.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", a.options.PVC.Spec.AccessModes, a.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
if util.CheckPersistentVolumeClaimModeBlock(a.options.PVC) {
|
if util.CheckPersistentVolumeClaimModeBlock(a.options.PVC) {
|
||||||
|
@ -185,7 +185,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
|||||||
supportedModes := p.plugin.GetAccessModes()
|
supportedModes := p.plugin.GetAccessModes()
|
||||||
if maxShares < 2 {
|
if maxShares < 2 {
|
||||||
// only do AccessModes validation when maxShares < 2
|
// only do AccessModes validation when maxShares < 2
|
||||||
if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported with maxShares(%d) < 2", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes(), maxShares)
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported with maxShares(%d) < 2", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes(), maxShares)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +561,7 @@ type cinderVolumeProvisioner struct {
|
|||||||
var _ volume.Provisioner = &cinderVolumeProvisioner{}
|
var _ volume.Provisioner = &cinderVolumeProvisioner{}
|
||||||
|
|
||||||
func (c *cinderVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *cinderVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ type csiClient interface {
|
|||||||
NodeSupportsStageUnstage(ctx context.Context) (bool, error)
|
NodeSupportsStageUnstage(ctx context.Context) (bool, error)
|
||||||
NodeSupportsNodeExpand(ctx context.Context) (bool, error)
|
NodeSupportsNodeExpand(ctx context.Context) (bool, error)
|
||||||
NodeSupportsVolumeStats(ctx context.Context) (bool, error)
|
NodeSupportsVolumeStats(ctx context.Context) (bool, error)
|
||||||
|
NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strongly typed address
|
// Strongly typed address
|
||||||
@ -120,6 +121,8 @@ type nodeV1ClientCreator func(addr csiAddr, metricsManager *MetricsManager) (
|
|||||||
err error,
|
err error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type nodeV1AccessModeMapper func(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapability_AccessMode_Mode
|
||||||
|
|
||||||
// newV1NodeClient creates a new NodeClient with the internally used gRPC
|
// newV1NodeClient creates a new NodeClient with the internally used gRPC
|
||||||
// connection set up. It also returns a closer which must to be called to close
|
// connection set up. It also returns a closer which must to be called to close
|
||||||
// the gRPC connection when the NodeClient is not used anymore.
|
// the gRPC connection when the NodeClient is not used anymore.
|
||||||
@ -217,7 +220,11 @@ func (c *csiDriverClient) NodePublishVolume(
|
|||||||
|
|
||||||
if c.nodeV1ClientCreator == nil {
|
if c.nodeV1ClientCreator == nil {
|
||||||
return errors.New("failed to call NodePublishVolume. nodeV1ClientCreator is nil")
|
return errors.New("failed to call NodePublishVolume. nodeV1ClientCreator is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
accessModeMapper, err := c.getNodeV1AccessModeMapper(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
||||||
@ -235,7 +242,7 @@ func (c *csiDriverClient) NodePublishVolume(
|
|||||||
Secrets: secrets,
|
Secrets: secrets,
|
||||||
VolumeCapability: &csipbv1.VolumeCapability{
|
VolumeCapability: &csipbv1.VolumeCapability{
|
||||||
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
||||||
Mode: asCSIAccessModeV1(accessMode),
|
Mode: accessModeMapper(accessMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -279,6 +286,11 @@ func (c *csiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOp
|
|||||||
return opts.newSize, errors.New("size can not be less than 0")
|
return opts.newSize, errors.New("size can not be less than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessModeMapper, err := c.getNodeV1AccessModeMapper(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return opts.newSize, err
|
||||||
|
}
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return opts.newSize, err
|
return opts.newSize, err
|
||||||
@ -291,7 +303,7 @@ func (c *csiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOp
|
|||||||
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: opts.newSize.Value()},
|
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: opts.newSize.Value()},
|
||||||
VolumeCapability: &csipbv1.VolumeCapability{
|
VolumeCapability: &csipbv1.VolumeCapability{
|
||||||
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
||||||
Mode: asCSIAccessModeV1(opts.accessMode),
|
Mode: accessModeMapper(opts.accessMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -371,6 +383,11 @@ func (c *csiDriverClient) NodeStageVolume(ctx context.Context,
|
|||||||
return errors.New("nodeV1ClientCreate is nil")
|
return errors.New("nodeV1ClientCreate is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessModeMapper, err := c.getNodeV1AccessModeMapper(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -383,7 +400,7 @@ func (c *csiDriverClient) NodeStageVolume(ctx context.Context,
|
|||||||
StagingTargetPath: stagingTargetPath,
|
StagingTargetPath: stagingTargetPath,
|
||||||
VolumeCapability: &csipbv1.VolumeCapability{
|
VolumeCapability: &csipbv1.VolumeCapability{
|
||||||
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
||||||
Mode: asCSIAccessModeV1(accessMode),
|
Mode: accessModeMapper(accessMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Secrets: secrets,
|
Secrets: secrets,
|
||||||
@ -438,66 +455,23 @@ func (c *csiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stagingT
|
|||||||
|
|
||||||
func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
|
func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
|
||||||
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if Node has EXPAND_VOLUME capability"))
|
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if Node has EXPAND_VOLUME capability"))
|
||||||
if c.nodeV1ClientCreator == nil {
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME)
|
||||||
return false, errors.New("nodeV1ClientCreate is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
|
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
|
||||||
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
|
func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
|
||||||
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage"))
|
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage"))
|
||||||
if c.nodeV1ClientCreator == nil {
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
|
||||||
return false, errors.New("nodeV1ClientCreate is nil")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
func (c *csiDriverClient) getNodeV1AccessModeMapper(ctx context.Context) (nodeV1AccessModeMapper, error) {
|
||||||
|
supported, err := c.NodeSupportsSingleNodeMultiWriterAccessMode(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer closer.Close()
|
if supported {
|
||||||
|
return asSingleNodeMultiWriterCapableCSIAccessModeV1, nil
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
|
||||||
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
return asCSIAccessModeV1, nil
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
|
|
||||||
stageUnstageSet := false
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME {
|
|
||||||
stageUnstageSet = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stageUnstageSet, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func asCSIAccessModeV1(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapability_AccessMode_Mode {
|
func asCSIAccessModeV1(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapability_AccessMode_Mode {
|
||||||
@ -508,6 +482,25 @@ func asCSIAccessModeV1(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapabili
|
|||||||
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY
|
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY
|
||||||
case api.ReadWriteMany:
|
case api.ReadWriteMany:
|
||||||
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER
|
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER
|
||||||
|
// This mapping exists to enable CSI drivers that lack the
|
||||||
|
// SINGLE_NODE_MULTI_WRITER capability to work with the
|
||||||
|
// ReadWriteOncePod access mode.
|
||||||
|
case api.ReadWriteOncePod:
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER
|
||||||
|
}
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
func asSingleNodeMultiWriterCapableCSIAccessModeV1(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapability_AccessMode_Mode {
|
||||||
|
switch am {
|
||||||
|
case api.ReadWriteOnce:
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER
|
||||||
|
case api.ReadOnlyMany:
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY
|
||||||
|
case api.ReadWriteMany:
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER
|
||||||
|
case api.ReadWriteOncePod:
|
||||||
|
return csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER
|
||||||
}
|
}
|
||||||
return csipbv1.VolumeCapability_AccessMode_UNKNOWN
|
return csipbv1.VolumeCapability_AccessMode_UNKNOWN
|
||||||
}
|
}
|
||||||
@ -561,30 +554,12 @@ func (c *csiClientGetter) Get() (csiClient, error) {
|
|||||||
|
|
||||||
func (c *csiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
|
func (c *csiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
|
||||||
klog.V(5).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsVolumeStats"))
|
klog.V(5).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsVolumeStats"))
|
||||||
if c.nodeV1ClientCreator == nil {
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS)
|
||||||
return false, errors.New("nodeV1ClientCreate is nil")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
func (c *csiDriverClient) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) {
|
||||||
if err != nil {
|
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsSingleNodeMultiWriterAccessMode"))
|
||||||
return false, err
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER)
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
|
||||||
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *csiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string, targetPath string) (*volume.Metrics, error) {
|
func (c *csiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string, targetPath string) (*volume.Metrics, error) {
|
||||||
@ -628,7 +603,7 @@ func (c *csiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.CSIVolumeHealth) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSIVolumeHealth) {
|
||||||
isSupportNodeVolumeCondition, err := supportNodeGetVolumeCondition(ctx, nodeClient)
|
isSupportNodeVolumeCondition, err := c.nodeSupportsVolumeCondition(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -661,30 +636,47 @@ func (c *csiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string,
|
|||||||
return metrics, nil
|
return metrics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func supportNodeGetVolumeCondition(ctx context.Context, nodeClient csipbv1.NodeClient) (supportNodeGetVolumeCondition bool, err error) {
|
func (c *csiDriverClient) nodeSupportsVolumeCondition(ctx context.Context) (bool, error) {
|
||||||
req := csipbv1.NodeGetCapabilitiesRequest{}
|
klog.V(5).Info(log("calling NodeGetCapabilities rpc to determine if nodeSupportsVolumeCondition"))
|
||||||
rsp, err := nodeClient.NodeGetCapabilities(ctx, &req)
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *csiDriverClient) nodeSupportsCapability(ctx context.Context, capabilityType csipbv1.NodeServiceCapability_RPC_Type) (bool, error) {
|
||||||
|
capabilities, err := c.nodeGetCapabilities(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cap := range rsp.GetCapabilities() {
|
for _, capability := range capabilities {
|
||||||
if cap == nil {
|
if capability == nil || capability.GetRpc() == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rpc := cap.GetRpc()
|
if capability.GetRpc().GetType() == capabilityType {
|
||||||
if rpc == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t := rpc.GetType()
|
|
||||||
if t == csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION {
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *csiDriverClient) nodeGetCapabilities(ctx context.Context) ([]*csipbv1.NodeServiceCapability, error) {
|
||||||
|
if c.nodeV1ClientCreator == nil {
|
||||||
|
return []*csipbv1.NodeServiceCapability{}, errors.New("nodeV1ClientCreate is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr, c.metricsManager)
|
||||||
|
if err != nil {
|
||||||
|
return []*csipbv1.NodeServiceCapability{}, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
||||||
|
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return []*csipbv1.NodeServiceCapability{}, err
|
||||||
|
}
|
||||||
|
return resp.GetCapabilities(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func isFinalError(err error) bool {
|
func isFinalError(err error) bool {
|
||||||
// Sources:
|
// Sources:
|
||||||
// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
|
// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
|
||||||
|
@ -106,7 +106,7 @@ func (c *fakeCsiDriverClient) NodeGetVolumeStats(ctx context.Context, volID stri
|
|||||||
|
|
||||||
metrics := &volume.Metrics{}
|
metrics := &volume.Metrics{}
|
||||||
|
|
||||||
isSupportNodeVolumeCondition, err := supportNodeGetVolumeCondition(ctx, c.nodeClient)
|
isSupportNodeVolumeCondition, err := c.nodeSupportsVolumeCondition(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -137,21 +137,7 @@ func (c *fakeCsiDriverClient) NodeGetVolumeStats(ctx context.Context, volID stri
|
|||||||
|
|
||||||
func (c *fakeCsiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
|
func (c *fakeCsiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
|
||||||
c.t.Log("calling fake.NodeSupportsVolumeStats...")
|
c.t.Log("calling fake.NodeSupportsVolumeStats...")
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS)
|
||||||
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeCsiDriverClient) NodePublishVolume(
|
func (c *fakeCsiDriverClient) NodePublishVolume(
|
||||||
@ -269,46 +255,12 @@ func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stag
|
|||||||
|
|
||||||
func (c *fakeCsiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
|
func (c *fakeCsiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
|
||||||
c.t.Log("calling fake.NodeSupportsNodeExpand...")
|
c.t.Log("calling fake.NodeSupportsNodeExpand...")
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME)
|
||||||
|
|
||||||
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
|
func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
|
||||||
c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...")
|
c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...")
|
||||||
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
|
||||||
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
capabilities := resp.GetCapabilities()
|
|
||||||
|
|
||||||
stageUnstageSet := false
|
|
||||||
if capabilities == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for _, capability := range capabilities {
|
|
||||||
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME {
|
|
||||||
stageUnstageSet = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stageUnstageSet, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOptions) (resource.Quantity, error) {
|
func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOptions) (resource.Quantity, error) {
|
||||||
@ -344,6 +296,39 @@ func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResi
|
|||||||
return *updatedQuantity, nil
|
return *updatedQuantity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *fakeCsiDriverClient) nodeSupportsVolumeCondition(ctx context.Context) (bool, error) {
|
||||||
|
c.t.Log("calling fake.nodeSupportsVolumeCondition...")
|
||||||
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeCsiDriverClient) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) {
|
||||||
|
c.t.Log("calling fake.NodeSupportsSingleNodeMultiWriterAccessMode...")
|
||||||
|
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeCsiDriverClient) nodeSupportsCapability(ctx context.Context, capabilityType csipbv1.NodeServiceCapability_RPC_Type) (bool, error) {
|
||||||
|
capabilities, err := c.nodeGetCapabilities(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, capability := range capabilities {
|
||||||
|
if capability.GetRpc().GetType() == capabilityType {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeCsiDriverClient) nodeGetCapabilities(ctx context.Context) ([]*csipbv1.NodeServiceCapability, error) {
|
||||||
|
req := &csipbv1.NodeGetCapabilitiesRequest{}
|
||||||
|
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return []*csipbv1.NodeServiceCapability{}, err
|
||||||
|
}
|
||||||
|
return resp.GetCapabilities(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
|
func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
|
||||||
return newFakeCsiDriverClient(t, stageUnstageSet)
|
return newFakeCsiDriverClient(t, stageUnstageSet)
|
||||||
}
|
}
|
||||||
@ -885,3 +870,83 @@ func TestVolumeStats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccessModeMapping(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
singleNodeMultiWriterSet bool
|
||||||
|
accessMode api.PersistentVolumeAccessMode
|
||||||
|
expectedMappedAccessMode csipbv1.VolumeCapability_AccessMode_Mode
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with ReadWriteOnce and incapable driver",
|
||||||
|
singleNodeMultiWriterSet: false,
|
||||||
|
accessMode: api.ReadWriteOnce,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadOnlyMany and incapable driver",
|
||||||
|
singleNodeMultiWriterSet: false,
|
||||||
|
accessMode: api.ReadOnlyMany,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadWriteMany and incapable driver",
|
||||||
|
singleNodeMultiWriterSet: false,
|
||||||
|
accessMode: api.ReadWriteMany,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadWriteOncePod and incapable driver",
|
||||||
|
singleNodeMultiWriterSet: false,
|
||||||
|
accessMode: api.ReadWriteOncePod,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadWriteOnce and capable driver",
|
||||||
|
singleNodeMultiWriterSet: true,
|
||||||
|
accessMode: api.ReadWriteOnce,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadOnlyMany and capable driver",
|
||||||
|
singleNodeMultiWriterSet: true,
|
||||||
|
accessMode: api.ReadOnlyMany,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadWriteMany and capable driver",
|
||||||
|
singleNodeMultiWriterSet: true,
|
||||||
|
accessMode: api.ReadWriteMany,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with ReadWriteOncePod and capable driver",
|
||||||
|
singleNodeMultiWriterSet: true,
|
||||||
|
accessMode: api.ReadWriteOncePod,
|
||||||
|
expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fakeCloser := fake.NewCloser(t)
|
||||||
|
client := &csiDriverClient{
|
||||||
|
driverName: "Fake Driver Name",
|
||||||
|
nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
|
||||||
|
nodeClient := fake.NewNodeClientWithSingleNodeMultiWriter(tc.singleNodeMultiWriterSet)
|
||||||
|
return nodeClient, fakeCloser, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
accessModeMapper, err := client.getNodeV1AccessModeMapper(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedAccessMode := accessModeMapper(tc.accessMode)
|
||||||
|
if mappedAccessMode != tc.expectedMappedAccessMode {
|
||||||
|
t.Errorf("expected access mode: %v; got: %v", tc.expectedMappedAccessMode, mappedAccessMode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -85,6 +85,7 @@ type NodeClient struct {
|
|||||||
expansionSet bool
|
expansionSet bool
|
||||||
volumeStatsSet bool
|
volumeStatsSet bool
|
||||||
volumeConditionSet bool
|
volumeConditionSet bool
|
||||||
|
singleNodeMultiWriterSet bool
|
||||||
nodeGetInfoResp *csipb.NodeGetInfoResponse
|
nodeGetInfoResp *csipb.NodeGetInfoResponse
|
||||||
nodeVolumeStatsResp *csipb.NodeGetVolumeStatsResponse
|
nodeVolumeStatsResp *csipb.NodeGetVolumeStatsResponse
|
||||||
FakeNodeExpansionRequest *csipb.NodeExpandVolumeRequest
|
FakeNodeExpansionRequest *csipb.NodeExpandVolumeRequest
|
||||||
@ -123,6 +124,16 @@ func NewNodeClientWithVolumeStatsAndCondition(volumeStatsSet, volumeConditionSet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewNodeClientWithSingleNodeMultiWriter(singleNodeMultiWriterSet bool) *NodeClient {
|
||||||
|
return &NodeClient{
|
||||||
|
nodePublishedVolumes: make(map[string]CSIVolume),
|
||||||
|
nodeStagedVolumes: make(map[string]CSIVolume),
|
||||||
|
stageUnstageSet: true,
|
||||||
|
volumeStatsSet: true,
|
||||||
|
singleNodeMultiWriterSet: singleNodeMultiWriterSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetNextError injects next expected error
|
// SetNextError injects next expected error
|
||||||
func (f *NodeClient) SetNextError(err error) {
|
func (f *NodeClient) SetNextError(err error) {
|
||||||
f.nextErr = err
|
f.nextErr = err
|
||||||
@ -364,6 +375,16 @@ func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetC
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.singleNodeMultiWriterSet {
|
||||||
|
resp.Capabilities = append(resp.Capabilities, &csipb.NodeServiceCapability{
|
||||||
|
Type: &csipb.NodeServiceCapability_Rpc{
|
||||||
|
Rpc: &csipb.NodeServiceCapability_RPC{
|
||||||
|
Type: csipb.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ type flockerVolumeProvisioner struct {
|
|||||||
var _ volume.Provisioner = &flockerVolumeProvisioner{}
|
var _ volume.Provisioner = &flockerVolumeProvisioner{}
|
||||||
|
|
||||||
func (c *flockerVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *flockerVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +488,7 @@ type gcePersistentDiskProvisioner struct {
|
|||||||
var _ volume.Provisioner = &gcePersistentDiskProvisioner{}
|
var _ volume.Provisioner = &gcePersistentDiskProvisioner{}
|
||||||
|
|
||||||
func (c *gcePersistentDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *gcePersistentDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -720,7 +720,7 @@ func filterClient(client *gcli.Client, opts *proxyutil.FilteredDialOptions) *gcl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *glusterfsVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (p *glusterfsVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !volutil.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
if !volutil.ContainsAllAccessModes(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
if p.options.PVC.Spec.Selector != nil {
|
if p.options.PVC.Spec.Selector != nil {
|
||||||
|
@ -389,7 +389,7 @@ type portworxVolumeProvisioner struct {
|
|||||||
var _ volume.Provisioner = &portworxVolumeProvisioner{}
|
var _ volume.Provisioner = &portworxVolumeProvisioner{}
|
||||||
|
|
||||||
func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +373,7 @@ type quobyteVolumeProvisioner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *quobyteVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (provisioner *quobyteVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(provisioner.plugin.GetAccessModes(), provisioner.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(provisioner.plugin.GetAccessModes(), provisioner.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,7 +615,7 @@ type rbdVolumeProvisioner struct {
|
|||||||
var _ volume.Provisioner = &rbdVolumeProvisioner{}
|
var _ volume.Provisioner = &rbdVolumeProvisioner{}
|
||||||
|
|
||||||
func (r *rbdVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (r *rbdVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !volutil.AccessModesContainedInAll(r.plugin.GetAccessModes(), r.options.PVC.Spec.AccessModes) {
|
if !volutil.ContainsAllAccessModes(r.plugin.GetAccessModes(), r.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", r.options.PVC.Spec.AccessModes, r.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", r.options.PVC.Spec.AccessModes, r.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +570,7 @@ type storageosProvisioner struct {
|
|||||||
var _ volume.Provisioner = &storageosProvisioner{}
|
var _ volume.Provisioner = &storageosProvisioner{}
|
||||||
|
|
||||||
func (c *storageosProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (c *storageosProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) {
|
if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) {
|
||||||
|
@ -203,6 +203,9 @@ type ActualStateOfWorldMounterUpdater interface {
|
|||||||
// GetVolumeMountState returns mount state of the volume for the Pod
|
// GetVolumeMountState returns mount state of the volume for the Pod
|
||||||
GetVolumeMountState(volumName v1.UniqueVolumeName, podName volumetypes.UniquePodName) VolumeMountState
|
GetVolumeMountState(volumName v1.UniqueVolumeName, podName volumetypes.UniquePodName) VolumeMountState
|
||||||
|
|
||||||
|
// IsVolumeMountedElsewhere returns whether the supplied volume is mounted in a Pod other than the supplied one
|
||||||
|
IsVolumeMountedElsewhere(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
|
||||||
|
|
||||||
// MarkForInUseExpansionError marks the volume to have in-use error during expansion.
|
// MarkForInUseExpansionError marks the volume to have in-use error during expansion.
|
||||||
// volume expansion must not be retried for this volume
|
// volume expansion must not be retried for this volume
|
||||||
MarkForInUseExpansionError(volumeName v1.UniqueVolumeName)
|
MarkForInUseExpansionError(volumeName v1.UniqueVolumeName)
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
volerr "k8s.io/cloud-provider/volume/errors"
|
volerr "k8s.io/cloud-provider/volume/errors"
|
||||||
csitrans "k8s.io/csi-translation-lib"
|
csitrans "k8s.io/csi-translation-lib"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
kevents "k8s.io/kubernetes/pkg/kubelet/events"
|
kevents "k8s.io/kubernetes/pkg/kubelet/events"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
@ -537,12 +538,23 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
|
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
|
||||||
|
|
||||||
if mountCheckError != nil {
|
if mountCheckError != nil {
|
||||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountOptionSupport check failed", mountCheckError)
|
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountOptionSupport check failed", mountCheckError)
|
||||||
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce ReadWriteOncePod access mode if it is the only one present. This is also enforced during scheduling.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) &&
|
||||||
|
actualStateOfWorld.IsVolumeMountedElsewhere(volumeToMount.VolumeName, volumeToMount.PodName) &&
|
||||||
|
// Because we do not know what access mode the pod intends to use if there are multiple.
|
||||||
|
len(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes) == 1 &&
|
||||||
|
v1helper.ContainsAccessMode(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
|
||||||
|
|
||||||
|
err = goerrors.New("volume uses the ReadWriteOncePod access mode and is already in use by another pod")
|
||||||
|
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.SetUp failed", err)
|
||||||
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
|
}
|
||||||
|
|
||||||
// Get attacher, if possible
|
// Get attacher, if possible
|
||||||
attachableVolumePlugin, _ :=
|
attachableVolumePlugin, _ :=
|
||||||
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
||||||
@ -1027,6 +1039,18 @@ func (og *operationGenerator) GenerateMapVolumeFunc(
|
|||||||
|
|
||||||
migrated := getMigratedStatusBySpec(volumeToMount.VolumeSpec)
|
migrated := getMigratedStatusBySpec(volumeToMount.VolumeSpec)
|
||||||
|
|
||||||
|
// Enforce ReadWriteOncePod access mode. This is also enforced during scheduling.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) &&
|
||||||
|
actualStateOfWorld.IsVolumeMountedElsewhere(volumeToMount.VolumeName, volumeToMount.PodName) &&
|
||||||
|
// Because we do not know what access mode the pod intends to use if there are multiple.
|
||||||
|
len(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes) == 1 &&
|
||||||
|
v1helper.ContainsAccessMode(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
|
||||||
|
|
||||||
|
err = goerrors.New("volume uses the ReadWriteOncePod access mode and is already in use by another pod")
|
||||||
|
eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.SetUpDevice failed", err)
|
||||||
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
|
}
|
||||||
|
|
||||||
// Set up global map path under the given plugin directory using symbolic link
|
// Set up global map path under the given plugin directory using symbolic link
|
||||||
globalMapPath, err :=
|
globalMapPath, err :=
|
||||||
blockVolumeMapper.GetGlobalMapPath(volumeToMount.VolumeSpec)
|
blockVolumeMapper.GetGlobalMapPath(volumeToMount.VolumeSpec)
|
||||||
|
@ -298,8 +298,8 @@ func JoinMountOptions(userOptions []string, systemOptions []string) []string {
|
|||||||
return allMountOptions.List()
|
return allMountOptions.List()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessModesContains returns whether the requested mode is contained by modes
|
// ContainsAccessMode returns whether the requested mode is contained by modes
|
||||||
func AccessModesContains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if m == mode {
|
if m == mode {
|
||||||
return true
|
return true
|
||||||
@ -308,10 +308,10 @@ func AccessModesContains(modes []v1.PersistentVolumeAccessMode, mode v1.Persiste
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessModesContainedInAll returns whether all of the requested modes are contained by modes
|
// ContainsAllAccessModes returns whether all of the requested modes are contained by modes
|
||||||
func AccessModesContainedInAll(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
|
func ContainsAllAccessModes(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
|
||||||
for _, mode := range requestedModes {
|
for _, mode := range requestedModes {
|
||||||
if !AccessModesContains(indexedModes, mode) {
|
if !ContainsAccessMode(indexedModes, mode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,7 +369,7 @@ func (plugin *vsphereVolumePlugin) newProvisionerInternal(options volume.VolumeO
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *vsphereVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (v *vsphereVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(v.plugin.GetAccessModes(), v.options.PVC.Spec.AccessModes) {
|
if !util.ContainsAllAccessModes(v.plugin.GetAccessModes(), v.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes())
|
||||||
}
|
}
|
||||||
klog.V(1).Infof("Provision with selectedNode: %s and allowedTopologies : %s", getNodeName(selectedNode), allowedTopologies)
|
klog.V(1).Infof("Provision with selectedNode: %s and allowedTopologies : %s", getNodeName(selectedNode), allowedTopologies)
|
||||||
|
@ -560,6 +560,9 @@ const (
|
|||||||
ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany"
|
ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany"
|
||||||
// can be mounted in read/write mode to many hosts
|
// can be mounted in read/write mode to many hosts
|
||||||
ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany"
|
ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany"
|
||||||
|
// can be mounted in read/write mode to exactly 1 pod
|
||||||
|
// cannot be used in combination with other access modes
|
||||||
|
ReadWriteOncePod PersistentVolumeAccessMode = "ReadWriteOncePod"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PersistentVolumePhase string
|
type PersistentVolumePhase string
|
||||||
|
@ -45,19 +45,22 @@ func IsDefaultAnnotationText(obj metav1.ObjectMeta) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessModesAsString returns a string representation of an array of access modes.
|
// GetAccessModesAsString returns a string representation of an array of access modes.
|
||||||
// modes, when present, are always in the same order: RWO,ROX,RWX.
|
// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
|
||||||
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
||||||
modes = removeDuplicateAccessModes(modes)
|
modes = removeDuplicateAccessModes(modes)
|
||||||
modesStr := []string{}
|
modesStr := []string{}
|
||||||
if containsAccessMode(modes, v1.ReadWriteOnce) {
|
if ContainsAccessMode(modes, v1.ReadWriteOnce) {
|
||||||
modesStr = append(modesStr, "RWO")
|
modesStr = append(modesStr, "RWO")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, v1.ReadOnlyMany) {
|
if ContainsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
modesStr = append(modesStr, "ROX")
|
modesStr = append(modesStr, "ROX")
|
||||||
}
|
}
|
||||||
if containsAccessMode(modes, v1.ReadWriteMany) {
|
if ContainsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
modesStr = append(modesStr, "RWX")
|
modesStr = append(modesStr, "RWX")
|
||||||
}
|
}
|
||||||
|
if ContainsAccessMode(modes, v1.ReadWriteOncePod) {
|
||||||
|
modesStr = append(modesStr, "RWOP")
|
||||||
|
}
|
||||||
return strings.Join(modesStr, ",")
|
return strings.Join(modesStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,14 +68,14 @@ func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
|||||||
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
||||||
accessModes := []v1.PersistentVolumeAccessMode{}
|
accessModes := []v1.PersistentVolumeAccessMode{}
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if !containsAccessMode(accessModes, m) {
|
if !ContainsAccessMode(accessModes, m) {
|
||||||
accessModes = append(accessModes, m)
|
accessModes = append(accessModes, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accessModes
|
return accessModes
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||||
for _, m := range modes {
|
for _, m := range modes {
|
||||||
if m == mode {
|
if m == mode {
|
||||||
return true
|
return true
|
||||||
|
Loading…
Reference in New Issue
Block a user