Skip to content

Commit f4487a0

Browse files
authored
Merge pull request kubernetes#70490 from liggitt/tolerate-existing-subpath
tolerate existing subpath on pod update when feature is disabled
2 parents b97092c + c4a0254 commit f4487a0

File tree

14 files changed

+226
-49
lines changed

14 files changed

+226
-49
lines changed

pkg/api/pod/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ go_test(
3838
deps = [
3939
"//pkg/apis/core:go_default_library",
4040
"//pkg/features:go_default_library",
41+
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
4142
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
4243
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
4344
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",

pkg/api/pod/util.go

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,16 @@ func UpdatePodCondition(status *api.PodStatus, condition *api.PodCondition) bool
232232
return !isEqual
233233
}
234234

235-
// DropDisabledAlphaFields removes disabled fields from the pod spec.
235+
// DropDisabledFields removes disabled fields from the pod spec.
236236
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pod spec.
237-
func DropDisabledAlphaFields(podSpec *api.PodSpec) {
237+
func DropDisabledFields(podSpec, oldPodSpec *api.PodSpec) {
238238
if !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) {
239239
podSpec.Priority = nil
240240
podSpec.PriorityClassName = ""
241+
if oldPodSpec != nil {
242+
oldPodSpec.Priority = nil
243+
oldPodSpec.PriorityClassName = ""
244+
}
241245
}
242246

243247
if !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
@@ -246,22 +250,46 @@ func DropDisabledAlphaFields(podSpec *api.PodSpec) {
246250
podSpec.Volumes[i].EmptyDir.SizeLimit = nil
247251
}
248252
}
253+
if oldPodSpec != nil {
254+
for i := range oldPodSpec.Volumes {
255+
if oldPodSpec.Volumes[i].EmptyDir != nil {
256+
oldPodSpec.Volumes[i].EmptyDir.SizeLimit = nil
257+
}
258+
}
259+
}
260+
}
261+
262+
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpath) && !subpathInUse(oldPodSpec) {
263+
// drop subpath from the pod if the feature is disabled and the old spec did not specify subpaths
264+
for i := range podSpec.Containers {
265+
for j := range podSpec.Containers[i].VolumeMounts {
266+
podSpec.Containers[i].VolumeMounts[j].SubPath = ""
267+
}
268+
}
269+
for i := range podSpec.InitContainers {
270+
for j := range podSpec.InitContainers[i].VolumeMounts {
271+
podSpec.InitContainers[i].VolumeMounts[j].SubPath = ""
272+
}
273+
}
249274
}
250275

251-
DropDisabledVolumeDevicesAlphaFields(podSpec)
276+
dropDisabledVolumeDevicesAlphaFields(podSpec, oldPodSpec)
252277

253-
DropDisabledRunAsGroupField(podSpec)
278+
dropDisabledRunAsGroupField(podSpec, oldPodSpec)
254279

255-
if !utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) && podSpec.RuntimeClassName != nil {
280+
if !utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) {
256281
podSpec.RuntimeClassName = nil
282+
if oldPodSpec != nil {
283+
oldPodSpec.RuntimeClassName = nil
284+
}
257285
}
258286

259-
DropDisabledProcMountField(podSpec)
287+
dropDisabledProcMountField(podSpec, oldPodSpec)
260288
}
261289

262-
// DropDisabledRunAsGroupField removes disabled fields from PodSpec related
290+
// dropDisabledRunAsGroupField removes disabled fields from PodSpec related
263291
// to RunAsGroup
264-
func DropDisabledRunAsGroupField(podSpec *api.PodSpec) {
292+
func dropDisabledRunAsGroupField(podSpec, oldPodSpec *api.PodSpec) {
265293
if !utilfeature.DefaultFeatureGate.Enabled(features.RunAsGroup) {
266294
if podSpec.SecurityContext != nil {
267295
podSpec.SecurityContext.RunAsGroup = nil
@@ -276,12 +304,28 @@ func DropDisabledRunAsGroupField(podSpec *api.PodSpec) {
276304
podSpec.InitContainers[i].SecurityContext.RunAsGroup = nil
277305
}
278306
}
307+
308+
if oldPodSpec != nil {
309+
if oldPodSpec.SecurityContext != nil {
310+
oldPodSpec.SecurityContext.RunAsGroup = nil
311+
}
312+
for i := range oldPodSpec.Containers {
313+
if oldPodSpec.Containers[i].SecurityContext != nil {
314+
oldPodSpec.Containers[i].SecurityContext.RunAsGroup = nil
315+
}
316+
}
317+
for i := range oldPodSpec.InitContainers {
318+
if oldPodSpec.InitContainers[i].SecurityContext != nil {
319+
oldPodSpec.InitContainers[i].SecurityContext.RunAsGroup = nil
320+
}
321+
}
322+
}
279323
}
280324
}
281325

282-
// DropDisabledProcMountField removes disabled fields from PodSpec related
326+
// dropDisabledProcMountField removes disabled fields from PodSpec related
283327
// to ProcMount
284-
func DropDisabledProcMountField(podSpec *api.PodSpec) {
328+
func dropDisabledProcMountField(podSpec, oldPodSpec *api.PodSpec) {
285329
if !utilfeature.DefaultFeatureGate.Enabled(features.ProcMountType) {
286330
defProcMount := api.DefaultProcMount
287331
for i := range podSpec.Containers {
@@ -294,18 +338,62 @@ func DropDisabledProcMountField(podSpec *api.PodSpec) {
294338
podSpec.InitContainers[i].SecurityContext.ProcMount = &defProcMount
295339
}
296340
}
341+
342+
if oldPodSpec != nil {
343+
for i := range oldPodSpec.Containers {
344+
if oldPodSpec.Containers[i].SecurityContext != nil {
345+
oldPodSpec.Containers[i].SecurityContext.ProcMount = &defProcMount
346+
}
347+
}
348+
for i := range oldPodSpec.InitContainers {
349+
if oldPodSpec.InitContainers[i].SecurityContext != nil {
350+
oldPodSpec.InitContainers[i].SecurityContext.ProcMount = &defProcMount
351+
}
352+
}
353+
}
297354
}
298355
}
299356

300-
// DropDisabledVolumeDevicesAlphaFields removes disabled fields from []VolumeDevice.
357+
// dropDisabledVolumeDevicesAlphaFields removes disabled fields from []VolumeDevice.
301358
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a VolumeDevice
302-
func DropDisabledVolumeDevicesAlphaFields(podSpec *api.PodSpec) {
359+
func dropDisabledVolumeDevicesAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
303360
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
304361
for i := range podSpec.Containers {
305362
podSpec.Containers[i].VolumeDevices = nil
306363
}
307364
for i := range podSpec.InitContainers {
308365
podSpec.InitContainers[i].VolumeDevices = nil
309366
}
367+
368+
if oldPodSpec != nil {
369+
for i := range oldPodSpec.Containers {
370+
oldPodSpec.Containers[i].VolumeDevices = nil
371+
}
372+
for i := range oldPodSpec.InitContainers {
373+
oldPodSpec.InitContainers[i].VolumeDevices = nil
374+
}
375+
}
376+
}
377+
}
378+
379+
// subpathInUse returns true if the pod spec is non-nil and has a volume mount that makes use of the subPath feature
380+
func subpathInUse(podSpec *api.PodSpec) bool {
381+
if podSpec == nil {
382+
return false
383+
}
384+
for i := range podSpec.Containers {
385+
for j := range podSpec.Containers[i].VolumeMounts {
386+
if len(podSpec.Containers[i].VolumeMounts[j].SubPath) > 0 {
387+
return true
388+
}
389+
}
390+
}
391+
for i := range podSpec.InitContainers {
392+
for j := range podSpec.InitContainers[i].VolumeMounts {
393+
if len(podSpec.InitContainers[i].VolumeMounts[j].SubPath) > 0 {
394+
return true
395+
}
396+
}
310397
}
398+
return false
311399
}

pkg/api/pod/util_test.go

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ limitations under the License.
1717
package pod
1818

1919
import (
20+
"fmt"
2021
"reflect"
2122
"strings"
2223
"testing"
2324

25+
"k8s.io/apimachinery/pkg/util/diff"
2426
"k8s.io/apimachinery/pkg/util/sets"
2527
"k8s.io/apimachinery/pkg/util/validation/field"
2628
utilfeature "k8s.io/apiserver/pkg/util/feature"
@@ -307,7 +309,7 @@ func TestDropAlphaVolumeDevices(t *testing.T) {
307309
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
308310

309311
// now test dropping the fields - should not be dropped
310-
DropDisabledAlphaFields(&testPod.Spec)
312+
DropDisabledFields(&testPod.Spec, nil)
311313

312314
// check to make sure VolumeDevices is still present
313315
// if featureset is set to true
@@ -322,14 +324,108 @@ func TestDropAlphaVolumeDevices(t *testing.T) {
322324
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, false)()
323325

324326
// now test dropping the fields
325-
DropDisabledAlphaFields(&testPod.Spec)
327+
DropDisabledFields(&testPod.Spec, nil)
326328

327329
// check to make sure VolumeDevices is nil
328330
// if featureset is set to false
329331
if testPod.Spec.Containers[0].VolumeDevices != nil {
330-
t.Error("DropDisabledAlphaFields for Containers failed")
332+
t.Error("DropDisabledFields for Containers failed")
331333
}
332334
if testPod.Spec.InitContainers[0].VolumeDevices != nil {
333-
t.Error("DropDisabledAlphaFields for InitContainers failed")
335+
t.Error("DropDisabledFields for InitContainers failed")
336+
}
337+
}
338+
339+
func TestDropSubPath(t *testing.T) {
340+
podWithSubpaths := func() *api.Pod {
341+
return &api.Pod{
342+
Spec: api.PodSpec{
343+
RestartPolicy: api.RestartPolicyNever,
344+
Containers: []api.Container{{Name: "container1", Image: "testimage", VolumeMounts: []api.VolumeMount{{Name: "a", SubPath: "foo"}, {Name: "a", SubPath: "foo2"}, {Name: "a", SubPath: "foo3"}}}},
345+
InitContainers: []api.Container{{Name: "container1", Image: "testimage", VolumeMounts: []api.VolumeMount{{Name: "a", SubPath: "foo"}, {Name: "a", SubPath: "foo2"}}}},
346+
Volumes: []api.Volume{{Name: "a", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/dev/xvdc"}}}},
347+
},
348+
}
349+
}
350+
podWithoutSubpaths := func() *api.Pod {
351+
return &api.Pod{
352+
Spec: api.PodSpec{
353+
RestartPolicy: api.RestartPolicyNever,
354+
Containers: []api.Container{{Name: "container1", Image: "testimage", VolumeMounts: []api.VolumeMount{{Name: "a", SubPath: ""}, {Name: "a", SubPath: ""}, {Name: "a", SubPath: ""}}}},
355+
InitContainers: []api.Container{{Name: "container1", Image: "testimage", VolumeMounts: []api.VolumeMount{{Name: "a", SubPath: ""}, {Name: "a", SubPath: ""}}}},
356+
Volumes: []api.Volume{{Name: "a", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/dev/xvdc"}}}},
357+
},
358+
}
359+
}
360+
361+
podInfo := []struct {
362+
description string
363+
hasSubpaths bool
364+
pod func() *api.Pod
365+
}{
366+
{
367+
description: "has subpaths",
368+
hasSubpaths: true,
369+
pod: podWithSubpaths,
370+
},
371+
{
372+
description: "does not have subpaths",
373+
hasSubpaths: false,
374+
pod: podWithoutSubpaths,
375+
},
376+
{
377+
description: "is nil",
378+
hasSubpaths: false,
379+
pod: func() *api.Pod { return nil },
380+
},
381+
}
382+
383+
for _, enabled := range []bool{true, false} {
384+
for _, oldPodInfo := range podInfo {
385+
for _, newPodInfo := range podInfo {
386+
oldPodHasSubpaths, oldPod := oldPodInfo.hasSubpaths, oldPodInfo.pod()
387+
newPodHasSubpaths, newPod := newPodInfo.hasSubpaths, newPodInfo.pod()
388+
if newPod == nil {
389+
continue
390+
}
391+
392+
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
393+
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, enabled)()
394+
395+
var oldPodSpec *api.PodSpec
396+
if oldPod != nil {
397+
oldPodSpec = &oldPod.Spec
398+
}
399+
DropDisabledFields(&newPod.Spec, oldPodSpec)
400+
401+
// old pod should never be changed
402+
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
403+
t.Errorf("old pod changed: %v", diff.ObjectReflectDiff(oldPod, oldPodInfo.pod()))
404+
}
405+
406+
switch {
407+
case enabled || oldPodHasSubpaths:
408+
// new pod should not be changed if the feature is enabled, or if the old pod had subpaths
409+
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
410+
t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod()))
411+
}
412+
case newPodHasSubpaths:
413+
// new pod should be changed
414+
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
415+
t.Errorf("new pod was not changed")
416+
}
417+
// new pod should not have subpaths
418+
if !reflect.DeepEqual(newPod, podWithoutSubpaths()) {
419+
t.Errorf("new pod had subpaths: %v", diff.ObjectReflectDiff(newPod, podWithoutSubpaths()))
420+
}
421+
default:
422+
// new pod should not need to be changed
423+
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
424+
t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod()))
425+
}
426+
}
427+
})
428+
}
429+
}
334430
}
335431
}

pkg/apis/core/validation/validation.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,11 +2299,7 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]strin
22992299
}
23002300

23012301
if len(mnt.SubPath) > 0 {
2302-
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpath) {
2303-
allErrs = append(allErrs, field.Forbidden(fldPath.Child("subPath"), "subPath is disabled by feature-gate"))
2304-
} else {
2305-
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
2306-
}
2302+
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
23072303
}
23082304

23092305
if mnt.MountPropagation != nil {

pkg/apis/core/validation/validation_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4931,7 +4931,7 @@ func TestValidateDisabledSubpath(t *testing.T) {
49314931
SubPath: "baz",
49324932
},
49334933
},
4934-
true,
4934+
false, // validation should not fail, dropping the field is handled in PrepareForCreate/PrepareForUpdate
49354935
},
49364936
}
49374937

pkg/registry/apps/daemonset/strategy.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,15 @@ func (daemonSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Objec
7575
daemonSet.Spec.TemplateGeneration = 1
7676
}
7777

78-
pod.DropDisabledAlphaFields(&daemonSet.Spec.Template.Spec)
78+
pod.DropDisabledFields(&daemonSet.Spec.Template.Spec, nil)
7979
}
8080

8181
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
8282
func (daemonSetStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
8383
newDaemonSet := obj.(*apps.DaemonSet)
8484
oldDaemonSet := old.(*apps.DaemonSet)
8585

86-
pod.DropDisabledAlphaFields(&newDaemonSet.Spec.Template.Spec)
87-
pod.DropDisabledAlphaFields(&oldDaemonSet.Spec.Template.Spec)
86+
pod.DropDisabledFields(&newDaemonSet.Spec.Template.Spec, &oldDaemonSet.Spec.Template.Spec)
8887

8988
// update is not allowed to set status
9089
newDaemonSet.Status = oldDaemonSet.Status

pkg/registry/apps/deployment/strategy.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (deploymentStrategy) PrepareForCreate(ctx context.Context, obj runtime.Obje
7373
deployment.Status = apps.DeploymentStatus{}
7474
deployment.Generation = 1
7575

76-
pod.DropDisabledAlphaFields(&deployment.Spec.Template.Spec)
76+
pod.DropDisabledFields(&deployment.Spec.Template.Spec, nil)
7777
}
7878

7979
// Validate validates a new deployment.
@@ -97,8 +97,7 @@ func (deploymentStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime
9797
oldDeployment := old.(*apps.Deployment)
9898
newDeployment.Status = oldDeployment.Status
9999

100-
pod.DropDisabledAlphaFields(&newDeployment.Spec.Template.Spec)
101-
pod.DropDisabledAlphaFields(&oldDeployment.Spec.Template.Spec)
100+
pod.DropDisabledFields(&newDeployment.Spec.Template.Spec, &oldDeployment.Spec.Template.Spec)
102101

103102
// Spec updates bump the generation so that we can distinguish between
104103
// scaling events and template changes, annotation updates bump the generation

pkg/registry/apps/replicaset/strategy.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (rsStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
8080

8181
rs.Generation = 1
8282

83-
pod.DropDisabledAlphaFields(&rs.Spec.Template.Spec)
83+
pod.DropDisabledFields(&rs.Spec.Template.Spec, nil)
8484
}
8585

8686
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
@@ -90,8 +90,7 @@ func (rsStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object)
9090
// update is not allowed to set status
9191
newRS.Status = oldRS.Status
9292

93-
pod.DropDisabledAlphaFields(&newRS.Spec.Template.Spec)
94-
pod.DropDisabledAlphaFields(&oldRS.Spec.Template.Spec)
93+
pod.DropDisabledFields(&newRS.Spec.Template.Spec, &oldRS.Spec.Template.Spec)
9594

9695
// Any changes to the spec increment the generation number, any changes to the
9796
// status should reflect the generation number of the corresponding object. We push

0 commit comments

Comments
 (0)