mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
generic ephemeral volumes: fix and test apiserver feature gate
The implementation should have preserved an existing ephemeral volume source during an update even when the feature gate is currently disabled, but due to a cut-and-paste error it was checking for CSI volumes instead. The new test detected that. It's based on https://github.com/kubernetes/kubernetes/pull/97058/files#diff-7826f7adbc1996a05ab52e3f5f02429e94b68ce6bce0dc534d1be636154fded3
This commit is contained in:
parent
5b0d0451ff
commit
d64165c803
@ -565,7 +565,7 @@ func dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
|
|||||||
// dropDisabledEphemeralVolumeSourceAlphaFields removes disabled alpha fields from []EphemeralVolumeSource.
|
// dropDisabledEphemeralVolumeSourceAlphaFields removes disabled alpha fields from []EphemeralVolumeSource.
|
||||||
// This should be called from PrepareForCreate/PrepareForUpdate for all pod specs resources containing a EphemeralVolumeSource
|
// This should be called from PrepareForCreate/PrepareForUpdate for all pod specs resources containing a EphemeralVolumeSource
|
||||||
func dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
|
func dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) && !csiInUse(oldPodSpec) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) && !ephemeralInUse(oldPodSpec) {
|
||||||
for i := range podSpec.Volumes {
|
for i := range podSpec.Volumes {
|
||||||
podSpec.Volumes[i].Ephemeral = nil
|
podSpec.Volumes[i].Ephemeral = nil
|
||||||
}
|
}
|
||||||
@ -711,6 +711,19 @@ func csiInUse(podSpec *api.PodSpec) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ephemeralInUse returns true if any pod's spec include inline CSI volumes.
|
||||||
|
func ephemeralInUse(podSpec *api.PodSpec) bool {
|
||||||
|
if podSpec == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range podSpec.Volumes {
|
||||||
|
if podSpec.Volumes[i].Ephemeral != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// podPriorityInUse returns true if status is not nil and number of PodIPs is greater than one
|
// podPriorityInUse returns true if status is not nil and number of PodIPs is greater than one
|
||||||
func multiplePodIPsInUse(podStatus *api.PodStatus) bool {
|
func multiplePodIPsInUse(podStatus *api.PodStatus) bool {
|
||||||
if podStatus == nil {
|
if podStatus == nil {
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -1103,3 +1103,97 @@ func TestApplySeccompVersionSkew(t *testing.T) {
|
|||||||
test.validation(t, test.pod)
|
test.validation(t, test.pod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEphemeralVolumeEnablement checks the behavior of the API server
|
||||||
|
// when the GenericEphemeralVolume feature is turned on and then off:
|
||||||
|
// the Ephemeral struct must be preserved even during updates.
|
||||||
|
func TestEphemeralVolumeEnablement(t *testing.T) {
|
||||||
|
// Enable the Feature Gate during the first pod creation
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
|
||||||
|
|
||||||
|
pod := createPodWithGenericEphemeralVolume()
|
||||||
|
expectedPod := pod.DeepCopy()
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(context.Background(), pod)
|
||||||
|
require.Equal(t, expectedPod.Spec, pod.Spec, "pod spec")
|
||||||
|
|
||||||
|
errs := Strategy.Validate(context.Background(), pod)
|
||||||
|
require.Empty(t, errs, "errors from validation")
|
||||||
|
|
||||||
|
// Now let's disable the Feature Gate, update some other field from the Pod and expect the volume to remain present
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
|
||||||
|
updatePod := testUpdatePod(t, pod, "aaa")
|
||||||
|
|
||||||
|
// And let's enable the FG again, add another from and check if the volume is still present
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
|
||||||
|
testUpdatePod(t, updatePod, "bbb")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEphemeralVolumeDisabled checks the behavior of the API server
|
||||||
|
// when the GenericEphemeralVolume is off: the Ephemeral struct gets dropped,
|
||||||
|
// validation fails.
|
||||||
|
func TestEphemeralVolumeDisabled(t *testing.T) {
|
||||||
|
// Disable the Feature Gate during the first pod creation
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
|
||||||
|
|
||||||
|
pod := createPodWithGenericEphemeralVolume()
|
||||||
|
expectedPod := pod.DeepCopy()
|
||||||
|
expectedPod.Spec.Volumes[0].VolumeSource.Ephemeral = nil
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(context.Background(), pod)
|
||||||
|
require.Equal(t, expectedPod.Spec, pod.Spec, "pod spec")
|
||||||
|
|
||||||
|
errs := Strategy.Validate(context.Background(), pod)
|
||||||
|
require.NotEmpty(t, errs, "no errors from validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdatePod(t *testing.T, oldPod *api.Pod, labelValue string) *api.Pod {
|
||||||
|
updatedPod := oldPod.DeepCopy()
|
||||||
|
updatedPod.Labels = map[string]string{"XYZ": labelValue}
|
||||||
|
expectedPod := updatedPod.DeepCopy()
|
||||||
|
Strategy.PrepareForUpdate(context.Background(), updatedPod, oldPod)
|
||||||
|
require.Equal(t, expectedPod.Spec, updatedPod.Spec, "updated pod spec")
|
||||||
|
errs := Strategy.Validate(context.Background(), updatedPod)
|
||||||
|
require.Empty(t, errs, "errors from validation")
|
||||||
|
return updatedPod
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPodWithGenericEphemeralVolume() *api.Pod {
|
||||||
|
return &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "ns",
|
||||||
|
Name: "pod",
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
RestartPolicy: api.RestartPolicyAlways,
|
||||||
|
DNSPolicy: api.DNSClusterFirst,
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "foo",
|
||||||
|
Image: "example",
|
||||||
|
TerminationMessagePolicy: api.TerminationMessageReadFile,
|
||||||
|
ImagePullPolicy: api.PullAlways,
|
||||||
|
}},
|
||||||
|
Volumes: []api.Volume{
|
||||||
|
{
|
||||||
|
Name: "ephemeral",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Ephemeral: &api.EphemeralVolumeSource{
|
||||||
|
VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{
|
||||||
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{
|
||||||
|
api.ReadWriteOnce,
|
||||||
|
},
|
||||||
|
Resources: api.ResourceRequirements{
|
||||||
|
Requests: api.ResourceList{
|
||||||
|
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user