diff --git a/pkg/api/persistentvolumeclaim/util.go b/pkg/api/persistentvolumeclaim/util.go index 2f24c645b66..4334afcca59 100644 --- a/pkg/api/persistentvolumeclaim/util.go +++ b/pkg/api/persistentvolumeclaim/util.go @@ -32,11 +32,22 @@ 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 *core.PersistentVolumeClaimSpec) { +func DropDisabledFields(pvcSpec, oldPVCSpec *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 + if !dataSourceRefInUse(oldPVCSpec) { + pvcSpec.DataSourceRef = nil + } + } + + // Drop the contents of the dataSourceRef field if the CrossNamespaceVolumeDataSource + // feature gate is disabled and dataSourceRef.Namespace is specified. + if !utilfeature.DefaultFeatureGate.Enabled(features.CrossNamespaceVolumeDataSource) && + pvcSpec.DataSourceRef != nil && pvcSpec.DataSourceRef.Namespace != nil && len(*pvcSpec.DataSourceRef.Namespace) != 0 { + if !dataSourceRefInUse(oldPVCSpec) { + pvcSpec.DataSourceRef = nil + } } } @@ -116,6 +127,16 @@ func dataSourceIsPvcOrSnapshot(dataSource *core.TypedLocalObjectReference) bool return false } +func dataSourceRefInUse(oldPVCSpec *core.PersistentVolumeClaimSpec) bool { + if oldPVCSpec == nil { + return false + } + if oldPVCSpec.DataSourceRef != nil { + 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 @@ -126,10 +147,26 @@ func NormalizeDataSources(pvcSpec *core.PersistentVolumeClaimSpec) { } if pvcSpec.DataSource != nil && pvcSpec.DataSourceRef == nil { // Using the old way of setting a data source - pvcSpec.DataSourceRef = pvcSpec.DataSource.DeepCopy() + pvcSpec.DataSourceRef = &core.TypedObjectReference{ + Kind: pvcSpec.DataSource.Kind, + Name: pvcSpec.DataSource.Name, + } + if pvcSpec.DataSource.APIGroup != nil { + apiGroup := *pvcSpec.DataSource.APIGroup + pvcSpec.DataSourceRef.APIGroup = &apiGroup + } } else if pvcSpec.DataSourceRef != nil && pvcSpec.DataSource == nil { - // Using the new way of setting a data source - pvcSpec.DataSource = pvcSpec.DataSourceRef.DeepCopy() + if pvcSpec.DataSourceRef.Namespace == nil || len(*pvcSpec.DataSourceRef.Namespace) == 0 { + // Using the new way of setting a data source + pvcSpec.DataSource = &core.TypedLocalObjectReference{ + Kind: pvcSpec.DataSourceRef.Kind, + Name: pvcSpec.DataSourceRef.Name, + } + if pvcSpec.DataSourceRef.APIGroup != nil { + apiGroup := *pvcSpec.DataSourceRef.APIGroup + pvcSpec.DataSource.APIGroup = &apiGroup + } + } } } diff --git a/pkg/api/persistentvolumeclaim/util_test.go b/pkg/api/persistentvolumeclaim/util_test.go index a0e4f5fec63..945ff2f0446 100644 --- a/pkg/api/persistentvolumeclaim/util_test.go +++ b/pkg/api/persistentvolumeclaim/util_test.go @@ -154,23 +154,47 @@ func TestPVCDataSourceSpecFilter(t *testing.T) { } } -// TestAnyDataSourceFilter checks to ensure the AnyVolumeDataSource feature gate works -func TestAnyDataSourceFilter(t *testing.T) { - makeDataSource := func(apiGroup, kind, name string) *core.TypedLocalObjectReference { - return &core.TypedLocalObjectReference{ - APIGroup: &apiGroup, - Kind: kind, - Name: name, - } - } +var ( + coreGroup = "" + snapGroup = "snapshot.storage.k8s.io" + genericGroup = "generic.storage.k8s.io" + pvcKind = "PersistentVolumeClaim" + snapKind = "VolumeSnapshot" + genericKind = "Generic" + podKind = "Pod" +) - volumeDataSource := makeDataSource("", "PersistentVolumeClaim", "my-vol") +func makeDataSource(apiGroup, kind, name string) *core.TypedLocalObjectReference { + return &core.TypedLocalObjectReference{ + APIGroup: &apiGroup, + Kind: kind, + Name: name, + } +} + +func makeDataSourceRef(apiGroup, kind, name string, namespace *string) *core.TypedObjectReference { + return &core.TypedObjectReference{ + APIGroup: &apiGroup, + Kind: kind, + Name: name, + Namespace: namespace, + } +} + +// TestDataSourceFilter checks to ensure the AnyVolumeDataSource feature gate and CrossNamespaceVolumeDataSource works +func TestDataSourceFilter(t *testing.T) { + ns := "ns1" + volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol") + volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil) + xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns) var tests = map[string]struct { spec core.PersistentVolumeClaimSpec + oldSpec core.PersistentVolumeClaimSpec anyEnabled bool + xnsEnabled bool want *core.TypedLocalObjectReference - wantRef *core.TypedLocalObjectReference + wantRef *core.TypedObjectReference }{ "any disabled with empty ds": { spec: core.PersistentVolumeClaimSpec{}, @@ -180,10 +204,10 @@ func TestAnyDataSourceFilter(t *testing.T) { want: volumeDataSource, }, "any disabled with volume ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource}, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, }, "any disabled with both data sources": { - spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSource}, + spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef}, want: volumeDataSource, }, "any enabled with empty ds": { @@ -196,25 +220,63 @@ func TestAnyDataSourceFilter(t *testing.T) { want: volumeDataSource, }, "any enabled with volume ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource}, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, anyEnabled: true, - wantRef: volumeDataSource, + wantRef: volumeDataSourceRef, }, "any enabled with both data sources": { - spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSource}, + spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef}, anyEnabled: true, want: volumeDataSource, - wantRef: volumeDataSource, + wantRef: volumeDataSourceRef, + }, + "both any and xns enabled with xns volume ds": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsVolumeDataSourceRef, + }, + "both any and xns enabled with xns volume ds when xns volume exists in oldSpec": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsVolumeDataSourceRef, + }, + "only xns enabled with xns volume ds": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + xnsEnabled: true, + }, + "only any enabled with xns volume ds": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + anyEnabled: true, + }, + "only any enabled with xns volume ds when xns volume exists in oldSpec": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + anyEnabled: true, + wantRef: xnsVolumeDataSourceRef, // existing field isn't dropped. + }, + "only any enabled with xns volume ds when volume exists in oldSpec": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, + anyEnabled: true, + wantRef: xnsVolumeDataSourceRef, // existing field isn't dropped. }, } 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) - 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) + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, test.xnsEnabled)() + DropDisabledFields(&test.spec, &test.oldSpec) + if test.spec.DataSource != test.want { + t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSource: %+v", + testName, test.anyEnabled, test.xnsEnabled, test.spec, test.want) + } + if test.spec.DataSourceRef != test.wantRef { + t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSourceRef: %+v", + testName, test.anyEnabled, test.xnsEnabled, test.spec, test.wantRef) } }) } @@ -223,69 +285,99 @@ func TestAnyDataSourceFilter(t *testing.T) { // 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") + ns := "ns1" + volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol") + volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil) + xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns) + snapshotDataSource := makeDataSource(snapGroup, snapKind, "my-snap") + snapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", nil) + xnsSnapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", &ns) + genericDataSource := makeDataSource(genericGroup, genericKind, "my-foo") + genericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", nil) + xnsGenericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", &ns) + coreDataSource := makeDataSource(coreGroup, podKind, "my-pod") + coreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", nil) + xnsCoreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", &ns) var tests = map[string]struct { - spec core.PersistentVolumeClaimSpec - want *core.TypedLocalObjectReference + spec core.PersistentVolumeClaimSpec + want *core.TypedLocalObjectReference + wantRef *core.TypedObjectReference }{ "empty ds": { spec: core.PersistentVolumeClaimSpec{}, }, "volume ds": { - spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource}, - want: volumeDataSource, + spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource}, + want: volumeDataSource, + wantRef: volumeDataSourceRef, }, "snapshot ds": { - spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource}, - want: snapshotDataSource, + spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource}, + want: snapshotDataSource, + wantRef: snapshotDataSourceRef, }, "generic ds": { - spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource}, - want: genericDataSource, + spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource}, + want: genericDataSource, + wantRef: genericDataSourceRef, }, "core ds": { - spec: core.PersistentVolumeClaimSpec{DataSource: coreDataSource}, - want: coreDataSource, + spec: core.PersistentVolumeClaimSpec{DataSource: coreDataSource}, + want: coreDataSource, + wantRef: coreDataSourceRef, }, "volume ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSource}, - want: volumeDataSource, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, + want: volumeDataSource, + wantRef: volumeDataSourceRef, }, "snapshot ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: snapshotDataSource}, - want: snapshotDataSource, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: snapshotDataSourceRef}, + want: snapshotDataSource, + wantRef: snapshotDataSourceRef, }, "generic ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: genericDataSource}, - want: genericDataSource, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: genericDataSourceRef}, + want: genericDataSource, + wantRef: genericDataSourceRef, }, "core ds ref": { - spec: core.PersistentVolumeClaimSpec{DataSourceRef: coreDataSource}, - want: coreDataSource, + spec: core.PersistentVolumeClaimSpec{DataSourceRef: coreDataSourceRef}, + want: coreDataSource, + wantRef: coreDataSourceRef, + }, + "xns volume ds ref": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, + wantRef: xnsVolumeDataSourceRef, + }, + "xns snapshot ds ref": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsSnapshotDataSourceRef}, + wantRef: xnsSnapshotDataSourceRef, + }, + "xns generic ds ref": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsGenericDataSourceRef}, + wantRef: xnsGenericDataSourceRef, + }, + "xns core ds ref": { + spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsCoreDataSourceRef}, + wantRef: xnsCoreDataSourceRef, }, } defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, 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) + if !reflect.DeepEqual(test.spec.DataSource, test.want) { + t.Errorf("expected condition was not met, test: %s, spec.datasource: %+v, want: %+v", + testName, test.spec.DataSource, test.want) + } + if !reflect.DeepEqual(test.spec.DataSourceRef, test.wantRef) { + t.Errorf("expected condition was not met, test: %s, spec.datasourceRef: %+v, wantRef: %+v", + testName, test.spec.DataSourceRef, test.wantRef) } }) } diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index 1746640f1db..0622050a17d 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -452,29 +452,54 @@ type PersistentVolumeClaimSpec struct { // * 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. + // When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + // and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + // If the namespace is specified, then dataSourceRef will not be copied to dataSource. // +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 + // volume is desired. This may be any 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 + // 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 + // compatibility, when namespace isn't specified in dataSourceRef, + // 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 + // When namespace is specified in dataSourceRef, + // dataSource isn't set to the same value and must be empty. + // There are three 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 + // * While dataSource ignores disallowed values (dropping them), dataSourceRef // preserves all values, and generates an error if a disallowed value is // specified. + // * While dataSource only allows local objects, dataSourceRef allows objects + // in any namespaces. // (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + // (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. // +optional - DataSourceRef *TypedLocalObjectReference + DataSourceRef *TypedObjectReference +} + +type TypedObjectReference struct { + // APIGroup is the group for the resource being referenced. + // If APIGroup is not specified, the specified Kind must be in the core API group. + // For any other third-party types, APIGroup is required. + // +optional + APIGroup *string + // Kind is the type of resource being referenced + Kind string + // Name is the name of resource being referenced + Name string + // Namespace is the namespace of resource being referenced + // Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + // (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + // +featureGate=CrossNamespaceVolumeDataSource + // +optional + Namespace *string } // PersistentVolumeClaimConditionType defines the condition of PV claim. diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 91635d83dca..8c5611cec59 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -2104,7 +2104,34 @@ func validateDataSource(dataSource *core.TypedLocalObjectReference, fldPath *fie apiGroup = *dataSource.APIGroup } if len(apiGroup) == 0 && dataSource.Kind != "PersistentVolumeClaim" { - allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, "")) + allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup")) + } + + return allErrs +} + +// validateDataSourceRef validates a DataSourceRef in a PersistentVolumeClaimSpec +func validateDataSourceRef(dataSourceRef *core.TypedObjectReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(dataSourceRef.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + if len(dataSourceRef.Kind) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) + } + apiGroup := "" + if dataSourceRef.APIGroup != nil { + apiGroup = *dataSourceRef.APIGroup + } + if len(apiGroup) == 0 && dataSourceRef.Kind != "PersistentVolumeClaim" { + allErrs = append(allErrs, field.Invalid(fldPath, dataSourceRef.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup")) + } + + if dataSourceRef.Namespace != nil && len(*dataSourceRef.Namespace) > 0 { + for _, msg := range ValidateNameFunc(ValidateNamespaceName)(*dataSourceRef.Namespace, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), *dataSourceRef.Namespace, msg)) + } } return allErrs @@ -2166,10 +2193,15 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld allErrs = append(allErrs, validateDataSource(spec.DataSource, fldPath.Child("dataSource"))...) } if spec.DataSourceRef != nil { - allErrs = append(allErrs, validateDataSource(spec.DataSourceRef, fldPath.Child("dataSourceRef"))...) + allErrs = append(allErrs, validateDataSourceRef(spec.DataSourceRef, fldPath.Child("dataSourceRef"))...) } - if spec.DataSource != nil && spec.DataSourceRef != nil { - if !apiequality.Semantic.DeepEqual(spec.DataSource, spec.DataSourceRef) { + if spec.DataSourceRef != nil && spec.DataSourceRef.Namespace != nil && len(*spec.DataSourceRef.Namespace) > 0 { + if spec.DataSource != nil { + allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"), + "may not be specified when dataSourceRef.namespace is specified")) + } + } else if spec.DataSource != nil && spec.DataSourceRef != nil { + if !isDataSourceEqualDataSourceRef(spec.DataSource, spec.DataSourceRef) { allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"), "must match dataSourceRef")) } @@ -2178,6 +2210,10 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld return allErrs } +func isDataSourceEqualDataSourceRef(dataSource *core.TypedLocalObjectReference, dataSourceRef *core.TypedObjectReference) bool { + return reflect.DeepEqual(dataSource.APIGroup, dataSourceRef.APIGroup) && dataSource.Kind == dataSourceRef.Kind && dataSource.Name == dataSourceRef.Name +} + // ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 15629884d56..9947b30045b 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -1672,7 +1672,7 @@ func testValidatePVC(t *testing.T, ephemeral bool) { Kind: "PersistentVolumeClaim", Name: "pvc1", }, - DataSourceRef: &core.TypedLocalObjectReference{ + DataSourceRef: &core.TypedObjectReference{ Kind: "PersistentVolumeClaim", Name: "pvc2", }, @@ -19816,7 +19816,11 @@ func testAnyDataSource(t *testing.T, ds, dsRef bool) { for _, tc := range testCases { if dsRef { - tc.claimSpec.DataSourceRef = tc.claimSpec.DataSource.DeepCopy() + tc.claimSpec.DataSourceRef = &core.TypedObjectReference{ + APIGroup: tc.claimSpec.DataSource.APIGroup, + Kind: tc.claimSpec.DataSource.Kind, + Name: tc.claimSpec.DataSource.Name, + } } if !ds { tc.claimSpec.DataSource = nil @@ -19840,6 +19844,110 @@ func TestAnyDataSource(t *testing.T) { testAnyDataSource(t, true, false) } +func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec { + scName := "csi-plugin" + spec := core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + StorageClassName: &scName, + DataSourceRef: &core.TypedObjectReference{ + APIGroup: apiGroup, + Kind: kind, + Namespace: namespace, + Name: name, + }, + } + + if isDataSourceSet { + spec.DataSource = &core.TypedLocalObjectReference{ + APIGroup: apiGroup, + Kind: kind, + Name: name, + } + } + return &spec +} + +func TestCrossNamespaceSource(t *testing.T) { + snapAPIGroup := "snapshot.storage.k8s.io" + coreAPIGroup := "" + unsupportedAPIGroup := "unsupported.example.com" + snapKind := "VolumeSnapshot" + pvcKind := "PersistentVolumeClaim" + goodNS := "ns1" + badNS := "a*b" + emptyNS := "" + goodName := "snapshot1" + + testCases := []struct { + testName string + expectedFail bool + claimSpec *core.PersistentVolumeClaimSpec + }{ + { + testName: "Feature gate enabled and valid xns DataSourceRef specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with PVC source specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with unsupported source specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with nil apiGroup", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with invalid namspace specified", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with nil namspace specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false), + }, + { + testName: "Feature gate enabled and xns DataSourceRef with empty namspace specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false), + }, + { + testName: "Feature gate enabled and both xns DataSourceRef and DataSource specified", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true), + }, + } + + for _, tc := range testCases { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)() + opts := PersistentVolumeClaimSpecValidationOptions{} + if tc.expectedFail { + if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 { + t.Errorf("%s: expected failure: %v", tc.testName, errs) + } + } else { + if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 { + t.Errorf("%s: expected success: %v", tc.testName, errs) + } + } + } +} + func TestValidateTopologySpreadConstraints(t *testing.T) { fieldPath := field.NewPath("field") subFldPath0 := fieldPath.Index(0) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index e4abde3d789..a84d7368e7b 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -36,6 +36,13 @@ const ( // of code conflicts because changes are more likely to be scattered // across the file. + // owner: @ttakahashi21 @mkimuram + // kep: https://kep.k8s.io/3294 + // alpha: v1.26 + // + // Enable usage of Provision of PVCs from snapshots in other namespaces + CrossNamespaceVolumeDataSource featuregate.Feature = "CrossNamespaceVolumeDataSource" + // owner: @bswartz // alpha: v1.18 // beta: v1.24 @@ -893,6 +900,8 @@ func init() { // Entries are separated from each other with blank lines to avoid sweeping gofmt changes // when adding or removing one entry. var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + CrossNamespaceVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha}, + AnyVolumeDataSource: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.24 APISelfSubjectReview: {Default: false, PreRelease: featuregate.Alpha}, diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go index 5cbdc2a65ca..54c0ca625f5 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go @@ -216,11 +216,14 @@ 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", } + dataSourceRef := api.TypedObjectReference{ + Kind: "PersistentVolumeClaim", + Name: "my-pvc", + } var tests = map[string]struct { anyEnabled bool @@ -278,15 +281,15 @@ func TestDefaultOnReadPvc(t *testing.T) { pvc.Spec.DataSource = dataSource.DeepCopy() } if test.dataSourceRef { - pvc.Spec.DataSourceRef = dataSource.DeepCopy() + pvc.Spec.DataSourceRef = dataSourceRef.DeepCopy() } var expectDataSource *api.TypedLocalObjectReference if test.want { expectDataSource = &dataSource } - var expectDataSourceRef *api.TypedLocalObjectReference + var expectDataSourceRef *api.TypedObjectReference if test.wantRef { - expectDataSourceRef = &dataSource + expectDataSourceRef = &dataSourceRef } // Method under test diff --git a/pkg/registry/core/persistentvolumeclaim/strategy.go b/pkg/registry/core/persistentvolumeclaim/strategy.go index 14bf61e2b33..2352c8edc4e 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy.go @@ -65,7 +65,7 @@ func (persistentvolumeclaimStrategy) GetResetFields() map[fieldpath.APIVersion]* func (persistentvolumeclaimStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { pvc := obj.(*api.PersistentVolumeClaim) pvc.Status = api.PersistentVolumeClaimStatus{} - pvcutil.DropDisabledFields(&pvc.Spec) + pvcutil.DropDisabledFields(&pvc.Spec, nil) // For data sources, we need to do 2 things to implement KEP 1495 @@ -103,7 +103,7 @@ func (persistentvolumeclaimStrategy) PrepareForUpdate(ctx context.Context, obj, oldPvc := old.(*api.PersistentVolumeClaim) newPvc.Status = oldPvc.Status - pvcutil.DropDisabledFields(&newPvc.Spec) + pvcutil.DropDisabledFields(&newPvc.Spec, &oldPvc.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 diff --git a/pkg/registry/core/persistentvolumeclaim/strategy_test.go b/pkg/registry/core/persistentvolumeclaim/strategy_test.go index 1f6a332a9b9..a868b8bb922 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy_test.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy_test.go @@ -107,28 +107,58 @@ func TestDropConditions(t *testing.T) { } +var ( + coreGroup = "" + snapGroup = "snapshot.storage.k8s.io" + genericGroup = "generic.storage.k8s.io" + pvcKind = "PersistentVolumeClaim" + snapKind = "VolumeSnapshot" + genericKind = "Generic" + podKind = "Pod" +) + +func makeDataSource(apiGroup, kind, name string) *api.TypedLocalObjectReference { + return &api.TypedLocalObjectReference{ + APIGroup: &apiGroup, + Kind: kind, + Name: name, + } + +} + +func makeDataSourceRef(apiGroup, kind, name string, namespace *string) *api.TypedObjectReference { + return &api.TypedObjectReference{ + APIGroup: &apiGroup, + Kind: kind, + Name: name, + Namespace: namespace, + } +} + 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") + ns := "ns1" + volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol") + volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil) + xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns) + snapshotDataSource := makeDataSource(snapGroup, snapKind, "my-snap") + snapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", nil) + xnsSnapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", &ns) + genericDataSource := makeDataSource(genericGroup, genericKind, "my-foo") + genericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", nil) + xnsGenericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", &ns) + coreDataSource := makeDataSource(coreGroup, podKind, "my-pod") + coreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", nil) + xnsCoreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", &ns) var tests = map[string]struct { anyEnabled bool + xnsEnabled bool dataSource *api.TypedLocalObjectReference - dataSourceRef *api.TypedLocalObjectReference + dataSourceRef *api.TypedObjectReference want *api.TypedLocalObjectReference - wantRef *api.TypedLocalObjectReference + wantRef *api.TypedObjectReference }{ "any disabled with empty ds": { want: nil, @@ -150,16 +180,16 @@ func TestPrepareForCreate(t *testing.T) { want: nil, }, "any disabled with volume ds ref": { - dataSourceRef: volumeDataSource, + dataSourceRef: volumeDataSourceRef, }, "any disabled with snapshot ds ref": { - dataSourceRef: snapshotDataSource, + dataSourceRef: snapshotDataSourceRef, }, "any disabled with generic ds ref": { - dataSourceRef: genericDataSource, + dataSourceRef: genericDataSourceRef, }, "any disabled with invalid ds ref": { - dataSourceRef: coreDataSource, + dataSourceRef: coreDataSourceRef, }, "any enabled with empty ds": { anyEnabled: true, @@ -169,13 +199,13 @@ func TestPrepareForCreate(t *testing.T) { dataSource: volumeDataSource, anyEnabled: true, want: volumeDataSource, - wantRef: volumeDataSource, + wantRef: volumeDataSourceRef, }, "any enabled with snapshot ds": { dataSource: snapshotDataSource, anyEnabled: true, want: snapshotDataSource, - wantRef: snapshotDataSource, + wantRef: snapshotDataSourceRef, }, "any enabled with generic ds": { dataSource: genericDataSource, @@ -186,41 +216,135 @@ func TestPrepareForCreate(t *testing.T) { anyEnabled: true, }, "any enabled with volume ds ref": { - dataSourceRef: volumeDataSource, + dataSourceRef: volumeDataSourceRef, anyEnabled: true, want: volumeDataSource, - wantRef: volumeDataSource, + wantRef: volumeDataSourceRef, }, "any enabled with snapshot ds ref": { - dataSourceRef: snapshotDataSource, + dataSourceRef: snapshotDataSourceRef, anyEnabled: true, want: snapshotDataSource, - wantRef: snapshotDataSource, + wantRef: snapshotDataSourceRef, }, "any enabled with generic ds ref": { - dataSourceRef: genericDataSource, + dataSourceRef: genericDataSourceRef, anyEnabled: true, want: genericDataSource, - wantRef: genericDataSource, + wantRef: genericDataSourceRef, }, "any enabled with invalid ds ref": { - dataSourceRef: coreDataSource, + dataSourceRef: coreDataSourceRef, anyEnabled: true, want: coreDataSource, - wantRef: coreDataSource, + wantRef: coreDataSourceRef, }, "any enabled with mismatched data sources": { dataSource: volumeDataSource, - dataSourceRef: snapshotDataSource, + dataSourceRef: snapshotDataSourceRef, anyEnabled: true, want: volumeDataSource, - wantRef: snapshotDataSource, + wantRef: snapshotDataSourceRef, + }, + "both any and xns enabled with empty ds": { + anyEnabled: true, + xnsEnabled: true, + want: nil, + }, + "both any and xns enabled with volume ds": { + dataSource: volumeDataSource, + anyEnabled: true, + xnsEnabled: true, + want: volumeDataSource, + wantRef: volumeDataSourceRef, + }, + "both any and xns enabled with snapshot ds": { + dataSource: snapshotDataSource, + anyEnabled: true, + xnsEnabled: true, + want: snapshotDataSource, + wantRef: snapshotDataSourceRef, + }, + "both any and xns enabled with generic ds": { + dataSource: genericDataSource, + anyEnabled: true, + xnsEnabled: true, + }, + "both any and xns enabled with invalid ds": { + dataSource: coreDataSource, + anyEnabled: true, + xnsEnabled: true, + }, + "both any and xns enabled with volume ds ref": { + dataSourceRef: volumeDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + want: volumeDataSource, + wantRef: volumeDataSourceRef, + }, + "both any and xns enabled with snapshot ds ref": { + dataSourceRef: snapshotDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + want: snapshotDataSource, + wantRef: snapshotDataSourceRef, + }, + "both any and xns enabled with generic ds ref": { + dataSourceRef: genericDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + want: genericDataSource, + wantRef: genericDataSourceRef, + }, + "both any and xns enabled with invalid ds ref": { + dataSourceRef: coreDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + want: coreDataSource, + wantRef: coreDataSourceRef, + }, + "both any and xns enabled with mismatched data sources": { + dataSource: volumeDataSource, + dataSourceRef: snapshotDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + want: volumeDataSource, + wantRef: snapshotDataSourceRef, + }, + "both any and xns enabled with volume xns ds ref": { + dataSourceRef: xnsVolumeDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsVolumeDataSourceRef, + }, + "both any and xns enabled with snapshot xns ds ref": { + dataSourceRef: xnsSnapshotDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsSnapshotDataSourceRef, + }, + "both any and xns enabled with generic xns ds ref": { + dataSourceRef: xnsGenericDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsGenericDataSourceRef, + }, + "both any and xns enabled with invalid xns ds ref": { + dataSourceRef: xnsCoreDataSourceRef, + anyEnabled: true, + xnsEnabled: true, + wantRef: xnsCoreDataSourceRef, + }, + "only xns enabled with snapshot xns ds ref": { + dataSourceRef: xnsSnapshotDataSourceRef, + xnsEnabled: true, }, } for testName, test := range tests { t.Run(testName, func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, test.xnsEnabled)() pvc := api.PersistentVolumeClaim{ Spec: api.PersistentVolumeClaimSpec{ DataSource: test.dataSource, diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index bae7e8940b1..0b8ecc6b270 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -497,29 +497,54 @@ type PersistentVolumeClaimSpec struct { // * 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. + // When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + // and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + // If the namespace is specified, then dataSourceRef will not be copied to dataSource. // +optional DataSource *TypedLocalObjectReference `json:"dataSource,omitempty" protobuf:"bytes,7,opt,name=dataSource"` // 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 + // volume is desired. This may be any 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 + // 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 + // compatibility, when namespace isn't specified in dataSourceRef, + // 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 + // When namespace is specified in dataSourceRef, + // dataSource isn't set to the same value and must be empty. + // There are three 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 + // * While dataSource ignores disallowed values (dropping them), dataSourceRef // preserves all values, and generates an error if a disallowed value is // specified. + // * While dataSource only allows local objects, dataSourceRef allows objects + // in any namespaces. // (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + // (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. // +optional - DataSourceRef *TypedLocalObjectReference `json:"dataSourceRef,omitempty" protobuf:"bytes,8,opt,name=dataSourceRef"` + DataSourceRef *TypedObjectReference `json:"dataSourceRef,omitempty" protobuf:"bytes,8,opt,name=dataSourceRef"` +} + +type TypedObjectReference struct { + // APIGroup is the group for the resource being referenced. + // If APIGroup is not specified, the specified Kind must be in the core API group. + // For any other third-party types, APIGroup is required. + // +optional + APIGroup *string `json:"apiGroup" protobuf:"bytes,1,opt,name=apiGroup"` + // Kind is the type of resource being referenced + Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` + // Name is the name of resource being referenced + Name string `json:"name" protobuf:"bytes,3,opt,name=name"` + // Namespace is the namespace of resource being referenced + // Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + // (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + // +featureGate=CrossNamespaceVolumeDataSource + // +optional + Namespace *string `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"` } // PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type diff --git a/test/e2e/storage/testsuites/multivolume.go b/test/e2e/storage/testsuites/multivolume.go index 002fa166ef5..3a96a391558 100644 --- a/test/e2e/storage/testsuites/multivolume.go +++ b/test/e2e/storage/testsuites/multivolume.go @@ -342,7 +342,7 @@ func (t *multiVolumeTestSuite) DefineTests(driver storageframework.TestDriver, p } testConfig := storageframework.ConvertTestConfig(l.config) dc := l.config.Framework.DynamicClient - dataSource := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, resource.Pvc, resource.Sc, sDriver, pattern.VolMode, expectedContent) + dataSourceRef := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, resource.Pvc, resource.Sc, sDriver, pattern.VolMode, expectedContent) // Create 2nd PVC for testing pvc2 := &v1.PersistentVolumeClaim{ @@ -353,7 +353,7 @@ func (t *multiVolumeTestSuite) DefineTests(driver storageframework.TestDriver, p } resource.Pvc.Spec.DeepCopyInto(&pvc2.Spec) pvc2.Spec.VolumeName = "" - pvc2.Spec.DataSource = dataSource + pvc2.Spec.DataSourceRef = dataSourceRef pvc2, err := l.cs.CoreV1().PersistentVolumeClaims(pvc2.Namespace).Create(context.TODO(), pvc2, metav1.CreateOptions{}) framework.ExpectNoError(err) @@ -386,7 +386,7 @@ func (t *multiVolumeTestSuite) DefineTests(driver storageframework.TestDriver, p l.resources = append(l.resources, resource) pvcs := []*v1.PersistentVolumeClaim{resource.Pvc} testConfig := storageframework.ConvertTestConfig(l.config) - dataSource := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, resource.Pvc, resource.Sc, pattern.VolMode, expectedContent) + dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, resource.Pvc, resource.Sc, pattern.VolMode, expectedContent) // Create 2nd PVC for testing pvc2 := &v1.PersistentVolumeClaim{ @@ -397,7 +397,7 @@ func (t *multiVolumeTestSuite) DefineTests(driver storageframework.TestDriver, p } resource.Pvc.Spec.DeepCopyInto(&pvc2.Spec) pvc2.Spec.VolumeName = "" - pvc2.Spec.DataSource = dataSource + pvc2.Spec.DataSourceRef = dataSourceRef pvc2, err := l.cs.CoreV1().PersistentVolumeClaims(pvc2.Namespace).Create(context.TODO(), pvc2, metav1.CreateOptions{}) framework.ExpectNoError(err) diff --git a/test/e2e/storage/testsuites/provisioning.go b/test/e2e/storage/testsuites/provisioning.go index 70e4086b8f6..5a18638d25f 100644 --- a/test/e2e/storage/testsuites/provisioning.go +++ b/test/e2e/storage/testsuites/provisioning.go @@ -214,9 +214,9 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, dc := l.config.Framework.DynamicClient testConfig := storageframework.ConvertTestConfig(l.config) expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) - dataSource := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, l.pvc, l.sc, sDriver, pattern.VolMode, expectedContent) + dataSourceRef := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, l.pvc, l.sc, sDriver, pattern.VolMode, expectedContent) - l.pvc.Spec.DataSource = dataSource + l.pvc.Spec.DataSourceRef = dataSourceRef l.testCase.PvCheck = func(claim *v1.PersistentVolumeClaim) { ginkgo.By("checking whether the created volume has the pre-populated data") tests := []e2evolume.Test{ @@ -386,7 +386,7 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, }() apiGroup := "hello.example.com" - l.pvc.Spec.DataSourceRef = &v1.TypedLocalObjectReference{ + l.pvc.Spec.DataSourceRef = &v1.TypedObjectReference{ APIGroup: &apiGroup, Kind: "Hello", Name: helloCRName, @@ -427,8 +427,8 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, } testConfig := storageframework.ConvertTestConfig(l.config) expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) - dataSource := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) - l.pvc.Spec.DataSource = dataSource + dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) + l.pvc.Spec.DataSourceRef = dataSourceRef l.testCase.NodeSelection = testConfig.ClientNodeSelection l.testCase.PvCheck = func(claim *v1.PersistentVolumeClaim) { ginkgo.By("checking whether the created volume has the pre-populated data") @@ -443,7 +443,7 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, e2evolume.TestVolumeClientSlow(f, testConfig, nil, "", tests) } // Cloning fails if the source disk is still in the process of detaching, so we wait for the VolumeAttachment to be removed before cloning. - volumeAttachment := e2evolume.GetVolumeAttachmentName(f.ClientSet, testConfig, l.testCase.Provisioner, dataSource.Name, l.sourcePVC.Namespace) + volumeAttachment := e2evolume.GetVolumeAttachmentName(f.ClientSet, testConfig, l.testCase.Provisioner, dataSourceRef.Name, l.sourcePVC.Namespace) e2evolume.WaitForVolumeAttachmentTerminated(volumeAttachment, f.ClientSet, f.Timeouts.DataSourceProvision) l.testCase.TestDynamicProvisioning(ctx) }) @@ -468,8 +468,8 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, } testConfig := storageframework.ConvertTestConfig(l.config) expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) - dataSource := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) - l.pvc.Spec.DataSource = dataSource + dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) + l.pvc.Spec.DataSourceRef = dataSourceRef var wg sync.WaitGroup for i := 0; i < 5; i++ { @@ -497,7 +497,7 @@ func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, e2evolume.TestVolumeClientSlow(f, myTestConfig, nil, "", tests) } // Cloning fails if the source disk is still in the process of detaching, so we wait for the VolumeAttachment to be removed before cloning. - volumeAttachment := e2evolume.GetVolumeAttachmentName(f.ClientSet, testConfig, l.testCase.Provisioner, dataSource.Name, l.sourcePVC.Namespace) + volumeAttachment := e2evolume.GetVolumeAttachmentName(f.ClientSet, testConfig, l.testCase.Provisioner, dataSourceRef.Name, l.sourcePVC.Namespace) e2evolume.WaitForVolumeAttachmentTerminated(volumeAttachment, f.ClientSet, f.Timeouts.DataSourceProvision) t.TestDynamicProvisioning(ctx) }(i) @@ -1049,7 +1049,7 @@ func prepareSnapshotDataSourceForProvisioning( sDriver storageframework.SnapshottableTestDriver, mode v1.PersistentVolumeMode, injectContent string, -) *v1.TypedLocalObjectReference { +) *v1.TypedObjectReference { SetupStorageClass(ctx, client, class) if initClaim.ResourceVersion != "" { @@ -1078,7 +1078,7 @@ func prepareSnapshotDataSourceForProvisioning( parameters := map[string]string{} snapshotResource := storageframework.CreateSnapshotResource(sDriver, perTestConfig, pattern, initClaim.GetName(), initClaim.GetNamespace(), f.Timeouts, parameters) group := "snapshot.storage.k8s.io" - dataSourceRef := &v1.TypedLocalObjectReference{ + dataSourceRef := &v1.TypedObjectReference{ APIGroup: &group, Kind: "VolumeSnapshot", Name: snapshotResource.Vs.GetName(), @@ -1108,7 +1108,7 @@ func preparePVCDataSourceForProvisioning( class *storagev1.StorageClass, mode v1.PersistentVolumeMode, injectContent string, -) *v1.TypedLocalObjectReference { +) *v1.TypedObjectReference { SetupStorageClass(ctx, client, class) if source.ResourceVersion != "" { @@ -1130,7 +1130,7 @@ func preparePVCDataSourceForProvisioning( } e2evolume.InjectContent(f, config, nil, "", tests) - dataSourceRef := &v1.TypedLocalObjectReference{ + dataSourceRef := &v1.TypedObjectReference{ Kind: "PersistentVolumeClaim", Name: source.GetName(), }