mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-05-28 04:04:39 -04:00
Merge pull request #137458 from natasha41575/tighten-initctr-validation
[InPlacePodVerticalScaling] validate initContainer restart policy against container resize policy
This commit is contained in:
commit
5f94c5bb7d
4 changed files with 238 additions and 39 deletions
|
|
@ -430,8 +430,9 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||
AllowContainerRestartPolicyRules: utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules),
|
||||
AllowUserNamespacesWithVolumeDevices: false,
|
||||
// This also allows restart rules on sidecar containers.
|
||||
AllowRestartAllContainers: utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits),
|
||||
AllowImageVolumeWithDigest: utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest),
|
||||
AllowRestartAllContainers: utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits),
|
||||
AllowImageVolumeWithDigest: utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest),
|
||||
AllowExistingRestartContainerForNonSidecarInitContainer: hasRestartContainerForNonSidecarInitContainer(oldPodSpec),
|
||||
}
|
||||
|
||||
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
|
||||
|
|
@ -2006,3 +2007,21 @@ func dropImageVolumeWithDigest(podStatus *api.PodStatus) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hasRestartContainerForNonSidecarInitContainer returns true if any non-sidecar init container
|
||||
// has a RestartContainer resize policy.
|
||||
func hasRestartContainerForNonSidecarInitContainer(spec *api.PodSpec) bool {
|
||||
if spec == nil {
|
||||
return false
|
||||
}
|
||||
for _, c := range spec.InitContainers {
|
||||
if !IsRestartableInitContainer(&c) {
|
||||
for _, p := range c.ResizePolicy {
|
||||
if p.RestartPolicy == api.RestartContainer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7377,3 +7377,104 @@ func TestDropDisabledPodStatusFields_ResourceHealthStatusMessage(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasRestartContainerForNonSidecarInitContainer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
podSpec *api.PodSpec
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil pod spec",
|
||||
podSpec: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no init containers",
|
||||
podSpec: &api.PodSpec{InitContainers: []api.Container{}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "regular init container without resize policy",
|
||||
podSpec: &api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init-1"},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "sidecar (restartable) init container with RestartContainer policy",
|
||||
podSpec: &api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Name: "sidecar",
|
||||
RestartPolicy: ptr.To(api.ContainerRestartPolicyAlways),
|
||||
ResizePolicy: []api.ContainerResizePolicy{
|
||||
{ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false, // Should be false because it's a sidecar
|
||||
},
|
||||
{
|
||||
name: "non-sidecar init container with NotRequired policy",
|
||||
podSpec: &api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Name: "init-1",
|
||||
ResizePolicy: []api.ContainerResizePolicy{
|
||||
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-sidecar init container with RestartContainer policy",
|
||||
podSpec: &api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Name: "init-1",
|
||||
ResizePolicy: []api.ContainerResizePolicy{
|
||||
{ResourceName: api.ResourceMemory, RestartPolicy: api.RestartContainer},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "mix of sidecar and non-sidecar with RestartContainer policy",
|
||||
podSpec: &api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Name: "sidecar",
|
||||
RestartPolicy: ptr.To(api.ContainerRestartPolicyAlways),
|
||||
ResizePolicy: []api.ContainerResizePolicy{
|
||||
{ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "init-2",
|
||||
ResizePolicy: []api.ContainerResizePolicy{
|
||||
{ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := hasRestartContainerForNonSidecarInitContainer(tt.podSpec)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %v, got %v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3645,9 +3645,10 @@ func validateResizePolicy(policyList []core.ContainerResizePolicy, fldPath *fiel
|
|||
}
|
||||
|
||||
if *podRestartPolicy == core.RestartPolicyNever && p.RestartPolicy != core.NotRequired {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when `restartPolicy` is 'Never'"))
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when pod `restartPolicy` is 'Never'"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
|
||||
|
|
@ -3858,6 +3859,14 @@ func validateInitContainers(containers []core.Container, os *core.PodOS, regular
|
|||
if !opts.AllowSidecarResizePolicy && len(ctr.ResizePolicy) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers"))
|
||||
}
|
||||
|
||||
if !restartAlways && !opts.AllowExistingRestartContainerForNonSidecarInitContainer {
|
||||
for j, p := range ctr.ResizePolicy {
|
||||
if p.RestartPolicy == core.RestartContainer {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("resizePolicy").Index(j).Child("restartPolicy"), p.RestartPolicy, "must not be set to 'RestartContainer' for non-sidecar initContainers"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
|
@ -4477,6 +4486,8 @@ type PodValidationOptions struct {
|
|||
AllowEnvFilesValidation bool
|
||||
// Allows containers have restart policy and restart policy rules.
|
||||
AllowContainerRestartPolicyRules bool
|
||||
// Allow existing RestartContainer resize policy for non-sidecar init containers for backward compatibility
|
||||
AllowExistingRestartContainerForNonSidecarInitContainer bool
|
||||
// Allow user namespaces with volume devices, even though they will not function properly (should only be tolerated in updates of objects which already have this invalid configuration).
|
||||
AllowUserNamespacesWithVolumeDevices bool
|
||||
// Allow taint toleration comparison operators (Lt, Gt)
|
||||
|
|
|
|||
|
|
@ -8035,7 +8035,7 @@ func TestValidateResizePolicy(t *testing.T) {
|
|||
{ResourceName: "memory", RestartPolicy: "RestartContainer"},
|
||||
},
|
||||
ExpectError: true,
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")},
|
||||
PodRestartPolicy: "Never",
|
||||
},
|
||||
"InvalidMemoryPolicyWithPodRestartPolicy": {
|
||||
|
|
@ -8044,7 +8044,7 @@ func TestValidateResizePolicy(t *testing.T) {
|
|||
{ResourceName: "memory", RestartPolicy: "NotRequired"},
|
||||
},
|
||||
ExpectError: true,
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")},
|
||||
PodRestartPolicy: "Never",
|
||||
},
|
||||
"InvalidMemoryCPUPolicyWithPodRestartPolicy": {
|
||||
|
|
@ -8053,7 +8053,7 @@ func TestValidateResizePolicy(t *testing.T) {
|
|||
{ResourceName: "memory", RestartPolicy: "RestartContainer"},
|
||||
},
|
||||
ExpectError: true,
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
|
||||
Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")},
|
||||
PodRestartPolicy: "Never",
|
||||
},
|
||||
"ValidMemoryCPUPolicyWithPodRestartPolicy": {
|
||||
|
|
@ -8067,19 +8067,21 @@ func TestValidateResizePolicy(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for k, v := range testCases {
|
||||
errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
|
||||
if !v.ExpectError && len(errs) > 0 {
|
||||
t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
|
||||
}
|
||||
if v.ExpectError {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Testcase %s - expected error, got success", k)
|
||||
t.Run(k, func(t *testing.T) {
|
||||
errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
|
||||
if !v.ExpectError && len(errs) > 0 {
|
||||
t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
|
||||
}
|
||||
delta := cmp.Diff(errs, v.Errors)
|
||||
if delta != "" {
|
||||
t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
|
||||
if v.ExpectError {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Testcase %s - expected error, got success", k)
|
||||
}
|
||||
delta := cmp.Diff(errs, v.Errors)
|
||||
if delta != "" {
|
||||
t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9467,8 +9469,7 @@ func TestValidateInitContainers(t *testing.T) {
|
|||
Image: "nginx",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
successCase := []core.Container{{
|
||||
Name: "container-1-same-host-port-different-protocol",
|
||||
|
|
@ -9537,10 +9538,27 @@ func TestValidateInitContainers(t *testing.T) {
|
|||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: "cpu", RestartPolicy: "NotRequired"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var PodRestartPolicy core.RestartPolicy = "Never"
|
||||
if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowSidecarResizePolicy: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
|
||||
}, {
|
||||
Name: "restartcontainer-policy-allowed-if-already-exists",
|
||||
Image: "nginx",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer},
|
||||
},
|
||||
}, {
|
||||
Name: "restartcontainer-policy-allowed-if-sidecar",
|
||||
Image: "nginx",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer},
|
||||
},
|
||||
RestartPolicy: ptr.To(core.ContainerRestartPolicyAlways),
|
||||
}}
|
||||
|
||||
var PodRestartPolicy core.RestartPolicy = "Always"
|
||||
if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
|
|
@ -9915,22 +9933,41 @@ func TestValidateInitContainers(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
|
||||
},
|
||||
{
|
||||
"Not supported ResizePolicy: invalid",
|
||||
line(),
|
||||
[]core.Container{{
|
||||
Name: "init",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: "cpu", RestartPolicy: "NotRequired"},
|
||||
},
|
||||
}},
|
||||
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].resizePolicy", BadValue: []core.ContainerResizePolicy{{ResourceName: "cpu", RestartPolicy: "NotRequired"}}}},
|
||||
},
|
||||
}
|
||||
}, {
|
||||
"Not supported ResizePolicy: invalid",
|
||||
line(),
|
||||
[]core.Container{{
|
||||
Name: "init",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: "cpu", RestartPolicy: "NotRequired"},
|
||||
},
|
||||
}},
|
||||
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].resizePolicy", BadValue: []core.ContainerResizePolicy{{ResourceName: "cpu", RestartPolicy: "NotRequired"}}}},
|
||||
}, {
|
||||
"RestartContainer policy forbidden for non-sidecar init container",
|
||||
line(),
|
||||
[]core.Container{{
|
||||
Name: "standard-init-no-restart",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{
|
||||
{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer},
|
||||
},
|
||||
}},
|
||||
field.ErrorList{{
|
||||
Type: field.ErrorTypeInvalid,
|
||||
Field: "initContainers[0].resizePolicy",
|
||||
BadValue: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}},
|
||||
}, {
|
||||
Type: field.ErrorTypeInvalid,
|
||||
Field: "initContainers[0].resizePolicy[0].restartPolicy",
|
||||
BadValue: core.RestartContainer,
|
||||
}},
|
||||
}}
|
||||
|
||||
for _, tc := range errorCases {
|
||||
t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
|
||||
|
|
@ -13718,6 +13755,37 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||
),
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set",
|
||||
}, {
|
||||
new: *podtest.MakePod("foo",
|
||||
podtest.SetInitContainers(core.Container{
|
||||
Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}},
|
||||
}),
|
||||
),
|
||||
old: *podtest.MakePod("foo",
|
||||
podtest.SetInitContainers(core.Container{
|
||||
Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
}),
|
||||
),
|
||||
err: "spec.initContainers[0].resizePolicy[0].restartPolicy: Invalid value: \"RestartContainer\": must not be set to 'RestartContainer' for non-sidecar initContainers",
|
||||
test: "adding RestartContainer resize policy to non-sidecar init container should fail",
|
||||
opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: false},
|
||||
}, {
|
||||
new: *podtest.MakePod("foo",
|
||||
podtest.SetInitContainers(core.Container{
|
||||
Name: "ictr", Image: "image:v2", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}},
|
||||
}),
|
||||
),
|
||||
old: *podtest.MakePod("foo",
|
||||
podtest.SetInitContainers(core.Container{
|
||||
Name: "ictr", Image: "image:v1", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}},
|
||||
}),
|
||||
),
|
||||
err: "",
|
||||
test: "existing RestartContainer resize policy on non-sidecar init container should be allowed on update",
|
||||
opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true},
|
||||
}, {
|
||||
new: *podtest.MakePod("foo",
|
||||
podtest.SetOS(core.Windows),
|
||||
|
|
|
|||
Loading…
Reference in a new issue