mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 03:03:59 +00:00
Merge pull request #127094 from sreeram-venkitesh/4818-allow-zero-for-prestop-hook
KEP-4818: Relaxed validation for allowing zero in PreStop hook sleep action
This commit is contained in:
commit
b337f048db
@ -385,6 +385,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
||||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||||
AllowNonLocalProjectedTokenPath: false,
|
AllowNonLocalProjectedTokenPath: false,
|
||||||
|
AllowPodLifecycleSleepActionZeroValue: utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepActionAllowZero),
|
||||||
}
|
}
|
||||||
|
|
||||||
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
|
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
|
||||||
@ -415,6 +416,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts.AllowPodLifecycleSleepActionZeroValue = opts.AllowPodLifecycleSleepActionZeroValue || podLifecycleSleepActionZeroValueInUse(podSpec)
|
||||||
}
|
}
|
||||||
if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
|
if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
|
||||||
// This is an update, so validate only if the existing object was valid.
|
// This is an update, so validate only if the existing object was valid.
|
||||||
@ -795,6 +798,28 @@ func podLifecycleSleepActionInUse(podSpec *api.PodSpec) bool {
|
|||||||
return inUse
|
return inUse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func podLifecycleSleepActionZeroValueInUse(podSpec *api.PodSpec) bool {
|
||||||
|
if podSpec == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var inUse bool
|
||||||
|
VisitContainers(podSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool {
|
||||||
|
if c.Lifecycle == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c.Lifecycle.PreStop != nil && c.Lifecycle.PreStop.Sleep != nil && c.Lifecycle.PreStop.Sleep.Seconds == 0 {
|
||||||
|
inUse = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.Lifecycle.PostStart != nil && c.Lifecycle.PostStart.Sleep != nil && c.Lifecycle.PreStop.Sleep.Seconds == 0 {
|
||||||
|
inUse = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return inUse
|
||||||
|
}
|
||||||
|
|
||||||
// dropDisabledPodStatusFields removes disabled fields from the pod status
|
// dropDisabledPodStatusFields removes disabled fields from the pod status
|
||||||
func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec, oldPodSpec *api.PodSpec) {
|
func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec, oldPodSpec *api.PodSpec) {
|
||||||
// the new status is always be non-nil
|
// the new status is always be non-nil
|
||||||
|
@ -3068,52 +3068,52 @@ func validatePodResourceClaim(podMeta *metav1.ObjectMeta, claim core.PodResource
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLivenessProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateLivenessProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if probe == nil {
|
if probe == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
|
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...)
|
||||||
if probe.SuccessThreshold != 1 {
|
if probe.SuccessThreshold != 1 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateReadinessProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateReadinessProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if probe == nil {
|
if probe == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
|
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...)
|
||||||
if probe.TerminationGracePeriodSeconds != nil {
|
if probe.TerminationGracePeriodSeconds != nil {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateStartupProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateStartupProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if probe == nil {
|
if probe == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
|
allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...)
|
||||||
if probe.SuccessThreshold != 1 {
|
if probe.SuccessThreshold != 1 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateProbe(probe *core.Probe, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if probe == nil {
|
if probe == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateHandler(handlerFromProbe(&probe.ProbeHandler), gracePeriod, fldPath)...)
|
allErrs = append(allErrs, validateHandler(handlerFromProbe(&probe.ProbeHandler), gracePeriod, fldPath, opts)...)
|
||||||
|
|
||||||
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...)
|
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...)
|
||||||
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...)
|
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...)
|
||||||
@ -3169,14 +3169,21 @@ func handlerFromLifecycle(lh *core.LifecycleHandler) commonHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSleepAction(sleep *core.SleepAction, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateSleepAction(sleep *core.SleepAction, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrors := field.ErrorList{}
|
allErrors := field.ErrorList{}
|
||||||
// We allow gracePeriod to be nil here because the pod in which this SleepAction
|
// We allow gracePeriod to be nil here because the pod in which this SleepAction
|
||||||
// is defined might have an invalid grace period defined, and we don't want to
|
// is defined might have an invalid grace period defined, and we don't want to
|
||||||
// flag another error here when the real problem will already be flagged.
|
// flag another error here when the real problem will already be flagged.
|
||||||
if gracePeriod != nil && sleep.Seconds <= 0 || sleep.Seconds > *gracePeriod {
|
if opts.AllowPodLifecycleSleepActionZeroValue {
|
||||||
invalidStr := fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", *gracePeriod)
|
if gracePeriod != nil && (sleep.Seconds < 0 || sleep.Seconds > *gracePeriod) {
|
||||||
allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr))
|
invalidStr := fmt.Sprintf("must be non-negative and less than terminationGracePeriodSeconds (%d)", *gracePeriod)
|
||||||
|
allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if gracePeriod != nil && (sleep.Seconds <= 0 || sleep.Seconds > *gracePeriod) {
|
||||||
|
invalidStr := fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d). Enable AllowPodLifecycleSleepActionZeroValue feature gate for zero sleep.", *gracePeriod)
|
||||||
|
allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return allErrors
|
return allErrors
|
||||||
}
|
}
|
||||||
@ -3289,7 +3296,7 @@ func validateTCPSocketAction(tcp *core.TCPSocketAction, fldPath *field.Path) fie
|
|||||||
func validateGRPCAction(grpc *core.GRPCAction, fldPath *field.Path) field.ErrorList {
|
func validateGRPCAction(grpc *core.GRPCAction, fldPath *field.Path) field.ErrorList {
|
||||||
return ValidatePortNumOrName(intstr.FromInt32(grpc.Port), fldPath.Child("port"))
|
return ValidatePortNumOrName(intstr.FromInt32(grpc.Port), fldPath.Child("port"))
|
||||||
}
|
}
|
||||||
func validateHandler(handler commonHandler, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateHandler(handler commonHandler, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
numHandlers := 0
|
numHandlers := 0
|
||||||
allErrors := field.ErrorList{}
|
allErrors := field.ErrorList{}
|
||||||
if handler.Exec != nil {
|
if handler.Exec != nil {
|
||||||
@ -3329,7 +3336,7 @@ func validateHandler(handler commonHandler, gracePeriod *int64, fldPath *field.P
|
|||||||
allErrors = append(allErrors, field.Forbidden(fldPath.Child("sleep"), "may not specify more than 1 handler type"))
|
allErrors = append(allErrors, field.Forbidden(fldPath.Child("sleep"), "may not specify more than 1 handler type"))
|
||||||
} else {
|
} else {
|
||||||
numHandlers++
|
numHandlers++
|
||||||
allErrors = append(allErrors, validateSleepAction(handler.Sleep, gracePeriod, fldPath.Child("sleep"))...)
|
allErrors = append(allErrors, validateSleepAction(handler.Sleep, gracePeriod, fldPath.Child("sleep"), opts)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if numHandlers == 0 {
|
if numHandlers == 0 {
|
||||||
@ -3338,13 +3345,13 @@ func validateHandler(handler commonHandler, gracePeriod *int64, fldPath *field.P
|
|||||||
return allErrors
|
return allErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLifecycle(lifecycle *core.Lifecycle, gracePeriod *int64, fldPath *field.Path) field.ErrorList {
|
func validateLifecycle(lifecycle *core.Lifecycle, gracePeriod *int64, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
if lifecycle.PostStart != nil {
|
if lifecycle.PostStart != nil {
|
||||||
allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PostStart), gracePeriod, fldPath.Child("postStart"))...)
|
allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PostStart), gracePeriod, fldPath.Child("postStart"), opts)...)
|
||||||
}
|
}
|
||||||
if lifecycle.PreStop != nil {
|
if lifecycle.PreStop != nil {
|
||||||
allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PreStop), gracePeriod, fldPath.Child("preStop"))...)
|
allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PreStop), gracePeriod, fldPath.Child("preStop"), opts)...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -3523,11 +3530,11 @@ func validateInitContainers(containers []core.Container, regularContainers []cor
|
|||||||
switch {
|
switch {
|
||||||
case restartAlways:
|
case restartAlways:
|
||||||
if ctr.Lifecycle != nil {
|
if ctr.Lifecycle != nil {
|
||||||
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, idxPath.Child("lifecycle"))...)
|
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, idxPath.Child("lifecycle"), opts)...)
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, idxPath.Child("livenessProbe"))...)
|
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, idxPath.Child("livenessProbe"), opts)...)
|
||||||
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"))...)
|
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"), opts)...)
|
||||||
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"))...)
|
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"), opts)...)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// These fields are disallowed for init containers.
|
// These fields are disallowed for init containers.
|
||||||
@ -3655,11 +3662,11 @@ func validateContainers(containers []core.Container, volumes map[string]core.Vol
|
|||||||
// Regular init container and ephemeral container validation will return
|
// Regular init container and ephemeral container validation will return
|
||||||
// field.Forbidden() for these paths.
|
// field.Forbidden() for these paths.
|
||||||
if ctr.Lifecycle != nil {
|
if ctr.Lifecycle != nil {
|
||||||
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, path.Child("lifecycle"))...)
|
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, path.Child("lifecycle"), opts)...)
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, path.Child("livenessProbe"))...)
|
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, path.Child("livenessProbe"), opts)...)
|
||||||
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"))...)
|
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"), opts)...)
|
||||||
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"))...)
|
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"), opts)...)
|
||||||
|
|
||||||
// These fields are disallowed for regular containers
|
// These fields are disallowed for regular containers
|
||||||
if ctr.RestartPolicy != nil {
|
if ctr.RestartPolicy != nil {
|
||||||
@ -4049,6 +4056,8 @@ type PodValidationOptions struct {
|
|||||||
AllowRelaxedEnvironmentVariableValidation bool
|
AllowRelaxedEnvironmentVariableValidation bool
|
||||||
// Allow the use of a relaxed DNS search
|
// Allow the use of a relaxed DNS search
|
||||||
AllowRelaxedDNSSearchValidation bool
|
AllowRelaxedDNSSearchValidation bool
|
||||||
|
// Allows zero value for Pod Lifecycle Sleep Action
|
||||||
|
AllowPodLifecycleSleepActionZeroValue bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
|
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
|
||||||
|
@ -7445,7 +7445,7 @@ func TestValidateProbe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range successCases {
|
for _, p := range successCases {
|
||||||
if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
|
if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7457,7 +7457,7 @@ func TestValidateProbe(t *testing.T) {
|
|||||||
errorCases = append(errorCases, probe)
|
errorCases = append(errorCases, probe)
|
||||||
}
|
}
|
||||||
for _, p := range errorCases {
|
for _, p := range errorCases {
|
||||||
if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
|
if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %v", p)
|
t.Errorf("expected failure for %v", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7563,7 +7563,7 @@ func Test_validateProbe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath)
|
got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath, PodValidationOptions{})
|
||||||
if len(got) != len(tt.want) {
|
if len(got) != len(tt.want) {
|
||||||
t.Errorf("validateProbe() = %v, want %v", got, tt.want)
|
t.Errorf("validateProbe() = %v, want %v", got, tt.want)
|
||||||
return
|
return
|
||||||
@ -7588,7 +7588,7 @@ func TestValidateHandler(t *testing.T) {
|
|||||||
{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}},
|
{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}},
|
||||||
}
|
}
|
||||||
for _, h := range successCases {
|
for _, h := range successCases {
|
||||||
if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
|
if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7603,7 +7603,7 @@ func TestValidateHandler(t *testing.T) {
|
|||||||
{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
|
{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
|
||||||
}
|
}
|
||||||
for _, h := range errorCases {
|
for _, h := range errorCases {
|
||||||
if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
|
if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %#v", h)
|
t.Errorf("expected failure for %#v", h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24162,43 +24162,109 @@ func TestValidateLoadBalancerStatus(t *testing.T) {
|
|||||||
func TestValidateSleepAction(t *testing.T) {
|
func TestValidateSleepAction(t *testing.T) {
|
||||||
fldPath := field.NewPath("root")
|
fldPath := field.NewPath("root")
|
||||||
getInvalidStr := func(gracePeriod int64) string {
|
getInvalidStr := func(gracePeriod int64) string {
|
||||||
return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
|
return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d). Enable AllowPodLifecycleSleepActionZeroValue feature gate for zero sleep.", gracePeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
getInvalidStrWithZeroValueEnabled := func(gracePeriod int64) string {
|
||||||
|
return fmt.Sprintf("must be non-negative and less than terminationGracePeriodSeconds (%d)", gracePeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
action *core.SleepAction
|
action *core.SleepAction
|
||||||
gracePeriod int64
|
gracePeriod int64
|
||||||
expectErr field.ErrorList
|
zeroValueEnabled bool
|
||||||
|
expectErr field.ErrorList
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid setting",
|
name: "valid setting",
|
||||||
action: &core.SleepAction{
|
action: &core.SleepAction{
|
||||||
Seconds: 5,
|
Seconds: 5,
|
||||||
},
|
},
|
||||||
gracePeriod: 30,
|
gracePeriod: 30,
|
||||||
|
zeroValueEnabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "negative seconds",
|
name: "negative seconds",
|
||||||
action: &core.SleepAction{
|
action: &core.SleepAction{
|
||||||
Seconds: -1,
|
Seconds: -1,
|
||||||
},
|
},
|
||||||
gracePeriod: 30,
|
gracePeriod: 30,
|
||||||
expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
|
zeroValueEnabled: false,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "longer than gracePeriod",
|
name: "longer than gracePeriod",
|
||||||
action: &core.SleepAction{
|
action: &core.SleepAction{
|
||||||
Seconds: 5,
|
Seconds: 5,
|
||||||
},
|
},
|
||||||
gracePeriod: 3,
|
gracePeriod: 3,
|
||||||
expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
|
zeroValueEnabled: false,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sleep duration of zero with zero value feature gate disabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: 0,
|
||||||
|
},
|
||||||
|
gracePeriod: 30,
|
||||||
|
zeroValueEnabled: false,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, 0, getInvalidStr(30))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sleep duration of zero with zero value feature gate enabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: 0,
|
||||||
|
},
|
||||||
|
gracePeriod: 30,
|
||||||
|
zeroValueEnabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid sleep duration (negative value) with zero value disabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: -1,
|
||||||
|
},
|
||||||
|
gracePeriod: 30,
|
||||||
|
zeroValueEnabled: false,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid sleep duration (negative value) with zero value enabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: -1,
|
||||||
|
},
|
||||||
|
gracePeriod: 30,
|
||||||
|
zeroValueEnabled: true,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStrWithZeroValueEnabled(30))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero grace period duration with zero value enabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: 0,
|
||||||
|
},
|
||||||
|
gracePeriod: 0,
|
||||||
|
zeroValueEnabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil grace period with zero value disabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: 5,
|
||||||
|
},
|
||||||
|
zeroValueEnabled: false,
|
||||||
|
expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(0))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil grace period with zero value enabled",
|
||||||
|
action: &core.SleepAction{
|
||||||
|
Seconds: 0,
|
||||||
|
},
|
||||||
|
zeroValueEnabled: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
errs := validateSleepAction(tc.action, &tc.gracePeriod, fldPath)
|
errs := validateSleepAction(tc.action, &tc.gracePeriod, fldPath, PodValidationOptions{AllowPodLifecycleSleepActionZeroValue: tc.zeroValueEnabled})
|
||||||
|
|
||||||
if len(tc.expectErr) > 0 && len(errs) == 0 {
|
if len(tc.expectErr) > 0 && len(errs) == 0 {
|
||||||
t.Errorf("Unexpected success")
|
t.Errorf("Unexpected success")
|
||||||
|
@ -477,6 +477,12 @@ const (
|
|||||||
// Enables SleepAction in container lifecycle hooks
|
// Enables SleepAction in container lifecycle hooks
|
||||||
PodLifecycleSleepAction featuregate.Feature = "PodLifecycleSleepAction"
|
PodLifecycleSleepAction featuregate.Feature = "PodLifecycleSleepAction"
|
||||||
|
|
||||||
|
// owner: @sreeram-venkitesh
|
||||||
|
// kep: http://kep.k8s.io/4818
|
||||||
|
//
|
||||||
|
// Allows zero value for sleep duration in SleepAction in container lifecycle hooks
|
||||||
|
PodLifecycleSleepActionAllowZero featuregate.Feature = "PodLifecycleSleepActionAllowZero"
|
||||||
|
|
||||||
// owner: @Huang-Wei
|
// owner: @Huang-Wei
|
||||||
// kep: https://kep.k8s.io/3521
|
// kep: https://kep.k8s.io/3521
|
||||||
//
|
//
|
||||||
|
@ -569,12 +569,13 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
|
|||||||
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
|
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
|
||||||
},
|
},
|
||||||
|
|
||||||
PodReadyToStartContainersCondition: {
|
PodReadyToStartContainersCondition: {
|
||||||
{Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha},
|
{Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
{Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.Beta},
|
{Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.Beta},
|
||||||
},
|
},
|
||||||
|
PodLifecycleSleepActionAllowZero: {
|
||||||
|
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
PodSchedulingReadiness: {
|
PodSchedulingReadiness: {
|
||||||
{Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha},
|
{Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
{Version: version.MustParse("1.27"), Default: true, PreRelease: featuregate.Beta},
|
{Version: version.MustParse("1.27"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
@ -547,15 +547,15 @@ func getSidecarPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validDuration(duration time.Duration, low, high int64) bool {
|
||||||
|
return duration >= time.Second*time.Duration(low) && duration <= time.Second*time.Duration(high)
|
||||||
|
}
|
||||||
|
|
||||||
var _ = SIGDescribe(feature.PodLifecycleSleepAction, func() {
|
var _ = SIGDescribe(feature.PodLifecycleSleepAction, func() {
|
||||||
f := framework.NewDefaultFramework("pod-lifecycle-sleep-action")
|
f := framework.NewDefaultFramework("pod-lifecycle-sleep-action")
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
|
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
|
||||||
var podClient *e2epod.PodClient
|
var podClient *e2epod.PodClient
|
||||||
|
|
||||||
validDuration := func(duration time.Duration, low, high int64) bool {
|
|
||||||
return duration >= time.Second*time.Duration(low) && duration <= time.Second*time.Duration(high)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.Context("when create a pod with lifecycle hook using sleep action", func() {
|
ginkgo.Context("when create a pod with lifecycle hook using sleep action", func() {
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
ginkgo.BeforeEach(func(ctx context.Context) {
|
||||||
podClient = e2epod.NewPodClient(f)
|
podClient = e2epod.NewPodClient(f)
|
||||||
@ -629,3 +629,36 @@ var _ = SIGDescribe(feature.PodLifecycleSleepAction, func() {
|
|||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var _ = SIGDescribe(feature.PodLifecycleSleepActionAllowZero, func() {
|
||||||
|
f := framework.NewDefaultFramework("pod-lifecycle-sleep-action-allow-zero")
|
||||||
|
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
|
||||||
|
var podClient *e2epod.PodClient
|
||||||
|
|
||||||
|
ginkgo.Context("when create a pod with lifecycle hook using sleep action with a duration of zero seconds", func() {
|
||||||
|
ginkgo.BeforeEach(func(ctx context.Context) {
|
||||||
|
podClient = e2epod.NewPodClient(f)
|
||||||
|
})
|
||||||
|
ginkgo.It("prestop hook using sleep action with zero duration", func(ctx context.Context) {
|
||||||
|
lifecycle := &v1.Lifecycle{
|
||||||
|
PreStop: &v1.LifecycleHandler{
|
||||||
|
Sleep: &v1.SleepAction{Seconds: 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
podWithHook := getPodWithHook("pod-with-prestop-sleep-hook-zero-duration", imageutils.GetPauseImageName(), lifecycle)
|
||||||
|
ginkgo.By("create the pod with lifecycle hook using sleep action with zero duration")
|
||||||
|
podClient.CreateSync(ctx, podWithHook)
|
||||||
|
ginkgo.By("delete the pod with lifecycle hook using sleep action with zero duration")
|
||||||
|
start := time.Now()
|
||||||
|
podClient.DeleteSync(ctx, podWithHook.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
|
||||||
|
cost := time.Since(start)
|
||||||
|
// cost should be
|
||||||
|
// longer than 0 seconds (pod shouldn't sleep and the handler should return immediately)
|
||||||
|
// shorter than gracePeriodSeconds (default 30 seconds here)
|
||||||
|
if !validDuration(cost, 0, 30) {
|
||||||
|
framework.Failf("unexpected delay duration before killing the pod, cost = %v", cost)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -267,6 +267,10 @@ var (
|
|||||||
// TODO: document the feature (owning SIG, when to use this feature for a test)
|
// TODO: document the feature (owning SIG, when to use this feature for a test)
|
||||||
PodLifecycleSleepAction = framework.WithFeature(framework.ValidFeatures.Add("PodLifecycleSleepAction"))
|
PodLifecycleSleepAction = framework.WithFeature(framework.ValidFeatures.Add("PodLifecycleSleepAction"))
|
||||||
|
|
||||||
|
// Owner: sig-node
|
||||||
|
// Marks a single test that tests Pod Lifecycle Sleep action with zero duration. Requires feature gate PodLifecycleSleepActionAllowZero to be enabled.
|
||||||
|
PodLifecycleSleepActionAllowZero = framework.WithFeature(framework.ValidFeatures.Add("PodLifecycleSleepActionAllowZero"))
|
||||||
|
|
||||||
// TODO: document the feature (owning SIG, when to use this feature for a test)
|
// TODO: document the feature (owning SIG, when to use this feature for a test)
|
||||||
PodPriority = framework.WithFeature(framework.ValidFeatures.Add("PodPriority"))
|
PodPriority = framework.WithFeature(framework.ValidFeatures.Add("PodPriority"))
|
||||||
|
|
||||||
|
@ -890,6 +890,12 @@
|
|||||||
lockToDefault: false
|
lockToDefault: false
|
||||||
preRelease: Beta
|
preRelease: Beta
|
||||||
version: "1.30"
|
version: "1.30"
|
||||||
|
- name: PodLifecycleSleepActionAllowZero
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.32"
|
||||||
- name: PodReadyToStartContainersCondition
|
- name: PodReadyToStartContainersCondition
|
||||||
versionedSpecs:
|
versionedSpecs:
|
||||||
- default: false
|
- default: false
|
||||||
|
Loading…
Reference in New Issue
Block a user