mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
Add API and validation for CrossNamespaceVolumeDataSource
This commit is contained in:
parent
623376bc82
commit
87c1ca88d4
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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"))
|
||||
|
@ -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)
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user