diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 2e425c93cd5..084e12b3bf0 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -385,6 +385,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po AllowInvalidTopologySpreadConstraintLabelSelector: false, AllowNamespacedSysctlsForHostNetAndHostIPC: false, AllowNonLocalProjectedTokenPath: false, + AllowPodLifecycleSleepActionZeroValue: utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepActionAllowZero), } // 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 { // 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 } +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 func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec, oldPodSpec *api.PodSpec) { // the new status is always be non-nil diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 34e3002155f..3cb76ab2624 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -3068,52 +3068,52 @@ func validatePodResourceClaim(podMeta *metav1.ObjectMeta, claim core.PodResource 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{} if probe == nil { return allErrs } - allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) + allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...) if probe.SuccessThreshold != 1 { allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1")) } 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{} if probe == nil { return allErrs } - allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) + allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...) if probe.TerminationGracePeriodSeconds != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes")) } 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{} if probe == nil { return allErrs } - allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) + allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath, opts)...) if probe.SuccessThreshold != 1 { allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1")) } 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{} if probe == nil { 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.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{} // 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 // flag another error here when the real problem will already be flagged. - if gracePeriod != nil && sleep.Seconds <= 0 || sleep.Seconds > *gracePeriod { - invalidStr := fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", *gracePeriod) - allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr)) + if opts.AllowPodLifecycleSleepActionZeroValue { + if gracePeriod != nil && (sleep.Seconds < 0 || sleep.Seconds > *gracePeriod) { + 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 } @@ -3289,7 +3296,7 @@ func validateTCPSocketAction(tcp *core.TCPSocketAction, fldPath *field.Path) fie func validateGRPCAction(grpc *core.GRPCAction, fldPath *field.Path) field.ErrorList { 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 allErrors := field.ErrorList{} 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")) } else { 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 { @@ -3338,13 +3345,13 @@ func validateHandler(handler commonHandler, gracePeriod *int64, fldPath *field.P 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{} 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 { - 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 } @@ -3523,11 +3530,11 @@ func validateInitContainers(containers []core.Container, regularContainers []cor switch { case restartAlways: 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, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"))...) - allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"))...) + allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, idxPath.Child("livenessProbe"), opts)...) + allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"), opts)...) + allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"), opts)...) default: // 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 // field.Forbidden() for these paths. 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, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"))...) - allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"))...) + allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, path.Child("livenessProbe"), opts)...) + allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"), opts)...) + allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"), opts)...) // These fields are disallowed for regular containers if ctr.RestartPolicy != nil { @@ -4049,6 +4056,8 @@ type PodValidationOptions struct { AllowRelaxedEnvironmentVariableValidation bool // Allow the use of a relaxed DNS search 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, diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 0d24d54f0e1..d1649032573 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -7445,7 +7445,7 @@ func TestValidateProbe(t *testing.T) { } 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) } } @@ -7457,7 +7457,7 @@ func TestValidateProbe(t *testing.T) { errorCases = append(errorCases, probe) } 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) } } @@ -7563,7 +7563,7 @@ func Test_validateProbe(t *testing.T) { } for _, tt := range tests { 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) { t.Errorf("validateProbe() = %v, want %v", got, tt.want) 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"}}}}, } 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) } } @@ -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"}}}}, } 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) } } @@ -24162,43 +24162,109 @@ func TestValidateLoadBalancerStatus(t *testing.T) { func TestValidateSleepAction(t *testing.T) { fldPath := field.NewPath("root") 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 { - name string - action *core.SleepAction - gracePeriod int64 - expectErr field.ErrorList + name string + action *core.SleepAction + gracePeriod int64 + zeroValueEnabled bool + expectErr field.ErrorList }{ { name: "valid setting", action: &core.SleepAction{ Seconds: 5, }, - gracePeriod: 30, + gracePeriod: 30, + zeroValueEnabled: false, }, { name: "negative seconds", action: &core.SleepAction{ Seconds: -1, }, - gracePeriod: 30, - expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))}, + gracePeriod: 30, + zeroValueEnabled: false, + expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))}, }, { name: "longer than gracePeriod", action: &core.SleepAction{ Seconds: 5, }, - gracePeriod: 3, - expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))}, + gracePeriod: 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 { 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 { t.Errorf("Unexpected success") diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index e7296c575bd..69258b83528 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -477,6 +477,12 @@ const ( // Enables SleepAction in container lifecycle hooks 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 // kep: https://kep.k8s.io/3521 // diff --git a/pkg/features/versioned_kube_features.go b/pkg/features/versioned_kube_features.go index 96e1a426417..0ed4bef24fc 100644 --- a/pkg/features/versioned_kube_features.go +++ b/pkg/features/versioned_kube_features.go @@ -569,12 +569,13 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, }, - PodReadyToStartContainersCondition: { {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.Beta}, }, - + PodLifecycleSleepActionAllowZero: { + {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + }, PodSchedulingReadiness: { {Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.27"), Default: true, PreRelease: featuregate.Beta}, diff --git a/test/e2e/common/node/lifecycle_hook.go b/test/e2e/common/node/lifecycle_hook.go index 0612e216089..a6de625f36b 100644 --- a/test/e2e/common/node/lifecycle_hook.go +++ b/test/e2e/common/node/lifecycle_hook.go @@ -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() { f := framework.NewDefaultFramework("pod-lifecycle-sleep-action") f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 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.BeforeEach(func(ctx context.Context) { 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) + } + }) + + }) +}) diff --git a/test/e2e/feature/feature.go b/test/e2e/feature/feature.go index c5521fae8ca..557d0930cee 100644 --- a/test/e2e/feature/feature.go +++ b/test/e2e/feature/feature.go @@ -267,6 +267,10 @@ var ( // TODO: document the feature (owning SIG, when to use this feature for a test) 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) PodPriority = framework.WithFeature(framework.ValidFeatures.Add("PodPriority")) diff --git a/test/featuregates_linter/test_data/versioned_feature_list.yaml b/test/featuregates_linter/test_data/versioned_feature_list.yaml index b542b85b85c..ca7fdf1590c 100644 --- a/test/featuregates_linter/test_data/versioned_feature_list.yaml +++ b/test/featuregates_linter/test_data/versioned_feature_list.yaml @@ -890,6 +890,12 @@ lockToDefault: false preRelease: Beta version: "1.30" +- name: PodLifecycleSleepActionAllowZero + versionedSpecs: + - default: false + lockToDefault: false + preRelease: Alpha + version: "1.32" - name: PodReadyToStartContainersCondition versionedSpecs: - default: false