Add ImageVolumeSource API

Adding the required Kubernetes API so that the kubelet can start using
it. This patch also adds the corresponding alpha feature gate as
outlined in KEP 4639.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
This commit is contained in:
Sascha Grunert
2024-06-24 10:34:43 +02:00
parent ad72be434d
commit f7ca3131e0
86 changed files with 2395 additions and 1167 deletions

View File

@@ -385,6 +385,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
AllowInvalidTopologySpreadConstraintLabelSelector: false,
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
AllowNonLocalProjectedTokenPath: false,
AllowImageVolumeSource: utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume),
}
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
@@ -713,6 +714,7 @@ func dropDisabledFields(
}
dropPodLifecycleSleepAction(podSpec, oldPodSpec)
dropImageVolumes(podSpec, oldPodSpec)
}
func dropPodLifecycleSleepAction(podSpec, oldPodSpec *api.PodSpec) {
@@ -1260,3 +1262,56 @@ func MarkPodProposedForResize(oldPod, newPod *api.Pod) {
}
}
}
// KEP: https://kep.k8s.io/4639
func dropImageVolumes(podSpec, oldPodSpec *api.PodSpec) {
if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) || imageVolumesInUse(oldPodSpec) {
return
}
imageVolumeNames := sets.New[string]()
var newVolumes []api.Volume
for _, v := range podSpec.Volumes {
if v.Image != nil {
imageVolumeNames.Insert(v.Name)
continue
}
newVolumes = append(newVolumes, v)
}
podSpec.Volumes = newVolumes
dropVolumeMounts := func(givenMounts []api.VolumeMount) (newVolumeMounts []api.VolumeMount) {
for _, m := range givenMounts {
if !imageVolumeNames.Has(m.Name) {
newVolumeMounts = append(newVolumeMounts, m)
}
}
return newVolumeMounts
}
for i, c := range podSpec.Containers {
podSpec.Containers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
}
for i, c := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
}
for i, c := range podSpec.EphemeralContainers {
podSpec.EphemeralContainers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
}
}
func imageVolumesInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
for _, v := range podSpec.Volumes {
if v.Image != nil {
return true
}
}
return false
}

View File

@@ -3574,3 +3574,156 @@ func TestDropSupplementalGroupsPolicy(t *testing.T) {
}
}
}
func TestDropImageVolumes(t *testing.T) {
const (
volumeNameImage = "volume"
volumeNameOther = "volume-other"
)
anotherVolume := api.Volume{Name: volumeNameOther, VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{}}}
podWithVolume := &api.Pod{
Spec: api.PodSpec{
Volumes: []api.Volume{
{Name: volumeNameImage, VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{}}},
anotherVolume,
},
Containers: []api.Container{{
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}, {Name: volumeNameOther}},
}},
InitContainers: []api.Container{{
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}},
}},
EphemeralContainers: []api.EphemeralContainer{
{EphemeralContainerCommon: api.EphemeralContainerCommon{
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}},
}},
},
},
}
podWithoutVolume := &api.Pod{
Spec: api.PodSpec{
Volumes: []api.Volume{anotherVolume},
Containers: []api.Container{{VolumeMounts: []api.VolumeMount{{Name: volumeNameOther}}}},
InitContainers: []api.Container{{}},
EphemeralContainers: []api.EphemeralContainer{{}},
},
}
noPod := &api.Pod{}
testcases := []struct {
description string
enabled bool
oldPod *api.Pod
newPod *api.Pod
wantPod *api.Pod
}{
{
description: "old with volume / new with volume / disabled",
oldPod: podWithVolume,
newPod: podWithVolume,
wantPod: podWithVolume,
},
{
description: "old without volume / new with volume / disabled",
oldPod: podWithoutVolume,
newPod: podWithVolume,
wantPod: podWithoutVolume,
},
{
description: "no old pod/ new with volume / disabled",
oldPod: noPod,
newPod: podWithVolume,
wantPod: podWithoutVolume,
},
{
description: "nil old pod/ new with volume / disabled",
oldPod: nil,
newPod: podWithVolume,
wantPod: podWithoutVolume,
},
{
description: "old with volume / new without volume / disabled",
oldPod: podWithVolume,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
{
description: "old without volume / new without volume / disabled",
oldPod: podWithoutVolume,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
{
description: "no old pod/ new without volume / disabled",
oldPod: noPod,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
{
description: "old with volume / new with volume / enabled",
enabled: true,
oldPod: podWithVolume,
newPod: podWithVolume,
wantPod: podWithVolume,
},
{
description: "old without volume / new with volume / enabled",
enabled: true,
oldPod: podWithoutVolume,
newPod: podWithVolume,
wantPod: podWithVolume,
},
{
description: "no old pod/ new with volume / enabled",
enabled: true,
oldPod: noPod,
newPod: podWithVolume,
wantPod: podWithVolume,
},
{
description: "old with volume / new without volume / enabled",
enabled: true,
oldPod: podWithVolume,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
{
description: "old without volume / new without volume / enabled",
enabled: true,
oldPod: podWithoutVolume,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
{
description: "no old pod/ new without volume / enabled",
enabled: true,
oldPod: noPod,
newPod: podWithoutVolume,
wantPod: podWithoutVolume,
},
}
for _, tc := range testcases {
t.Run(tc.description, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, tc.enabled)
oldPod := tc.oldPod.DeepCopy()
newPod := tc.newPod.DeepCopy()
wantPod := tc.wantPod
DropDisabledPodFields(newPod, oldPod)
// old pod should never be changed
if diff := cmp.Diff(oldPod, tc.oldPod); diff != "" {
t.Errorf("old pod changed: %s", diff)
}
if diff := cmp.Diff(wantPod, newPod); diff != "" {
t.Errorf("new pod changed (- want, + got): %s", diff)
}
})
}
}