Merge pull request #77516 from gnufied/implement-resize-secrets
Add a new field for storing volume expansion secrets
This commit is contained in:
@@ -28,6 +28,23 @@ func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.Persist
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && !volumeModeInUse(oldPVSpec) {
|
||||
pvSpec.VolumeMode = nil
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandCSIVolumes) && !hasExpansionSecrets(oldPVSpec) {
|
||||
if pvSpec.CSI != nil {
|
||||
pvSpec.CSI.ControllerExpandSecretRef = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasExpansionSecrets(oldPVSpec *api.PersistentVolumeSpec) bool {
|
||||
if oldPVSpec == nil || oldPVSpec.CSI == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if oldPVSpec.CSI.ControllerExpandSecretRef != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func volumeModeInUse(oldPVSpec *api.PersistentVolumeSpec) bool {
|
||||
|
@@ -32,14 +32,20 @@ func TestDropDisabledFields(t *testing.T) {
|
||||
return &api.PersistentVolumeSpec{VolumeMode: mode}
|
||||
}
|
||||
|
||||
secretRef := &api.SecretReference{
|
||||
Name: "expansion-secret",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
modeBlock := api.PersistentVolumeBlock
|
||||
|
||||
tests := map[string]struct {
|
||||
oldSpec *api.PersistentVolumeSpec
|
||||
newSpec *api.PersistentVolumeSpec
|
||||
expectOldSpec *api.PersistentVolumeSpec
|
||||
expectNewSpec *api.PersistentVolumeSpec
|
||||
blockEnabled bool
|
||||
oldSpec *api.PersistentVolumeSpec
|
||||
newSpec *api.PersistentVolumeSpec
|
||||
expectOldSpec *api.PersistentVolumeSpec
|
||||
expectNewSpec *api.PersistentVolumeSpec
|
||||
blockEnabled bool
|
||||
csiExpansionEnabled bool
|
||||
}{
|
||||
"disabled block clears new": {
|
||||
blockEnabled: false,
|
||||
@@ -84,11 +90,47 @@ func TestDropDisabledFields(t *testing.T) {
|
||||
oldSpec: specWithMode(&modeBlock),
|
||||
expectOldSpec: specWithMode(&modeBlock),
|
||||
},
|
||||
"disabled csi expansion clears secrets": {
|
||||
csiExpansionEnabled: false,
|
||||
newSpec: specWithCSISecrets(secretRef),
|
||||
expectNewSpec: specWithCSISecrets(nil),
|
||||
oldSpec: nil,
|
||||
expectOldSpec: nil,
|
||||
},
|
||||
"enabled csi expansion preserve secrets": {
|
||||
csiExpansionEnabled: true,
|
||||
newSpec: specWithCSISecrets(secretRef),
|
||||
expectNewSpec: specWithCSISecrets(secretRef),
|
||||
oldSpec: nil,
|
||||
expectOldSpec: nil,
|
||||
},
|
||||
"enabled csi expansion preserve secrets when both old and new have it": {
|
||||
csiExpansionEnabled: true,
|
||||
newSpec: specWithCSISecrets(secretRef),
|
||||
expectNewSpec: specWithCSISecrets(secretRef),
|
||||
oldSpec: specWithCSISecrets(secretRef),
|
||||
expectOldSpec: specWithCSISecrets(secretRef),
|
||||
},
|
||||
"disabled csi expansion old pv had secrets": {
|
||||
csiExpansionEnabled: false,
|
||||
newSpec: specWithCSISecrets(secretRef),
|
||||
expectNewSpec: specWithCSISecrets(secretRef),
|
||||
oldSpec: specWithCSISecrets(secretRef),
|
||||
expectOldSpec: specWithCSISecrets(secretRef),
|
||||
},
|
||||
"enabled csi expansion preserves secrets when old pv did not had secrets": {
|
||||
csiExpansionEnabled: true,
|
||||
newSpec: specWithCSISecrets(secretRef),
|
||||
expectNewSpec: specWithCSISecrets(secretRef),
|
||||
oldSpec: specWithCSISecrets(nil),
|
||||
expectOldSpec: specWithCSISecrets(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, tc.blockEnabled)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandCSIVolumes, tc.csiExpansionEnabled)()
|
||||
|
||||
DropDisabledFields(tc.newSpec, tc.oldSpec)
|
||||
if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) {
|
||||
@@ -100,3 +142,19 @@ func TestDropDisabledFields(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func specWithCSISecrets(secret *api.SecretReference) *api.PersistentVolumeSpec {
|
||||
pvSpec := &api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
CSI: &api.CSIPersistentVolumeSource{
|
||||
Driver: "com.google.gcepd",
|
||||
VolumeHandle: "foobar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if secret != nil {
|
||||
pvSpec.CSI.ControllerExpandSecretRef = secret
|
||||
}
|
||||
return pvSpec
|
||||
}
|
||||
|
@@ -119,6 +119,12 @@ func VisitPVSecretNames(pv *corev1.PersistentVolume, visitor Visitor) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if source.CSI.ControllerExpandSecretRef != nil {
|
||||
if !visitor(source.CSI.ControllerExpandSecretRef.Namespace, source.CSI.ControllerExpandSecretRef.Name, false /* kubeletVisible */) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if source.CSI.NodePublishSecretRef != nil {
|
||||
if !visitor(source.CSI.NodePublishSecretRef.Namespace, source.CSI.NodePublishSecretRef.Name, true /* kubeletVisible */) {
|
||||
return false
|
||||
|
@@ -142,9 +142,17 @@ func TestPVSecrets(t *testing.T) {
|
||||
NodeStageSecretRef: &corev1.SecretReference{
|
||||
Name: "Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
|
||||
Namespace: "csi"}}}}},
|
||||
{Spec: corev1.PersistentVolumeSpec{
|
||||
ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
|
||||
PersistentVolumeSource: corev1.PersistentVolumeSource{
|
||||
CSI: &corev1.CSIPersistentVolumeSource{
|
||||
ControllerExpandSecretRef: &corev1.SecretReference{
|
||||
Name: "Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
|
||||
Namespace: "csi"}}}}},
|
||||
}
|
||||
extractedNames := sets.NewString()
|
||||
extractedNamesWithNamespace := sets.NewString()
|
||||
|
||||
for _, pv := range pvs {
|
||||
VisitPVSecretNames(pv, func(namespace, name string, kubeletVisible bool) bool {
|
||||
extractedNames.Insert(name)
|
||||
@@ -172,6 +180,7 @@ func TestPVSecrets(t *testing.T) {
|
||||
"Spec.PersistentVolumeSource.CSI.ControllerPublishSecretRef",
|
||||
"Spec.PersistentVolumeSource.CSI.NodePublishSecretRef",
|
||||
"Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
|
||||
"Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
|
||||
)
|
||||
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.PersistentVolume{}))
|
||||
secretPaths = secretPaths.Difference(excludedSecretPaths)
|
||||
@@ -219,6 +228,7 @@ func TestPVSecrets(t *testing.T) {
|
||||
"csi/Spec.PersistentVolumeSource.CSI.ControllerPublishSecretRef",
|
||||
"csi/Spec.PersistentVolumeSource.CSI.NodePublishSecretRef",
|
||||
"csi/Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
|
||||
"csi/Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
|
||||
)
|
||||
if missingNames := expectedNamespacedNames.Difference(extractedNamesWithNamespace); len(missingNames) > 0 {
|
||||
t.Logf("Missing expected namespaced names:\n%s", strings.Join(missingNames.List(), "\n"))
|
||||
|
@@ -1601,6 +1601,15 @@ type CSIPersistentVolumeSource struct {
|
||||
// secret object contains more than one secret, all secrets are passed.
|
||||
// +optional
|
||||
NodePublishSecretRef *SecretReference
|
||||
|
||||
// ControllerExpandSecretRef is a reference to the secret object containing
|
||||
// sensitive information to pass to the CSI driver to complete the CSI
|
||||
// ControllerExpandVolume call.
|
||||
// This is an alpha field and requires enabling ExpandCSIVolumes feature gate.
|
||||
// This field is optional, and may be empty if no secret is required. If the
|
||||
// secret object contains more than one secret, all secrets are passed.
|
||||
// +optional
|
||||
ControllerExpandSecretRef *SecretReference
|
||||
}
|
||||
|
||||
// Represents a source location of a volume to mount, managed by an external CSI driver
|
||||
|
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
@@ -2310,6 +2310,7 @@ func autoConvert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource(
|
||||
out.ControllerPublishSecretRef = (*core.SecretReference)(unsafe.Pointer(in.ControllerPublishSecretRef))
|
||||
out.NodeStageSecretRef = (*core.SecretReference)(unsafe.Pointer(in.NodeStageSecretRef))
|
||||
out.NodePublishSecretRef = (*core.SecretReference)(unsafe.Pointer(in.NodePublishSecretRef))
|
||||
out.ControllerExpandSecretRef = (*core.SecretReference)(unsafe.Pointer(in.ControllerExpandSecretRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2327,6 +2328,7 @@ func autoConvert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource(
|
||||
out.ControllerPublishSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.ControllerPublishSecretRef))
|
||||
out.NodeStageSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.NodeStageSecretRef))
|
||||
out.NodePublishSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.NodePublishSecretRef))
|
||||
out.ControllerExpandSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.ControllerExpandSecretRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -1479,6 +1479,19 @@ func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldP
|
||||
}
|
||||
}
|
||||
|
||||
if csi.ControllerExpandSecretRef != nil {
|
||||
if len(csi.ControllerExpandSecretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerExpandSecretRef", "name"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerExpandSecretRef.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
if len(csi.ControllerExpandSecretRef.Namespace) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerExpandSecretRef", "namespace"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerExpandSecretRef.Namespace, fldPath.Child("namespace"))...)
|
||||
}
|
||||
}
|
||||
|
||||
if csi.NodePublishSecretRef != nil {
|
||||
if len(csi.NodePublishSecretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("nodePublishSecretRef ", "name"), ""))
|
||||
@@ -1772,12 +1785,17 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
|
||||
func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList {
|
||||
allErrs := ValidatePersistentVolume(newPv)
|
||||
|
||||
// if oldPV does not have ControllerExpandSecretRef then allow it to be set
|
||||
if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) &&
|
||||
(newPv.Spec.CSI != nil && newPv.Spec.CSI.ControllerExpandSecretRef != nil) {
|
||||
newPv = newPv.DeepCopy()
|
||||
newPv.Spec.CSI.ControllerExpandSecretRef = nil
|
||||
}
|
||||
|
||||
// PersistentVolumeSource should be immutable after creation.
|
||||
if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), "is immutable after creation"))
|
||||
}
|
||||
newPv.Status = oldPv.Status
|
||||
|
||||
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
|
||||
|
||||
// Allow setting NodeAffinity if oldPv NodeAffinity was not set
|
||||
|
@@ -455,10 +455,31 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
||||
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||
},
|
||||
}
|
||||
|
||||
validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
CSI: &core.CSIPersistentVolumeSource{
|
||||
Driver: "come.google.gcepd",
|
||||
VolumeHandle: "foobar",
|
||||
},
|
||||
},
|
||||
StorageClassName: "gp2",
|
||||
})
|
||||
|
||||
expandSecretRef := &core.SecretReference{
|
||||
Name: "expansion-secret",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
oldVolume *core.PersistentVolume
|
||||
newVolume *core.PersistentVolume
|
||||
isExpectedFailure bool
|
||||
csiExpansionEnabled bool
|
||||
oldVolume *core.PersistentVolume
|
||||
newVolume *core.PersistentVolume
|
||||
}{
|
||||
"condition-no-update": {
|
||||
isExpectedFailure: false,
|
||||
@@ -475,6 +496,21 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
||||
oldVolume: validVolume,
|
||||
newVolume: invalidPvSourceUpdateDeep,
|
||||
},
|
||||
"csi-expansion-enabled-with-pv-secret": {
|
||||
csiExpansionEnabled: true,
|
||||
isExpectedFailure: false,
|
||||
oldVolume: validCSIVolume,
|
||||
newVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef),
|
||||
},
|
||||
"csi-expansion-enabled-with-old-pv-secret": {
|
||||
csiExpansionEnabled: true,
|
||||
isExpectedFailure: true,
|
||||
oldVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef),
|
||||
newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{
|
||||
Name: "foo-secret",
|
||||
Namespace: "default",
|
||||
}),
|
||||
},
|
||||
}
|
||||
for name, scenario := range scenarios {
|
||||
errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume)
|
||||
@@ -487,6 +523,14 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference) *core.PersistentVolume {
|
||||
pvCopy := pv.DeepCopy()
|
||||
if secret != nil {
|
||||
pvCopy.Spec.CSI.ControllerExpandSecretRef = secret
|
||||
}
|
||||
return pvCopy
|
||||
}
|
||||
|
||||
func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
|
||||
return core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
@@ -1834,6 +1878,22 @@ func TestValidateCSIVolumeSource(t *testing.T) {
|
||||
errtype: field.ErrorTypeInvalid,
|
||||
errfield: "driver",
|
||||
},
|
||||
{
|
||||
name: "controllerExpandSecretRef: invalid name missing",
|
||||
csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}},
|
||||
errtype: field.ErrorTypeRequired,
|
||||
errfield: "controllerExpandSecretRef.name",
|
||||
},
|
||||
{
|
||||
name: "controllerExpandSecretRef: invalid namespace missing",
|
||||
csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}},
|
||||
errtype: field.ErrorTypeRequired,
|
||||
errfield: "controllerExpandSecretRef.namespace",
|
||||
},
|
||||
{
|
||||
name: "valid controllerExpandSecretRef",
|
||||
csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
|
||||
},
|
||||
}
|
||||
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, true)()
|
||||
|
5
pkg/apis/core/zz_generated.deepcopy.go
generated
5
pkg/apis/core/zz_generated.deepcopy.go
generated
@@ -237,6 +237,11 @@ func (in *CSIPersistentVolumeSource) DeepCopyInto(out *CSIPersistentVolumeSource
|
||||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ControllerExpandSecretRef != nil {
|
||||
in, out := &in.ControllerExpandSecretRef, &out.ControllerExpandSecretRef
|
||||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user