Add API and validation for CrossNamespaceVolumeDataSource
This commit is contained in:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user