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  }