Merge pull request #103276 from NetApp/data-source-ref

Add DataSourceRef field to PVC spec
This commit is contained in:
Kubernetes Prow Robot 2021-07-07 08:56:44 -07:00 committed by GitHub
commit eaba61b4de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 24037 additions and 23126 deletions

View File

@ -7145,7 +7145,11 @@
},
"dataSource": {
"$ref": "#/definitions/io.k8s.api.core.v1.TypedLocalObjectReference",
"description": "This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) * An existing custom resource that implements data population (Alpha) In order to use custom resource types that implement data population, the AnyVolumeDataSource feature gate must be enabled. If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source."
"description": "This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source. If the AnyVolumeDataSource feature gate is enabled, this field will always have the same contents as the DataSourceRef field."
},
"dataSourceRef": {
"$ref": "#/definitions/io.k8s.api.core.v1.TypedLocalObjectReference",
"description": "Specifies the object from which to populate the volume with data, if a non-empty volume is desired. This may be any local object from a non-empty API group (non core object) or a PersistentVolumeClaim object. When this field is specified, volume binding will only succeed if the type of the specified object matches some installed volume populator or dynamic provisioner. This field will replace the functionality of the DataSource field and as such if both fields are non-empty, they must have the same value. For backwards compatibility, both fields (DataSource and DataSourceRef) will be set to the same value automatically if one of them is empty and the other is non-empty. There are two important differences between DataSource and DataSourceRef: * While DataSource only allows two specific types of objects, DataSourceRef\n allows any non-core object, as well as PersistentVolumeClaim objects.\n* While DataSource ignores disallowed values (dropping them), DataSourceRef\n preserves all values, and generates an error if a disallowed value is\n specified.\n(Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled."
},
"resources": {
"$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements",

View File

@ -29,8 +29,47 @@ const (
// DropDisabledFields removes disabled fields from the pvc spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec.
func DropDisabledFields(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) {
if !dataSourceIsEnabled(pvcSpec) && !dataSourceInUse(oldPVCSpec) {
func DropDisabledFields(pvcSpec *core.PersistentVolumeClaimSpec) {
// Drop the contents of the dataSourceRef field if the AnyVolumeDataSource
// feature gate is disabled.
if !utilfeature.DefaultFeatureGate.Enabled(features.AnyVolumeDataSource) {
pvcSpec.DataSourceRef = nil
}
}
// EnforceDataSourceBackwardsCompatibility drops the data source field under certain conditions
// to maintain backwards compatibility with old behavior.
// See KEP 1495 for details.
// Specifically, if this is an update of a PVC with no data source, or a creation of a new PVC,
// and the dataSourceRef field is not filled in, then we will drop "invalid" data sources
// (anything other than a PVC or a VolumeSnapshot) from this request as if an empty PVC had
// been requested.
// This should be called after DropDisabledFields so that if the AnyVolumeDataSource feature
// gate is disabled, dataSourceRef will be forced to empty, ensuring pre-1.22 behavior.
// This should be called before NormalizeDataSources, so that data sources other than PVCs
// and VolumeSnapshots can only be set through the dataSourceRef field and not the dataSource
// field.
func EnforceDataSourceBackwardsCompatibility(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) {
// Check if the old PVC has a data source here is so that on updates from old clients
// that omit dataSourceRef, we preserve the data source, even if it would have been
// invalid to specify it at using the dataSource field at create.
if dataSourceInUse(oldPVCSpec) {
return
}
// Check if dataSourceRef is empty is because if it's not empty, then there is
// definitely a newer client and it definitely either wants to create a non-empty
// volume, or it wants to update a PVC that has a data source. Whether the
// specified data source is valid or satisfiable is a matter for validation and
// the volume populator code, but we can say with certainty that the client is
// not expecting the legacy behavior of ignoring invalid data sources.
if pvcSpec.DataSourceRef != nil {
return
}
// Historically, we only allow PVCs and VolumeSnapshots in the dataSource field.
// All other values are silently dropped.
if !dataSourceIsPvcOrSnapshot(pvcSpec.DataSource) {
pvcSpec.DataSource = nil
}
}
@ -39,31 +78,43 @@ func dataSourceInUse(oldPVCSpec *core.PersistentVolumeClaimSpec) bool {
if oldPVCSpec == nil {
return false
}
if oldPVCSpec.DataSource != nil {
if oldPVCSpec.DataSource != nil || oldPVCSpec.DataSourceRef != nil {
return true
}
return false
}
func dataSourceIsEnabled(pvcSpec *core.PersistentVolumeClaimSpec) bool {
if pvcSpec.DataSource != nil {
if utilfeature.DefaultFeatureGate.Enabled(features.AnyVolumeDataSource) {
return true
}
func dataSourceIsPvcOrSnapshot(dataSource *core.TypedLocalObjectReference) bool {
if dataSource != nil {
apiGroup := ""
if pvcSpec.DataSource.APIGroup != nil {
apiGroup = *pvcSpec.DataSource.APIGroup
if dataSource.APIGroup != nil {
apiGroup = *dataSource.APIGroup
}
if pvcSpec.DataSource.Kind == pvc &&
if dataSource.Kind == pvc &&
apiGroup == "" {
return true
}
if pvcSpec.DataSource.Kind == volumeSnapshot && apiGroup == "snapshot.storage.k8s.io" {
if dataSource.Kind == volumeSnapshot && apiGroup == "snapshot.storage.k8s.io" {
return true
}
}
return false
}
// NormalizeDataSources ensures that DataSource and DataSourceRef have the same contents
// as long as both are not explicitly set.
// This should be used by creates/gets of PVCs, but not updates
func NormalizeDataSources(pvcSpec *core.PersistentVolumeClaimSpec) {
// Don't enable this behavior if the feature gate is not on
if !utilfeature.DefaultFeatureGate.Enabled(features.AnyVolumeDataSource) {
return
}
if pvcSpec.DataSource != nil && pvcSpec.DataSourceRef == nil {
// Using the old way of setting a data source
pvcSpec.DataSourceRef = pvcSpec.DataSource.DeepCopy()
} else if pvcSpec.DataSourceRef != nil && pvcSpec.DataSource == nil {
// Using the new way of setting a data source
pvcSpec.DataSource = pvcSpec.DataSourceRef.DeepCopy()
}
}

View File

@ -68,9 +68,6 @@ func TestDropDisabledSnapshotDataSource(t *testing.T) {
},
}
// Ensure that any data sources aren't enabled for this test
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, false)()
for _, oldpvcInfo := range pvcInfo {
for _, newpvcInfo := range pvcInfo {
oldpvc := oldpvcInfo.pvc()
@ -80,11 +77,7 @@ func TestDropDisabledSnapshotDataSource(t *testing.T) {
}
t.Run(fmt.Sprintf("old pvc %v, new pvc %v", oldpvcInfo.description, newpvcInfo.description), func(t *testing.T) {
var oldpvcSpec *core.PersistentVolumeClaimSpec
if oldpvc != nil {
oldpvcSpec = &oldpvc.Spec
}
DropDisabledFields(&newpvc.Spec, oldpvcSpec)
EnforceDataSourceBackwardsCompatibility(&newpvc.Spec, nil)
// old pvc should never be changed
if !reflect.DeepEqual(oldpvc, oldpvcInfo.pvc()) {
@ -148,20 +141,15 @@ func TestPVCDataSourceSpecFilter(t *testing.T) {
},
}
// Ensure that any data sources aren't enabled for this test
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, false)()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
DropDisabledFields(&test.spec, nil)
EnforceDataSourceBackwardsCompatibility(&test.spec, nil)
if test.spec.DataSource != test.want {
t.Errorf("expected drop datasource condition was not met, test: %s, spec: %v, expected: %v", testName, test.spec, test.want)
}
})
}
}
// TestAnyDataSourceFilter checks to ensure the AnyVolumeDataSource feature gate works
@ -175,59 +163,127 @@ func TestAnyDataSourceFilter(t *testing.T) {
}
volumeDataSource := makeDataSource("", "PersistentVolumeClaim", "my-vol")
snapshotDataSource := makeDataSource("snapshot.storage.k8s.io", "VolumeSnapshot", "my-snap")
genericDataSource := makeDataSource("generic.storage.k8s.io", "Generic", "my-foo")
var tests = map[string]struct {
spec core.PersistentVolumeClaimSpec
anyEnabled bool
want *core.TypedLocalObjectReference
wantRef *core.TypedLocalObjectReference
}{
"any disabled with empty ds": {
spec: core.PersistentVolumeClaimSpec{},
want: nil,
},
"any disabled with volume ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
want: volumeDataSource,
},
"any disabled with snapshot ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource},
want: snapshotDataSource,
"any disabled with volume ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource},
},
"any disabled with generic ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource},
want: nil,
"any disabled with both data sources": {
spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSource},
want: volumeDataSource,
},
"any enabled with empty ds": {
spec: core.PersistentVolumeClaimSpec{},
anyEnabled: true,
want: nil,
},
"any enabled with volume ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
anyEnabled: true,
want: volumeDataSource,
},
"any enabled with snapshot ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource},
"any enabled with volume ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource},
anyEnabled: true,
want: snapshotDataSource,
wantRef: volumeDataSource,
},
"any enabled with generic ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource},
"any enabled with both data sources": {
spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSource},
anyEnabled: true,
want: genericDataSource,
want: volumeDataSource,
wantRef: volumeDataSource,
},
}
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)()
DropDisabledFields(&test.spec, nil)
if test.spec.DataSource != test.want {
t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, spec: %v, expected: %v",
testName, test.anyEnabled, test.spec, test.want)
DropDisabledFields(&test.spec)
if test.spec.DataSource != test.want || test.spec.DataSourceRef != test.wantRef {
t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, spec: %v, expected: %v %v",
testName, test.anyEnabled, test.spec, test.want, test.wantRef)
}
})
}
}
// TestDataSourceRef checks to ensure the DataSourceRef field handles backwards
// compatibility with the DataSource field
func TestDataSourceRef(t *testing.T) {
makeDataSource := func(apiGroup, kind, name string) *core.TypedLocalObjectReference {
return &core.TypedLocalObjectReference{
APIGroup: &apiGroup,
Kind: kind,
Name: name,
}
}
volumeDataSource := makeDataSource("", "PersistentVolumeClaim", "my-vol")
snapshotDataSource := makeDataSource("snapshot.storage.k8s.io", "VolumeSnapshot", "my-snap")
genericDataSource := makeDataSource("generic.storage.k8s.io", "Generic", "my-foo")
coreDataSource := makeDataSource("", "Pod", "my-pod")
var tests = map[string]struct {
spec core.PersistentVolumeClaimSpec
want *core.TypedLocalObjectReference
}{
"empty ds": {
spec: core.PersistentVolumeClaimSpec{},
},
"volume ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
want: volumeDataSource,
},
"snapshot ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource},
want: snapshotDataSource,
},
"generic ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource},
want: genericDataSource,
},
"core ds": {
spec: core.PersistentVolumeClaimSpec{DataSource: coreDataSource},
want: coreDataSource,
},
"volume ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource},
want: volumeDataSource,
},
"snapshot ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: snapshotDataSource},
want: snapshotDataSource,
},
"generic ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: genericDataSource},
want: genericDataSource,
},
"core ds ref": {
spec: core.PersistentVolumeClaimSpec{DataSourceRef: coreDataSource},
want: coreDataSource,
},
}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
NormalizeDataSources(&test.spec)
if !reflect.DeepEqual(test.spec.DataSource, test.want) ||
!reflect.DeepEqual(test.spec.DataSourceRef, test.want) {
t.Errorf("expected condition was not met, test: %s, spec: %v, expected: %v",
testName, test.spec, test.want)
}
})
}

View File

@ -450,13 +450,31 @@ type PersistentVolumeClaimSpec struct {
// This field can be used to specify either:
// * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
// * An existing PVC (PersistentVolumeClaim)
// * An existing custom resource that implements data population (Alpha)
// In order to use custom resource types that implement data population,
// the AnyVolumeDataSource feature gate must be enabled.
// If the provisioner or an external controller can support the specified data source,
// it will create a new volume based on the contents of the specified data source.
// If the AnyVolumeDataSource feature gate is enabled, this field will always have
// the same contents as the DataSourceRef field.
// +optional
DataSource *TypedLocalObjectReference
// Specifies the object from which to populate the volume with data, if a non-empty
// volume is desired. This may be any local object from a non-empty API group (non
// core object) or a PersistentVolumeClaim object.
// When this field is specified, volume binding will only succeed if the type of
// the specified object matches some installed volume populator or dynamic
// provisioner.
// This field will replace the functionality of the DataSource field and as such
// if both fields are non-empty, they must have the same value. For backwards
// compatibility, both fields (DataSource and DataSourceRef) will be set to the same
// value automatically if one of them is empty and the other is non-empty.
// There are two important differences between DataSource and DataSourceRef:
// * While DataSource only allows two specific types of objects, DataSourceRef
// allows any non-core object, as well as PersistentVolumeClaim objects.
// * While DataSource ignores disallowed values (dropping them), DataSourceRef
// preserves all values, and generates an error if a disallowed value is
// specified.
// (Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
// +optional
DataSourceRef *TypedLocalObjectReference
}
// PersistentVolumeClaimConditionType defines the condition of PV claim.

View File

@ -5117,6 +5117,7 @@ func autoConvert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(
out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName))
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.DataSource = (*core.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
out.DataSourceRef = (*core.TypedLocalObjectReference)(unsafe.Pointer(in.DataSourceRef))
return nil
}
@ -5135,6 +5136,7 @@ func autoConvert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(
out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName))
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.DataSource = (*v1.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
out.DataSourceRef = (*v1.TypedLocalObjectReference)(unsafe.Pointer(in.DataSourceRef))
return nil
}

View File

@ -2046,6 +2046,27 @@ func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim, opts Persist
return allErrs
}
// validateDataSource validates a DataSource/DataSourceRef in a PersistentVolumeClaimSpec
func validateDataSource(dataSource *core.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(dataSource.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
}
if len(dataSource.Kind) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
}
apiGroup := ""
if dataSource.APIGroup != nil {
apiGroup = *dataSource.APIGroup
}
if len(apiGroup) == 0 && dataSource.Kind != "PersistentVolumeClaim" {
allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, ""))
}
return allErrs
}
// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec
func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
@ -2096,18 +2117,15 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
}
if spec.DataSource != nil {
if len(spec.DataSource.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("dataSource", "name"), ""))
allErrs = append(allErrs, validateDataSource(spec.DataSource, fldPath.Child("dataSource"))...)
}
if len(spec.DataSource.Kind) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("dataSource", "kind"), ""))
if spec.DataSourceRef != nil {
allErrs = append(allErrs, validateDataSource(spec.DataSourceRef, fldPath.Child("dataSourceRef"))...)
}
apiGroup := ""
if spec.DataSource.APIGroup != nil {
apiGroup = *spec.DataSource.APIGroup
}
if len(apiGroup) == 0 && spec.DataSource.Kind != "PersistentVolumeClaim" {
allErrs = append(allErrs, field.Invalid(fldPath.Child("dataSource"), spec.DataSource.Kind, ""))
if spec.DataSource != nil && spec.DataSourceRef != nil {
if !apiequality.Semantic.DeepEqual(spec.DataSource, spec.DataSourceRef) {
allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"),
"must match dataSourceRef"))
}
}

View File

@ -1462,6 +1462,27 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
VolumeMode: &invalidMode,
}),
},
"mismatch-data-source-and-ref": {
isExpectedFailure: true,
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
DataSource: &core.TypedLocalObjectReference{
Kind: "PersistentVolumeClaim",
Name: "pvc1",
},
DataSourceRef: &core.TypedLocalObjectReference{
Kind: "PersistentVolumeClaim",
Name: "pvc2",
},
}),
},
}
for name, scenario := range scenarios {
@ -16966,7 +16987,7 @@ func TestValidateWindowsSecurityContextOptions(t *testing.T) {
}
}
func testDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec {
scName := "csi-plugin"
dataSourceInSpec := core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
@ -17024,14 +17045,13 @@ func TestAlphaVolumePVCDataSource(t *testing.T) {
}
for _, tc := range testCases {
if tc.expectedFail {
opts := PersistentVolumeClaimSpecValidationOptions{}
if tc.expectedFail {
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
t.Errorf("expected failure: %v", errs)
}
} else {
opts := PersistentVolumeClaimSpecValidationOptions{}
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
@ -17039,6 +17059,67 @@ func TestAlphaVolumePVCDataSource(t *testing.T) {
}
}
func testAnyDataSource(t *testing.T, ds, dsRef bool) {
testCases := []struct {
testName string
claimSpec core.PersistentVolumeClaimSpec
expectedFail bool
}{
{
testName: "test create from valid snapshot source",
claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
},
{
testName: "test create from valid pvc source",
claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
},
{
testName: "test missing name in snapshot datasource should fail",
claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
expectedFail: true,
},
{
testName: "test missing kind in snapshot datasource should fail",
claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
expectedFail: true,
},
{
testName: "test create from valid generic custom resource source",
claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
},
{
testName: "test invalid datasource should fail",
claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""),
expectedFail: true,
},
}
for _, tc := range testCases {
if dsRef {
tc.claimSpec.DataSourceRef = tc.claimSpec.DataSource.DeepCopy()
}
if !ds {
tc.claimSpec.DataSource = nil
}
opts := PersistentVolumeClaimSpecValidationOptions{}
if tc.expectedFail {
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
t.Errorf("expected failure: %v", errs)
}
} else {
if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
}
}
func TestAnyDataSource(t *testing.T) {
testAnyDataSource(t, true, false)
testAnyDataSource(t, false, true)
testAnyDataSource(t, true, false)
}
func TestValidateTopologySpreadConstraints(t *testing.T) {
testCases := []struct {
name string

View File

@ -2936,6 +2936,11 @@ func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
if in.DataSourceRef != nil {
in, out := &in.DataSourceRef, &out.DataSourceRef
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
return
}

View File

@ -18,6 +18,7 @@ package storage
import (
"context"
pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -62,7 +63,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore.UpdateStrategy = persistentvolumeclaim.StatusStrategy
statusStore.ResetFieldsStrategy = persistentvolumeclaim.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
rest := &REST{store}
store.Decorator = rest.defaultOnRead
return rest, &StatusREST{store: &statusStore}, nil
}
// Implement ShortNamesProvider
@ -73,6 +77,46 @@ func (r *REST) ShortNames() []string {
return []string{"pvc"}
}
// defaultOnRead sets interlinked fields that were not previously set on read.
// We can't do this in the normal defaulting path because that same logic
// applies on Get, Create, and Update, but we need to distinguish between them.
//
// This will be called on both PersistentVolumeClaim and PersistentVolumeClaimList types.
func (r *REST) defaultOnRead(obj runtime.Object) {
switch s := obj.(type) {
case *api.PersistentVolumeClaim:
r.defaultOnReadPvc(s)
case *api.PersistentVolumeClaimList:
r.defaultOnReadPvcList(s)
default:
// This was not an object we can default. This is not an error, as the
// caching layer can pass through here, too.
}
}
// defaultOnReadPvcList defaults a PersistentVolumeClaimList.
func (r *REST) defaultOnReadPvcList(pvcList *api.PersistentVolumeClaimList) {
if pvcList == nil {
return
}
for i := range pvcList.Items {
r.defaultOnReadPvc(&pvcList.Items[i])
}
}
// defaultOnReadPvc defaults a single PersistentVolumeClaim.
func (r *REST) defaultOnReadPvc(pvc *api.PersistentVolumeClaim) {
if pvc == nil {
return
}
// We set dataSourceRef to the same value as dataSource at creation time now,
// but for pre-existing PVCs with data sources, the dataSourceRef field will
// be blank, so we fill it in here at read time.
pvcutil.NormalizeDataSources(&pvc.Spec)
}
// StatusREST implements the REST endpoint for changing the status of a persistentvolumeclaim.
type StatusREST struct {
store *genericregistry.Store

View File

@ -17,6 +17,7 @@ limitations under the License.
package storage
import (
"reflect"
"testing"
apiequality "k8s.io/apimachinery/pkg/api/equality"
@ -31,7 +32,10 @@ import (
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
"k8s.io/apiserver/pkg/registry/rest"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/registrytest"
)
@ -207,3 +211,95 @@ func TestShortNames(t *testing.T) {
expected := []string{"pvc"}
registrytest.AssertShortNames(t, storage, expected)
}
func TestDefaultOnReadPvc(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
dataSource := api.TypedLocalObjectReference{
Kind: "PersistentVolumeClaim",
Name: "my-pvc",
}
var tests = map[string]struct {
anyEnabled bool
dataSource bool
dataSourceRef bool
want bool
wantRef bool
}{
"any disabled with empty ds": {
anyEnabled: false,
},
"any disabled with volume ds": {
dataSource: true,
want: true,
},
"any disabled with volume ds ref": {
dataSourceRef: true,
wantRef: true,
},
"any disabled with both data sources": {
dataSource: true,
dataSourceRef: true,
want: true,
wantRef: true,
},
"any enabled with empty ds": {
anyEnabled: true,
},
"any enabled with volume ds": {
anyEnabled: true,
dataSource: true,
want: true,
wantRef: true,
},
"any enabled with volume ds ref": {
anyEnabled: true,
dataSourceRef: true,
want: true,
wantRef: true,
},
"any enabled with both data sources": {
anyEnabled: true,
dataSource: true,
dataSourceRef: true,
want: true,
wantRef: true,
},
}
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)()
pvc := new(api.PersistentVolumeClaim)
if test.dataSource {
pvc.Spec.DataSource = dataSource.DeepCopy()
}
if test.dataSourceRef {
pvc.Spec.DataSourceRef = dataSource.DeepCopy()
}
var expectDataSource *api.TypedLocalObjectReference
if test.want {
expectDataSource = &dataSource
}
var expectDataSourceRef *api.TypedLocalObjectReference
if test.wantRef {
expectDataSourceRef = &dataSource
}
// Method under test
storage.defaultOnReadPvc(pvc)
if !reflect.DeepEqual(pvc.Spec.DataSource, expectDataSource) {
t.Errorf("data source does not match, test: %s, anyEnabled: %v, dataSource: %v, expected: %v",
testName, test.anyEnabled, test.dataSource, test.want)
}
if !reflect.DeepEqual(pvc.Spec.DataSourceRef, expectDataSourceRef) {
t.Errorf("data source ref does not match, test: %s, anyEnabled: %v, dataSourceRef: %v, expected: %v",
testName, test.anyEnabled, test.dataSourceRef, test.wantRef)
}
})
}
}

View File

@ -67,7 +67,17 @@ func (persistentvolumeclaimStrategy) PrepareForCreate(ctx context.Context, obj r
pvc := obj.(*api.PersistentVolumeClaim)
pvc.Status = api.PersistentVolumeClaimStatus{}
pvcutil.DropDisabledFields(&pvc.Spec, nil)
pvcutil.DropDisabledFields(&pvc.Spec)
// For data sources, we need to do 2 things to implement KEP 1495
// First drop invalid values from spec.dataSource (anything other than PVC or
// VolumeSnapshot) if certain conditions are met.
pvcutil.EnforceDataSourceBackwardsCompatibility(&pvc.Spec, nil)
// Second copy dataSource -> dataSourceRef or dataSourceRef -> dataSource if one of them
// is nil and the other is non-nil
pvcutil.NormalizeDataSources(&pvc.Spec)
}
func (persistentvolumeclaimStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
@ -95,7 +105,15 @@ func (persistentvolumeclaimStrategy) PrepareForUpdate(ctx context.Context, obj,
oldPvc := old.(*api.PersistentVolumeClaim)
newPvc.Status = oldPvc.Status
pvcutil.DropDisabledFields(&newPvc.Spec, &oldPvc.Spec)
pvcutil.DropDisabledFields(&newPvc.Spec)
// We need to use similar logic to PrepareForCreate here both to preserve backwards
// compatibility with the old behavior (ignoring of garbage dataSources at both create
// and update time) and also for compatibility with older clients, that might omit
// the dataSourceRef field which we filled in automatically, so we have to fill it
// in again here.
pvcutil.EnforceDataSourceBackwardsCompatibility(&newPvc.Spec, &oldPvc.Spec)
pvcutil.NormalizeDataSources(&newPvc.Spec)
}
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {

View File

@ -118,3 +118,139 @@ func TestDropConditions(t *testing.T) {
}
}
}
func TestPrepareForCreate(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
makeDataSource := func(apiGroup, kind, name string) *api.TypedLocalObjectReference {
return &api.TypedLocalObjectReference{
APIGroup: &apiGroup,
Kind: kind,
Name: name,
}
}
volumeDataSource := makeDataSource("", "PersistentVolumeClaim", "my-vol")
snapshotDataSource := makeDataSource("snapshot.storage.k8s.io", "VolumeSnapshot", "my-snap")
genericDataSource := makeDataSource("generic.storage.k8s.io", "Generic", "my-foo")
coreDataSource := makeDataSource("", "Pod", "my-pod")
var tests = map[string]struct {
anyEnabled bool
dataSource *api.TypedLocalObjectReference
dataSourceRef *api.TypedLocalObjectReference
want *api.TypedLocalObjectReference
wantRef *api.TypedLocalObjectReference
}{
"any disabled with empty ds": {
want: nil,
},
"any disabled with volume ds": {
dataSource: volumeDataSource,
want: volumeDataSource,
},
"any disabled with snapshot ds": {
dataSource: snapshotDataSource,
want: snapshotDataSource,
},
"any disabled with generic ds": {
dataSource: genericDataSource,
want: nil,
},
"any disabled with invalid ds": {
dataSource: coreDataSource,
want: nil,
},
"any disabled with volume ds ref": {
dataSourceRef: volumeDataSource,
},
"any disabled with snapshot ds ref": {
dataSourceRef: snapshotDataSource,
},
"any disabled with generic ds ref": {
dataSourceRef: genericDataSource,
},
"any disabled with invalid ds ref": {
dataSourceRef: coreDataSource,
},
"any enabled with empty ds": {
anyEnabled: true,
want: nil,
},
"any enabled with volume ds": {
dataSource: volumeDataSource,
anyEnabled: true,
want: volumeDataSource,
wantRef: volumeDataSource,
},
"any enabled with snapshot ds": {
dataSource: snapshotDataSource,
anyEnabled: true,
want: snapshotDataSource,
wantRef: snapshotDataSource,
},
"any enabled with generic ds": {
dataSource: genericDataSource,
anyEnabled: true,
},
"any enabled with invalid ds": {
dataSource: coreDataSource,
anyEnabled: true,
},
"any enabled with volume ds ref": {
dataSourceRef: volumeDataSource,
anyEnabled: true,
want: volumeDataSource,
wantRef: volumeDataSource,
},
"any enabled with snapshot ds ref": {
dataSourceRef: snapshotDataSource,
anyEnabled: true,
want: snapshotDataSource,
wantRef: snapshotDataSource,
},
"any enabled with generic ds ref": {
dataSourceRef: genericDataSource,
anyEnabled: true,
want: genericDataSource,
wantRef: genericDataSource,
},
"any enabled with invalid ds ref": {
dataSourceRef: coreDataSource,
anyEnabled: true,
want: coreDataSource,
wantRef: coreDataSource,
},
"any enabled with mismatched data sources": {
dataSource: volumeDataSource,
dataSourceRef: snapshotDataSource,
anyEnabled: true,
want: volumeDataSource,
wantRef: snapshotDataSource,
},
}
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)()
pvc := api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
DataSource: test.dataSource,
DataSourceRef: test.dataSourceRef,
},
}
// Method under test
Strategy.PrepareForCreate(ctx, &pvc)
if !reflect.DeepEqual(pvc.Spec.DataSource, test.want) {
t.Errorf("data source does not match, test: %s, anyEnabled: %v, dataSource: %v, expected: %v",
testName, test.anyEnabled, test.dataSource, test.want)
}
if !reflect.DeepEqual(pvc.Spec.DataSourceRef, test.wantRef) {
t.Errorf("data source ref does not match, test: %s, anyEnabled: %v, dataSourceRef: %v, expected: %v",
testName, test.anyEnabled, test.dataSourceRef, test.wantRef)
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2679,13 +2679,32 @@ message PersistentVolumeClaimSpec {
// This field can be used to specify either:
// * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
// * An existing PVC (PersistentVolumeClaim)
// * An existing custom resource that implements data population (Alpha)
// In order to use custom resource types that implement data population,
// the AnyVolumeDataSource feature gate must be enabled.
// If the provisioner or an external controller can support the specified data source,
// it will create a new volume based on the contents of the specified data source.
// If the AnyVolumeDataSource feature gate is enabled, this field will always have
// the same contents as the DataSourceRef field.
// +optional
optional TypedLocalObjectReference dataSource = 7;
// Specifies the object from which to populate the volume with data, if a non-empty
// volume is desired. This may be any local object from a non-empty API group (non
// core object) or a PersistentVolumeClaim object.
// When this field is specified, volume binding will only succeed if the type of
// the specified object matches some installed volume populator or dynamic
// provisioner.
// This field will replace the functionality of the DataSource field and as such
// if both fields are non-empty, they must have the same value. For backwards
// compatibility, both fields (DataSource and DataSourceRef) will be set to the same
// value automatically if one of them is empty and the other is non-empty.
// There are two important differences between DataSource and DataSourceRef:
// * While DataSource only allows two specific types of objects, DataSourceRef
// allows any non-core object, as well as PersistentVolumeClaim objects.
// * While DataSource ignores disallowed values (dropping them), DataSourceRef
// preserves all values, and generates an error if a disallowed value is
// specified.
// (Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
// +optional
optional TypedLocalObjectReference dataSourceRef = 8;
}
// PersistentVolumeClaimStatus is the current status of a persistent volume claim.

View File

@ -492,13 +492,31 @@ type PersistentVolumeClaimSpec struct {
// This field can be used to specify either:
// * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
// * An existing PVC (PersistentVolumeClaim)
// * An existing custom resource that implements data population (Alpha)
// In order to use custom resource types that implement data population,
// the AnyVolumeDataSource feature gate must be enabled.
// If the provisioner or an external controller can support the specified data source,
// it will create a new volume based on the contents of the specified data source.
// If the AnyVolumeDataSource feature gate is enabled, this field will always have
// the same contents as the DataSourceRef field.
// +optional
DataSource *TypedLocalObjectReference `json:"dataSource,omitempty" protobuf:"bytes,7,opt,name=dataSource"`
// Specifies the object from which to populate the volume with data, if a non-empty
// volume is desired. This may be any local object from a non-empty API group (non
// core object) or a PersistentVolumeClaim object.
// When this field is specified, volume binding will only succeed if the type of
// the specified object matches some installed volume populator or dynamic
// provisioner.
// This field will replace the functionality of the DataSource field and as such
// if both fields are non-empty, they must have the same value. For backwards
// compatibility, both fields (DataSource and DataSourceRef) will be set to the same
// value automatically if one of them is empty and the other is non-empty.
// There are two important differences between DataSource and DataSourceRef:
// * While DataSource only allows two specific types of objects, DataSourceRef
// allows any non-core object, as well as PersistentVolumeClaim objects.
// * While DataSource ignores disallowed values (dropping them), DataSourceRef
// preserves all values, and generates an error if a disallowed value is
// specified.
// (Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
// +optional
DataSourceRef *TypedLocalObjectReference `json:"dataSourceRef,omitempty" protobuf:"bytes,8,opt,name=dataSourceRef"`
}
// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type

View File

@ -1301,7 +1301,8 @@ var map_PersistentVolumeClaimSpec = map[string]string{
"volumeName": "VolumeName is the binding reference to the PersistentVolume backing this claim.",
"storageClassName": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1",
"volumeMode": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec.",
"dataSource": "This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) * An existing custom resource that implements data population (Alpha) In order to use custom resource types that implement data population, the AnyVolumeDataSource feature gate must be enabled. If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source.",
"dataSource": "This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source. If the AnyVolumeDataSource feature gate is enabled, this field will always have the same contents as the DataSourceRef field.",
"dataSourceRef": "Specifies the object from which to populate the volume with data, if a non-empty volume is desired. This may be any local object from a non-empty API group (non core object) or a PersistentVolumeClaim object. When this field is specified, volume binding will only succeed if the type of the specified object matches some installed volume populator or dynamic provisioner. This field will replace the functionality of the DataSource field and as such if both fields are non-empty, they must have the same value. For backwards compatibility, both fields (DataSource and DataSourceRef) will be set to the same value automatically if one of them is empty and the other is non-empty. There are two important differences between DataSource and DataSourceRef: * While DataSource only allows two specific types of objects, DataSourceRef\n allows any non-core object, as well as PersistentVolumeClaim objects.\n* While DataSource ignores disallowed values (dropping them), DataSourceRef\n preserves all values, and generates an error if a disallowed value is\n specified.\n(Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.",
}
func (PersistentVolumeClaimSpec) SwaggerDoc() map[string]string {

View File

@ -2934,6 +2934,11 @@ func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
if in.DataSourceRef != nil {
in, out := &in.DataSourceRef, &out.DataSourceRef
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
return
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -71,24 +71,29 @@
"apiGroup": "28",
"kind": "29",
"name": "30"
},
"dataSourceRef": {
"apiGroup": "31",
"kind": "32",
"name": "33"
}
},
"status": {
"phase": "燴壩卄蓨MĮ?",
"phase": "gɸ=ǤÆ碛,1ZƜ/C龷Ȫ",
"accessModes": [
"ĭÐl恕ɍȇ廄裭4懙"
"l殛瓷雼浢Ü礽"
],
"capacity": {
"嵒ƫS捕ɷD¡轫n": "583"
"{囥": "721"
},
"conditions": [
{
"type": "Ü郀",
"status": "ƣKʘńw:5塋",
"lastProbeTime": "2588-10-04T08:20:38Z",
"lastTransitionTime": "2095-10-31T02:52:44Z",
"reason": "31",
"message": "32"
"type": "n(鲼ƳÐƣKʘńw:5塋訩塶\"=",
"status": "Ka縳讋ɮ衺勽Ƙq",
"lastProbeTime": "2343-07-10T15:32:16Z",
"lastTransitionTime": "2944-07-13T11:02:13Z",
"reason": "34",
"message": "35"
}
]
}

View File

@ -37,6 +37,10 @@ spec:
apiGroup: "28"
kind: "29"
name: "30"
dataSourceRef:
apiGroup: "31"
kind: "32"
name: "33"
resources:
limits:
Ď儇击3ƆìQ喞艋涽託仭w-檮Ǣ: "465"
@ -53,14 +57,14 @@ spec:
volumeName: "26"
status:
accessModes:
- ĭÐl恕ɍȇ廄裭4懙
- l殛瓷雼浢Ü礽
capacity:
嵒ƫS捕ɷD¡轫n: "583"
'{囥': "721"
conditions:
- lastProbeTime: "2588-10-04T08:20:38Z"
lastTransitionTime: "2095-10-31T02:52:44Z"
message: "32"
reason: "31"
status: ƣKʘńw:5塋
type: Ü郀
phase: 燴壩卄蓨MĮ?
- lastProbeTime: "2343-07-10T15:32:16Z"
lastTransitionTime: "2944-07-13T11:02:13Z"
message: "35"
reason: "34"
status: Ka縳讋ɮ衺勽Ƙq
type: n(鲼ƳÐƣKʘńw:5塋訩塶"=
phase: gɸ=ǤÆ碛,1ZƜ/C龷Ȫ

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ type PersistentVolumeClaimSpecApplyConfiguration struct {
StorageClassName *string `json:"storageClassName,omitempty"`
VolumeMode *v1.PersistentVolumeMode `json:"volumeMode,omitempty"`
DataSource *TypedLocalObjectReferenceApplyConfiguration `json:"dataSource,omitempty"`
DataSourceRef *TypedLocalObjectReferenceApplyConfiguration `json:"dataSourceRef,omitempty"`
}
// PersistentVolumeClaimSpecApplyConfiguration constructs an declarative configuration of the PersistentVolumeClaimSpec type for use with
@ -98,3 +99,11 @@ func (b *PersistentVolumeClaimSpecApplyConfiguration) WithDataSource(value *Type
b.DataSource = value
return b
}
// WithDataSourceRef sets the DataSourceRef field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the DataSourceRef field is set to the value of the last call.
func (b *PersistentVolumeClaimSpecApplyConfiguration) WithDataSourceRef(value *TypedLocalObjectReferenceApplyConfiguration) *PersistentVolumeClaimSpecApplyConfiguration {
b.DataSourceRef = value
return b
}

View File

@ -4916,6 +4916,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: dataSource
type:
namedType: io.k8s.api.core.v1.TypedLocalObjectReference
- name: dataSourceRef
type:
namedType: io.k8s.api.core.v1.TypedLocalObjectReference
- name: resources
type:
namedType: io.k8s.api.core.v1.ResourceRequirements