From 3a96afdfefdf329c637623ae31a61d20dbdb0393 Mon Sep 17 00:00:00 2001 From: AxeZhan Date: Fri, 18 Aug 2023 14:09:41 +0800 Subject: [PATCH] implementation --- pkg/api/pod/util.go | 58 ++++++++++++++++++++++++++ pkg/kubelet/lifecycle/handlers.go | 23 ++++++++++ pkg/kubelet/lifecycle/handlers_test.go | 56 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 6b7394fe8eb..64bcef12c74 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -558,6 +558,64 @@ func dropDisabledFields( } // For other types of containers, validateContainers will handle them. } + + if !utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepAction) && !podLifecycleSleepActionInUse(oldPodSpec) { + for i := range podSpec.Containers { + if podSpec.Containers[i].Lifecycle == nil { + continue + } + if podSpec.Containers[i].Lifecycle.PreStop != nil { + podSpec.Containers[i].Lifecycle.PreStop.Sleep = nil + } + if podSpec.Containers[i].Lifecycle.PostStart != nil { + podSpec.Containers[i].Lifecycle.PostStart.Sleep = nil + } + } + for i := range podSpec.InitContainers { + if podSpec.InitContainers[i].Lifecycle == nil { + continue + } + if podSpec.InitContainers[i].Lifecycle.PreStop != nil { + podSpec.InitContainers[i].Lifecycle.PreStop.Sleep = nil + } + if podSpec.InitContainers[i].Lifecycle.PostStart != nil { + podSpec.InitContainers[i].Lifecycle.PostStart.Sleep = nil + } + } + for i := range podSpec.EphemeralContainers { + if podSpec.EphemeralContainers[i].Lifecycle == nil { + continue + } + if podSpec.EphemeralContainers[i].Lifecycle.PreStop != nil { + podSpec.EphemeralContainers[i].Lifecycle.PreStop.Sleep = nil + } + if podSpec.EphemeralContainers[i].Lifecycle.PostStart != nil { + podSpec.EphemeralContainers[i].Lifecycle.PostStart.Sleep = nil + } + } + } +} + +func podLifecycleSleepActionInUse(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 { + inUse = true + return false + } + if c.Lifecycle.PostStart != nil && c.Lifecycle.PostStart.Sleep != nil { + inUse = true + return false + } + return true + }) + return inUse } // dropDisabledPodStatusFields removes disabled fields from the pod status diff --git a/pkg/kubelet/lifecycle/handlers.go b/pkg/kubelet/lifecycle/handlers.go index 910c7a42edc..fcd7656bf9c 100644 --- a/pkg/kubelet/lifecycle/handlers.go +++ b/pkg/kubelet/lifecycle/handlers.go @@ -26,6 +26,7 @@ import ( "net/url" "strconv" "strings" + "time" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -86,6 +87,14 @@ func (hr *handlerRunner) Run(ctx context.Context, containerID kubecontainer.Cont klog.V(1).ErrorS(err, "HTTP lifecycle hook for Container in Pod failed", "path", handler.HTTPGet.Path, "containerName", container.Name, "pod", klog.KObj(pod)) } return msg, err + case handler.Sleep != nil: + err := hr.runSleepHandler(ctx, handler.Sleep.Seconds) + var msg string + if err != nil { + msg = fmt.Sprintf("Sleep lifecycle hook (%d) for Container %q in Pod %q failed - error: %v", handler.Sleep.Seconds, container.Name, format.Pod(pod), err) + klog.V(1).ErrorS(err, "Sleep lifecycle hook for Container in Pod failed", "sleepSeconds", handler.Sleep.Seconds, "containerName", container.Name, "pod", klog.KObj(pod)) + } + return msg, err default: err := fmt.Errorf("invalid handler: %v", handler) msg := fmt.Sprintf("Cannot run handler: %v", err) @@ -117,6 +126,20 @@ func resolvePort(portReference intstr.IntOrString, container *v1.Container) (int return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container) } +func (hr *handlerRunner) runSleepHandler(ctx context.Context, seconds int64) error { + if !utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepAction) { + return nil + } + c := time.After(time.Duration(seconds) * time.Second) + select { + case <-ctx.Done(): + // unexpected termination + return fmt.Errorf("container terminated before sleep hook finished") + case <-c: + return nil + } +} + func (hr *handlerRunner) runHTTPHandler(ctx context.Context, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler, eventRecorder record.EventRecorder) error { host := handler.HTTPGet.Host podIP := host diff --git a/pkg/kubelet/lifecycle/handlers_test.go b/pkg/kubelet/lifecycle/handlers_test.go index a6d095add38..ba812165a46 100644 --- a/pkg/kubelet/lifecycle/handlers_test.go +++ b/pkg/kubelet/lifecycle/handlers_test.go @@ -859,3 +859,59 @@ func TestIsHTTPResponseError(t *testing.T) { t.Errorf("unexpected http response error: %v", err) } } + +func TestRunSleepHandler(t *testing.T) { + handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil, nil) + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + containerName := "containerFoo" + container := v1.Container{ + Name: containerName, + Lifecycle: &v1.Lifecycle{ + PreStop: &v1.LifecycleHandler{}, + }, + } + pod := v1.Pod{} + pod.ObjectMeta.Name = "podFoo" + pod.ObjectMeta.Namespace = "nsFoo" + pod.Spec.Containers = []v1.Container{container} + + tests := []struct { + name string + sleepSeconds int64 + terminationGracePeriodSeconds int64 + expectErr bool + expectedErr string + }{ + { + name: "valid seconds", + sleepSeconds: 5, + terminationGracePeriodSeconds: 30, + }, + { + name: "longer than TerminationGracePeriodSeconds", + sleepSeconds: 3, + terminationGracePeriodSeconds: 2, + expectErr: true, + expectedErr: "container terminated before sleep hook finished", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, true)() + + pod.Spec.Containers[0].Lifecycle.PreStop.Sleep = &v1.SleepAction{Seconds: tt.sleepSeconds} + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.terminationGracePeriodSeconds)*time.Second) + defer cancel() + + _, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PreStop) + + if !tt.expectErr && err != nil { + t.Errorf("unexpected success") + } + if tt.expectErr && err.Error() != tt.expectedErr { + t.Errorf("%s: expected error want %s, got %s", tt.name, tt.expectedErr, err.Error()) + } + }) + } +}