k8s.io/kubernetes@v1.29.3/pkg/api/persistentvolumeclaim/util.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package persistentvolumeclaim 18 19 import ( 20 "fmt" 21 22 "k8s.io/apimachinery/pkg/util/validation/field" 23 utilfeature "k8s.io/apiserver/pkg/util/feature" 24 "k8s.io/kubernetes/pkg/apis/core" 25 "k8s.io/kubernetes/pkg/apis/core/helper" 26 "k8s.io/kubernetes/pkg/features" 27 ) 28 29 const ( 30 pvc string = "PersistentVolumeClaim" 31 volumeSnapshot string = "VolumeSnapshot" 32 deprecatedStorageClassAnnotationsMsg = `deprecated since v1.8; use "storageClassName" attribute instead` 33 ) 34 35 // DropDisabledFields removes disabled fields from the pvc spec. 36 // This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec. 37 func DropDisabledFields(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) { 38 // Drop the contents of the volumeAttributesClassName if the VolumeAttributesClass 39 // feature gate is disabled. 40 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) { 41 if oldPVCSpec == nil || oldPVCSpec.VolumeAttributesClassName == nil { 42 pvcSpec.VolumeAttributesClassName = nil 43 } 44 } 45 46 // Drop the contents of the dataSourceRef field if the AnyVolumeDataSource 47 // feature gate is disabled. 48 if !utilfeature.DefaultFeatureGate.Enabled(features.AnyVolumeDataSource) { 49 if !dataSourceRefInUse(oldPVCSpec) { 50 pvcSpec.DataSourceRef = nil 51 } 52 } 53 54 // Drop the contents of the dataSourceRef field if the CrossNamespaceVolumeDataSource 55 // feature gate is disabled and dataSourceRef.Namespace is specified. 56 if !utilfeature.DefaultFeatureGate.Enabled(features.CrossNamespaceVolumeDataSource) && 57 pvcSpec.DataSourceRef != nil && pvcSpec.DataSourceRef.Namespace != nil && len(*pvcSpec.DataSourceRef.Namespace) != 0 { 58 if !dataSourceRefInUse(oldPVCSpec) { 59 pvcSpec.DataSourceRef = nil 60 } 61 } 62 } 63 64 // EnforceDataSourceBackwardsCompatibility drops the data source field under certain conditions 65 // to maintain backwards compatibility with old behavior. 66 // See KEP 1495 for details. 67 // Specifically, if this is an update of a PVC with no data source, or a creation of a new PVC, 68 // and the dataSourceRef field is not filled in, then we will drop "invalid" data sources 69 // (anything other than a PVC or a VolumeSnapshot) from this request as if an empty PVC had 70 // been requested. 71 // This should be called after DropDisabledFields so that if the AnyVolumeDataSource feature 72 // gate is disabled, dataSourceRef will be forced to empty, ensuring pre-1.22 behavior. 73 // This should be called before NormalizeDataSources, so that data sources other than PVCs 74 // and VolumeSnapshots can only be set through the dataSourceRef field and not the dataSource 75 // field. 76 func EnforceDataSourceBackwardsCompatibility(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) { 77 // Check if the old PVC has a data source here is so that on updates from old clients 78 // that omit dataSourceRef, we preserve the data source, even if it would have been 79 // invalid to specify it at using the dataSource field at create. 80 if dataSourceInUse(oldPVCSpec) { 81 return 82 } 83 84 // Check if dataSourceRef is empty is because if it's not empty, then there is 85 // definitely a newer client and it definitely either wants to create a non-empty 86 // volume, or it wants to update a PVC that has a data source. Whether the 87 // specified data source is valid or satisfiable is a matter for validation and 88 // the volume populator code, but we can say with certainty that the client is 89 // not expecting the legacy behavior of ignoring invalid data sources. 90 if pvcSpec.DataSourceRef != nil { 91 return 92 } 93 94 // Historically, we only allow PVCs and VolumeSnapshots in the dataSource field. 95 // All other values are silently dropped. 96 if !dataSourceIsPvcOrSnapshot(pvcSpec.DataSource) { 97 pvcSpec.DataSource = nil 98 } 99 } 100 101 func DropDisabledFieldsFromStatus(pvc, oldPVC *core.PersistentVolumeClaim) { 102 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) { 103 if oldPVC == nil || oldPVC.Status.CurrentVolumeAttributesClassName == nil { 104 pvc.Status.CurrentVolumeAttributesClassName = nil 105 } 106 if oldPVC == nil || oldPVC.Status.ModifyVolumeStatus == nil { 107 pvc.Status.ModifyVolumeStatus = nil 108 } 109 } 110 111 if !utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure) { 112 if !helper.ClaimContainsAllocatedResources(oldPVC) { 113 pvc.Status.AllocatedResources = nil 114 } 115 if !helper.ClaimContainsAllocatedResourceStatus(oldPVC) { 116 pvc.Status.AllocatedResourceStatuses = nil 117 } 118 } 119 } 120 121 func dataSourceInUse(oldPVCSpec *core.PersistentVolumeClaimSpec) bool { 122 if oldPVCSpec == nil { 123 return false 124 } 125 if oldPVCSpec.DataSource != nil || oldPVCSpec.DataSourceRef != nil { 126 return true 127 } 128 return false 129 } 130 131 func dataSourceIsPvcOrSnapshot(dataSource *core.TypedLocalObjectReference) bool { 132 if dataSource != nil { 133 apiGroup := "" 134 if dataSource.APIGroup != nil { 135 apiGroup = *dataSource.APIGroup 136 } 137 if dataSource.Kind == pvc && 138 apiGroup == "" { 139 return true 140 } 141 142 if dataSource.Kind == volumeSnapshot && apiGroup == "snapshot.storage.k8s.io" { 143 return true 144 } 145 } 146 return false 147 } 148 149 func dataSourceRefInUse(oldPVCSpec *core.PersistentVolumeClaimSpec) bool { 150 if oldPVCSpec == nil { 151 return false 152 } 153 if oldPVCSpec.DataSourceRef != nil { 154 return true 155 } 156 return false 157 } 158 159 // NormalizeDataSources ensures that DataSource and DataSourceRef have the same contents 160 // as long as both are not explicitly set. 161 // This should be used by creates/gets of PVCs, but not updates 162 func NormalizeDataSources(pvcSpec *core.PersistentVolumeClaimSpec) { 163 // Don't enable this behavior if the feature gate is not on 164 if !utilfeature.DefaultFeatureGate.Enabled(features.AnyVolumeDataSource) { 165 return 166 } 167 if pvcSpec.DataSource != nil && pvcSpec.DataSourceRef == nil { 168 // Using the old way of setting a data source 169 pvcSpec.DataSourceRef = &core.TypedObjectReference{ 170 Kind: pvcSpec.DataSource.Kind, 171 Name: pvcSpec.DataSource.Name, 172 } 173 if pvcSpec.DataSource.APIGroup != nil { 174 apiGroup := *pvcSpec.DataSource.APIGroup 175 pvcSpec.DataSourceRef.APIGroup = &apiGroup 176 } 177 } else if pvcSpec.DataSourceRef != nil && pvcSpec.DataSource == nil { 178 if pvcSpec.DataSourceRef.Namespace == nil || len(*pvcSpec.DataSourceRef.Namespace) == 0 { 179 // Using the new way of setting a data source 180 pvcSpec.DataSource = &core.TypedLocalObjectReference{ 181 Kind: pvcSpec.DataSourceRef.Kind, 182 Name: pvcSpec.DataSourceRef.Name, 183 } 184 if pvcSpec.DataSourceRef.APIGroup != nil { 185 apiGroup := *pvcSpec.DataSourceRef.APIGroup 186 pvcSpec.DataSource.APIGroup = &apiGroup 187 } 188 } 189 } 190 } 191 192 func GetWarningsForPersistentVolumeClaim(pv *core.PersistentVolumeClaim) []string { 193 var warnings []string 194 195 if pv == nil { 196 return nil 197 } 198 199 if _, ok := pv.ObjectMeta.Annotations[core.BetaStorageClassAnnotation]; ok { 200 warnings = append(warnings, 201 fmt.Sprintf( 202 "%s: %s", 203 field.NewPath("metadata", "annotations").Key(core.BetaStorageClassAnnotation), 204 deprecatedStorageClassAnnotationsMsg, 205 ), 206 ) 207 } 208 209 warnings = append(warnings, GetWarningsForPersistentVolumeClaimSpec(field.NewPath("spec"), pv.Spec)...) 210 211 return warnings 212 } 213 214 func GetWarningsForPersistentVolumeClaimSpec(fieldPath *field.Path, pvSpec core.PersistentVolumeClaimSpec) []string { 215 216 var warnings []string 217 requestValue := pvSpec.Resources.Requests[core.ResourceStorage] 218 if requestValue.MilliValue()%int64(1000) != int64(0) { 219 warnings = append(warnings, fmt.Sprintf( 220 "%s: fractional byte value %q is invalid, must be an integer", 221 fieldPath.Child("resources").Child("requests").Key(core.ResourceStorage.String()), requestValue.String())) 222 } 223 limitValue := pvSpec.Resources.Limits[core.ResourceStorage] 224 if limitValue.MilliValue()%int64(1000) != int64(0) { 225 warnings = append(warnings, fmt.Sprintf( 226 "%s: fractional byte value %q is invalid, must be an integer", 227 fieldPath.Child("resources").Child("limits").Key(core.ResourceStorage.String()), limitValue.String())) 228 } 229 return warnings 230 }