From 5321f8064a6ed986dc868cc2a3dd09306acadb9e Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 1 Mar 2018 13:35:17 -0600 Subject: [PATCH] UPSTREAM: 54530: api: validate container phase transitions Origin-commit: f8298390c12428c93e4bf4048b82340898e37f6f --- pkg/apis/core/validation/validation.go | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 3d7c1979930cd..ae09f9a996928 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -3319,6 +3319,31 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList { return allErrs } +// ValidateContainerStateTransition test to if any illegal container state transitions are being attempted +func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, restartPolicy core.RestartPolicy) field.ErrorList { + allErrs := field.ErrorList{} + // If we should always restart, containers are allowed to leave the terminated state + if restartPolicy == core.RestartPolicyAlways { + return allErrs + } + for i, oldStatus := range oldStatuses { + // Skip any container that is not terminated + if oldStatus.State.Terminated == nil { + continue + } + // Skip any container that failed but is allowed to restart + if oldStatus.State.Terminated.ExitCode != 0 && restartPolicy == core.RestartPolicyOnFailure { + continue + } + for _, newStatus := range newStatuses { + if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { + allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state")) + } + } + } + return allErrs +} + // ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields // that cannot be changed. func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList { @@ -3326,10 +3351,16 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList { allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...) + fldPath = field.NewPath("status") if newPod.Spec.NodeName != oldPod.Spec.NodeName { - allErrs = append(allErrs, field.Forbidden(field.NewPath("status", "nodeName"), "may not be changed directly")) + allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly")) } + // If pod should not restart, make sure the status update does not transition + // any terminated containers to a non-terminated state. + allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...) + allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), oldPod.Spec.RestartPolicy)...) + // For status update we ignore changes to pod spec. newPod.Spec = oldPod.Spec