2021-08-12 17:13:11 -04:00
//go:build linux
2021-02-01 02:32:41 -05:00
/ *
Copyright 2021 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package e2enode
import (
"context"
"fmt"
2021-06-22 03:13:02 -04:00
"os"
2023-07-07 14:08:28 -04:00
"os/exec"
"regexp"
"strconv"
2021-02-01 02:32:41 -05:00
"time"
2021-11-25 01:24:32 -05:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2021-02-01 02:32:41 -05:00
"k8s.io/apimachinery/pkg/fields"
2022-06-06 21:12:59 -04:00
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
2022-03-08 22:54:25 -05:00
"k8s.io/kubectl/pkg/util/podutils"
2022-06-06 21:12:59 -04:00
2022-04-04 08:00:06 -04:00
admissionapi "k8s.io/pod-security-admission/api"
2021-02-01 02:32:41 -05:00
2022-03-29 02:12:12 -04:00
"github.com/onsi/ginkgo/v2"
2021-02-01 02:32:41 -05:00
"github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/apis/scheduling"
2024-12-11 16:11:51 -05:00
"k8s.io/kubernetes/test/e2e/feature"
2021-03-04 02:31:57 -05:00
"k8s.io/kubernetes/test/e2e/framework"
e2e: adapt to moved code
This is the result of automatically editing source files like this:
go install golang.org/x/tools/cmd/goimports@latest
find ./test/e2e* -name "*.go" | xargs env PATH=$GOPATH/bin:$PATH ./e2e-framework-sed.sh
with e2e-framework-sed.sh containing this:
sed -i \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecCommandInContainer(/e2epod.ExecCommandInContainer(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecCommandInContainerWithFullOutput(/e2epod.ExecCommandInContainerWithFullOutput(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInContainer(/e2epod.ExecShellInContainer(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInPod(/e2epod.ExecShellInPod(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInPodWithFullOutput(/e2epod.ExecShellInPodWithFullOutput(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecWithOptions(/e2epod.ExecWithOptions(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.MatchContainerOutput(/e2eoutput.MatchContainerOutput(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.PodClient(/e2epod.NewPodClient(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.PodClientNS(/e2epod.PodClientNS(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.TestContainerOutput(/e2eoutput.TestContainerOutput(\1, /" \
-e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.TestContainerOutputRegexp(/e2eoutput.TestContainerOutputRegexp(\1, /" \
-e "s/framework.AddOrUpdateLabelOnNode\b/e2enode.AddOrUpdateLabelOnNode/" \
-e "s/framework.AllNodes\b/e2edebug.AllNodes/" \
-e "s/framework.AllNodesReady\b/e2enode.AllNodesReady/" \
-e "s/framework.ContainerResourceGatherer\b/e2edebug.ContainerResourceGatherer/" \
-e "s/framework.ContainerResourceUsage\b/e2edebug.ContainerResourceUsage/" \
-e "s/framework.CreateEmptyFileOnPod\b/e2eoutput.CreateEmptyFileOnPod/" \
-e "s/framework.DefaultPodDeletionTimeout\b/e2epod.DefaultPodDeletionTimeout/" \
-e "s/framework.DumpAllNamespaceInfo\b/e2edebug.DumpAllNamespaceInfo/" \
-e "s/framework.DumpDebugInfo\b/e2eoutput.DumpDebugInfo/" \
-e "s/framework.DumpNodeDebugInfo\b/e2edebug.DumpNodeDebugInfo/" \
-e "s/framework.EtcdUpgrade\b/e2eproviders.EtcdUpgrade/" \
-e "s/framework.EventsLister\b/e2edebug.EventsLister/" \
-e "s/framework.ExecOptions\b/e2epod.ExecOptions/" \
-e "s/framework.ExpectNodeHasLabel\b/e2enode.ExpectNodeHasLabel/" \
-e "s/framework.ExpectNodeHasTaint\b/e2enode.ExpectNodeHasTaint/" \
-e "s/framework.GCEUpgradeScript\b/e2eproviders.GCEUpgradeScript/" \
-e "s/framework.ImagePrePullList\b/e2epod.ImagePrePullList/" \
-e "s/framework.KubectlBuilder\b/e2ekubectl.KubectlBuilder/" \
-e "s/framework.LocationParamGKE\b/e2eproviders.LocationParamGKE/" \
-e "s/framework.LogSizeDataTimeseries\b/e2edebug.LogSizeDataTimeseries/" \
-e "s/framework.LogSizeGatherer\b/e2edebug.LogSizeGatherer/" \
-e "s/framework.LogsSizeData\b/e2edebug.LogsSizeData/" \
-e "s/framework.LogsSizeDataSummary\b/e2edebug.LogsSizeDataSummary/" \
-e "s/framework.LogsSizeVerifier\b/e2edebug.LogsSizeVerifier/" \
-e "s/framework.LookForStringInLog\b/e2eoutput.LookForStringInLog/" \
-e "s/framework.LookForStringInPodExec\b/e2eoutput.LookForStringInPodExec/" \
-e "s/framework.LookForStringInPodExecToContainer\b/e2eoutput.LookForStringInPodExecToContainer/" \
-e "s/framework.MasterAndDNSNodes\b/e2edebug.MasterAndDNSNodes/" \
-e "s/framework.MasterNodes\b/e2edebug.MasterNodes/" \
-e "s/framework.MasterUpgradeGKE\b/e2eproviders.MasterUpgradeGKE/" \
-e "s/framework.NewKubectlCommand\b/e2ekubectl.NewKubectlCommand/" \
-e "s/framework.NewLogsVerifier\b/e2edebug.NewLogsVerifier/" \
-e "s/framework.NewNodeKiller\b/e2enode.NewNodeKiller/" \
-e "s/framework.NewResourceUsageGatherer\b/e2edebug.NewResourceUsageGatherer/" \
-e "s/framework.NodeHasTaint\b/e2enode.NodeHasTaint/" \
-e "s/framework.NodeKiller\b/e2enode.NodeKiller/" \
-e "s/framework.NodesSet\b/e2edebug.NodesSet/" \
-e "s/framework.PodClient\b/e2epod.PodClient/" \
-e "s/framework.RemoveLabelOffNode\b/e2enode.RemoveLabelOffNode/" \
-e "s/framework.ResourceConstraint\b/e2edebug.ResourceConstraint/" \
-e "s/framework.ResourceGathererOptions\b/e2edebug.ResourceGathererOptions/" \
-e "s/framework.ResourceUsagePerContainer\b/e2edebug.ResourceUsagePerContainer/" \
-e "s/framework.ResourceUsageSummary\b/e2edebug.ResourceUsageSummary/" \
-e "s/framework.RunHostCmd\b/e2eoutput.RunHostCmd/" \
-e "s/framework.RunHostCmdOrDie\b/e2eoutput.RunHostCmdOrDie/" \
-e "s/framework.RunHostCmdWithFullOutput\b/e2eoutput.RunHostCmdWithFullOutput/" \
-e "s/framework.RunHostCmdWithRetries\b/e2eoutput.RunHostCmdWithRetries/" \
-e "s/framework.RunKubectl\b/e2ekubectl.RunKubectl/" \
-e "s/framework.RunKubectlInput\b/e2ekubectl.RunKubectlInput/" \
-e "s/framework.RunKubectlOrDie\b/e2ekubectl.RunKubectlOrDie/" \
-e "s/framework.RunKubectlOrDieInput\b/e2ekubectl.RunKubectlOrDieInput/" \
-e "s/framework.RunKubectlWithFullOutput\b/e2ekubectl.RunKubectlWithFullOutput/" \
-e "s/framework.RunKubemciCmd\b/e2ekubectl.RunKubemciCmd/" \
-e "s/framework.RunKubemciWithKubeconfig\b/e2ekubectl.RunKubemciWithKubeconfig/" \
-e "s/framework.SingleContainerSummary\b/e2edebug.SingleContainerSummary/" \
-e "s/framework.SingleLogSummary\b/e2edebug.SingleLogSummary/" \
-e "s/framework.TimestampedSize\b/e2edebug.TimestampedSize/" \
-e "s/framework.WaitForAllNodesSchedulable\b/e2enode.WaitForAllNodesSchedulable/" \
-e "s/framework.WaitForSSHTunnels\b/e2enode.WaitForSSHTunnels/" \
-e "s/framework.WorkItem\b/e2edebug.WorkItem/" \
"$@"
for i in "$@"; do
# Import all sub packages and let goimports figure out which of those
# are redundant (= already imported) or not needed.
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2edebug "k8s.io/kubernetes/test/e2e/framework/debug"' "$i"
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"' "$i"
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2enode "k8s.io/kubernetes/test/e2e/framework/node"' "$i"
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"' "$i"
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2epod "k8s.io/kubernetes/test/e2e/framework/pod"' "$i"
sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2eproviders "k8s.io/kubernetes/test/e2e/framework/providers"' "$i"
goimports -w "$i"
done
2022-09-08 10:04:17 -04:00
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
2023-07-07 14:08:28 -04:00
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
2021-03-04 02:31:57 -05:00
2022-01-27 09:17:07 -05:00
"github.com/godbus/dbus/v5"
2021-03-04 02:31:57 -05:00
v1 "k8s.io/api/core/v1"
2021-11-12 03:49:33 -05:00
schedulingv1 "k8s.io/api/scheduling/v1"
2021-03-04 02:31:57 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2023-02-10 19:54:31 -05:00
"k8s.io/apimachinery/pkg/util/uuid"
2022-06-06 21:12:59 -04:00
"k8s.io/apimachinery/pkg/util/wait"
2021-11-12 03:49:33 -05:00
"k8s.io/kubernetes/pkg/features"
2021-02-01 02:32:41 -05:00
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
2021-03-04 02:31:57 -05:00
testutils "k8s.io/kubernetes/test/utils"
2021-02-01 02:32:41 -05:00
)
2024-12-11 16:11:51 -05:00
var _ = SIGDescribe ( "GracefulNodeShutdown" , framework . WithSerial ( ) , feature . GracefulNodeShutdown , feature . GracefulNodeShutdownBasedOnPodPriority , func ( ) {
2021-02-01 02:32:41 -05:00
f := framework . NewDefaultFramework ( "graceful-node-shutdown" )
2023-05-10 09:38:10 -04:00
f . NamespacePodSecurityLevel = admissionapi . LevelPrivileged
2022-10-10 07:58:40 -04:00
2023-07-07 14:08:28 -04:00
ginkgo . BeforeEach ( func ( ) {
if _ , err := exec . LookPath ( "systemd-run" ) ; err == nil {
if version , verr := exec . Command ( "systemd-run" , "--version" ) . Output ( ) ; verr == nil {
// sample output from $ systemd-run --version
// systemd 245 (245.4-4ubuntu3.13)
re := regexp . MustCompile ( ` systemd (\d+) ` )
if match := re . FindSubmatch ( version ) ; len ( match ) > 1 {
systemdVersion , err := strconv . Atoi ( string ( match [ 1 ] ) )
if err != nil {
framework . Logf ( "failed to parse systemd version with error %v, 'systemd-run --version' output was [%s]" , err , version )
} else {
// See comments in issue 107043, this is a known problem for a long time that this feature does not work on older systemd
// https://github.com/kubernetes/kubernetes/issues/107043#issuecomment-997546598
if systemdVersion < 245 {
e2eskipper . Skipf ( "skipping GracefulNodeShutdown tests as we are running on an old version of systemd : %d" , systemdVersion )
}
}
}
}
}
} )
2024-06-28 10:36:45 -04:00
f . Context ( "graceful node shutdown; baseline scenario to verify DisruptionTarget is added" , func ( ) {
2022-10-10 07:58:40 -04:00
const (
pollInterval = 1 * time . Second
podStatusUpdateTimeout = 30 * time . Second
nodeStatusUpdateTimeout = 30 * time . Second
nodeShutdownGracePeriod = 30 * time . Second
)
2022-12-12 04:11:10 -05:00
tempSetCurrentKubeletConfig ( f , func ( ctx context . Context , initialConfig * kubeletconfig . KubeletConfiguration ) {
2025-09-11 02:14:01 -04:00
if initialConfig . FeatureGates == nil {
initialConfig . FeatureGates = map [ string ] bool { }
2022-10-10 07:58:40 -04:00
}
2025-09-11 02:14:01 -04:00
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdown ) ] = true
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdownBasedOnPodPriority ) ] = false
2022-10-10 07:58:40 -04:00
initialConfig . ShutdownGracePeriod = metav1 . Duration { Duration : nodeShutdownGracePeriod }
} )
2022-12-12 04:11:10 -05:00
ginkgo . BeforeEach ( func ( ctx context . Context ) {
2022-10-10 07:58:40 -04:00
ginkgo . By ( "Wait for the node to be ready" )
2022-12-12 04:11:10 -05:00
waitForNodeReady ( ctx )
2022-10-10 07:58:40 -04:00
} )
ginkgo . AfterEach ( func ( ) {
ginkgo . By ( "Emitting Shutdown false signal; cancelling the shutdown" )
err := emitSignalPrepareForShutdown ( false )
framework . ExpectNoError ( err )
} )
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should add the DisruptionTarget pod failure condition to the evicted pods" , func ( ctx context . Context ) {
2022-12-12 04:11:10 -05:00
nodeName := getNodeName ( ctx , f )
2022-10-10 07:58:40 -04:00
nodeSelector := fields . Set {
"spec.nodeName" : nodeName ,
} . AsSelector ( ) . String ( )
// Define test pods
pods := [ ] * v1 . Pod {
2023-02-10 19:54:31 -05:00
getGracePeriodOverrideTestPod ( "pod-to-evict-" + string ( uuid . NewUUID ( ) ) , nodeName , 5 , "" ) ,
2022-10-10 07:58:40 -04:00
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
ginkgo . By ( "reating batch pods" )
2022-12-12 04:11:10 -05:00
e2epod . NewPodClient ( f ) . CreateBatch ( ctx , pods )
2022-10-10 07:58:40 -04:00
list , err := e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
FieldSelector : nodeSelector ,
} )
framework . ExpectNoError ( err )
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2022-10-10 07:58:40 -04:00
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
FieldSelector : nodeSelector ,
} )
if err != nil {
framework . Failf ( "Failed to start batch pod: %q" , err )
}
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2022-10-10 07:58:40 -04:00
for _ , pod := range list . Items {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Pod (%v/%v) status conditions: %q" , pod . Namespace , pod . Name , & pod . Status . Conditions )
2022-10-10 07:58:40 -04:00
}
ginkgo . By ( "Verifying batch pods are running" )
for _ , pod := range list . Items {
if podReady , err := testutils . PodRunningReady ( & pod ) ; err != nil || ! podReady {
2023-02-10 19:54:31 -05:00
framework . Failf ( "Failed to start batch pod: (%v/%v)" , pod . Namespace , pod . Name )
2022-10-10 07:58:40 -04:00
}
}
ginkgo . By ( "Emitting shutdown signal" )
err = emitSignalPrepareForShutdown ( true )
framework . ExpectNoError ( err )
ginkgo . By ( "Verifying that all pods are shutdown" )
// All pod should be shutdown
gomega . Eventually ( func ( ) error {
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
FieldSelector : nodeSelector ,
} )
if err != nil {
return err
}
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2022-10-10 07:58:40 -04:00
for _ , pod := range list . Items {
if ! isPodShutdown ( & pod ) {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Expecting pod to be shutdown, but it's not currently. Pod: (%v/%v), Pod Status Phase: %q, Pod Status Reason: %q" , pod . Namespace , pod . Name , pod . Status . Phase , pod . Status . Reason )
2022-10-10 07:58:40 -04:00
return fmt . Errorf ( "pod should be shutdown, phase: %s" , pod . Status . Phase )
}
2022-11-08 13:49:13 -05:00
podDisruptionCondition := e2epod . FindPodConditionByType ( & pod . Status , v1 . DisruptionTarget )
2022-10-10 07:58:40 -04:00
if podDisruptionCondition == nil {
2023-02-10 19:54:31 -05:00
framework . Failf ( "pod (%v/%v) should have the condition: %q, pod status: %v" , pod . Namespace , pod . Name , v1 . DisruptionTarget , pod . Status )
2022-10-10 07:58:40 -04:00
}
}
return nil
} , podStatusUpdateTimeout + ( nodeShutdownGracePeriod ) , pollInterval ) . Should ( gomega . BeNil ( ) )
} )
} )
2021-02-01 02:32:41 -05:00
ginkgo . Context ( "when gracefully shutting down" , func ( ) {
const (
pollInterval = 1 * time . Second
2021-11-03 00:03:19 -04:00
podStatusUpdateTimeout = 30 * time . Second
nodeStatusUpdateTimeout = 30 * time . Second
2021-02-01 02:32:41 -05:00
nodeShutdownGracePeriod = 20 * time . Second
nodeShutdownGracePeriodCriticalPods = 10 * time . Second
)
2022-12-12 04:11:10 -05:00
tempSetCurrentKubeletConfig ( f , func ( ctx context . Context , initialConfig * kubeletconfig . KubeletConfiguration ) {
2025-09-11 02:14:01 -04:00
if initialConfig . FeatureGates == nil {
initialConfig . FeatureGates = map [ string ] bool { }
2021-11-12 03:49:33 -05:00
}
2025-09-11 02:14:01 -04:00
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdown ) ] = true
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdownBasedOnPodPriority ) ] = false
initialConfig . FeatureGates [ string ( features . PodReadyToStartContainersCondition ) ] = true
2021-02-01 02:32:41 -05:00
initialConfig . ShutdownGracePeriod = metav1 . Duration { Duration : nodeShutdownGracePeriod }
initialConfig . ShutdownGracePeriodCriticalPods = metav1 . Duration { Duration : nodeShutdownGracePeriodCriticalPods }
} )
2022-12-12 04:11:10 -05:00
ginkgo . BeforeEach ( func ( ctx context . Context ) {
2021-04-28 04:10:11 -04:00
ginkgo . By ( "Wait for the node to be ready" )
2022-12-12 04:11:10 -05:00
waitForNodeReady ( ctx )
2021-04-28 04:10:11 -04:00
} )
2022-12-12 04:11:10 -05:00
ginkgo . AfterEach ( func ( ctx context . Context ) {
2021-02-01 02:32:41 -05:00
ginkgo . By ( "Emitting Shutdown false signal; cancelling the shutdown" )
err := emitSignalPrepareForShutdown ( false )
framework . ExpectNoError ( err )
} )
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should be able to gracefully shutdown pods with various grace periods" , func ( ctx context . Context ) {
2022-12-12 04:11:10 -05:00
nodeName := getNodeName ( ctx , f )
2021-02-01 02:32:41 -05:00
nodeSelector := fields . Set {
"spec.nodeName" : nodeName ,
} . AsSelector ( ) . String ( )
// Define test pods
pods := [ ] * v1 . Pod {
2023-02-10 19:54:31 -05:00
getGracePeriodOverrideTestPod ( "period-120-" + string ( uuid . NewUUID ( ) ) , nodeName , 120 , "" ) ,
getGracePeriodOverrideTestPod ( "period-5-" + string ( uuid . NewUUID ( ) ) , nodeName , 5 , "" ) ,
getGracePeriodOverrideTestPod ( "period-critical-120-" + string ( uuid . NewUUID ( ) ) , nodeName , 120 , scheduling . SystemNodeCritical ) ,
getGracePeriodOverrideTestPod ( "period-critical-5-" + string ( uuid . NewUUID ( ) ) , nodeName , 5 , scheduling . SystemNodeCritical ) ,
2021-02-01 02:32:41 -05:00
}
ginkgo . By ( "Creating batch pods" )
2022-12-12 04:11:10 -05:00
e2epod . NewPodClient ( f ) . CreateBatch ( ctx , pods )
2021-02-01 02:32:41 -05:00
2022-12-12 04:11:10 -05:00
list , err := e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
2021-02-01 02:32:41 -05:00
FieldSelector : nodeSelector ,
} )
framework . ExpectNoError ( err )
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2021-02-01 02:32:41 -05:00
2022-12-12 04:11:10 -05:00
ctx , cancel := context . WithCancel ( ctx )
2022-06-06 21:12:59 -04:00
defer cancel ( )
go func ( ) {
defer ginkgo . GinkgoRecover ( )
w := & cache . ListWatch {
WatchFunc : func ( options metav1 . ListOptions ) ( watch . Interface , error ) {
2022-12-12 04:11:10 -05:00
return f . ClientSet . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Watch ( ctx , options )
2022-06-06 21:12:59 -04:00
} ,
}
// Setup watch to continuously monitor any pod events and detect invalid pod status updates
_ , err = watchtools . Until ( ctx , list . ResourceVersion , w , func ( event watch . Event ) ( bool , error ) {
if pod , ok := event . Object . ( * v1 . Pod ) ; ok {
if isPodStatusAffectedByIssue108594 ( pod ) {
return false , fmt . Errorf ( "failing test due to detecting invalid pod status" )
}
// Watch will never terminate (only when the test ends due to context cancellation)
return false , nil
}
return false , nil
} )
// Ignore timeout error since the context will be explicitly cancelled and the watch will never return true
2025-08-01 04:48:06 -04:00
if err != nil && ! wait . Interrupted ( err ) {
2022-06-06 21:12:59 -04:00
framework . Failf ( "watch for invalid pod status failed: %v" , err . Error ( ) )
}
} ( )
2021-03-04 02:31:57 -05:00
ginkgo . By ( "Verifying batch pods are running" )
2021-02-01 02:32:41 -05:00
for _ , pod := range list . Items {
2021-03-04 02:31:57 -05:00
if podReady , err := testutils . PodRunningReady ( & pod ) ; err != nil || ! podReady {
framework . Failf ( "Failed to start batch pod: %v" , pod . Name )
}
2021-02-01 02:32:41 -05:00
}
ginkgo . By ( "Emitting shutdown signal" )
err = emitSignalPrepareForShutdown ( true )
framework . ExpectNoError ( err )
2021-03-04 02:31:57 -05:00
ginkgo . By ( "Verifying that non-critical pods are shutdown" )
2021-02-01 02:32:41 -05:00
// Not critical pod should be shutdown
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
2021-02-01 02:32:41 -05:00
FieldSelector : nodeSelector ,
} )
if err != nil {
return err
}
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2021-02-01 02:32:41 -05:00
for _ , pod := range list . Items {
if kubelettypes . IsCriticalPod ( & pod ) {
2021-11-03 00:03:19 -04:00
if isPodShutdown ( & pod ) {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Expecting critical pod (%v/%v) to be running, but it's not currently. Pod Status %+v" , pod . Namespace , pod . Name , pod . Status )
return fmt . Errorf ( "critical pod (%v/%v) should not be shutdown, phase: %s" , pod . Namespace , pod . Name , pod . Status . Phase )
2021-02-01 02:32:41 -05:00
}
} else {
2021-11-03 00:03:19 -04:00
if ! isPodShutdown ( & pod ) {
2024-01-11 06:45:51 -05:00
framework . Logf ( "Expecting non-critical pod (%v/%v) to be shutdown, but it's not currently. Pod Status %+v" , pod . Namespace , pod . Name , pod . Status )
2023-02-10 19:54:31 -05:00
return fmt . Errorf ( "pod (%v/%v) should be shutdown, phase: %s" , pod . Namespace , pod . Name , pod . Status . Phase )
2021-02-01 02:32:41 -05:00
}
}
}
return nil
2022-12-12 04:11:10 -05:00
} , podStatusUpdateTimeout , pollInterval ) . Should ( gomega . Succeed ( ) )
2021-02-01 02:32:41 -05:00
2021-03-04 02:31:57 -05:00
ginkgo . By ( "Verifying that all pods are shutdown" )
2021-02-01 02:32:41 -05:00
// All pod should be shutdown
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
2021-02-01 02:32:41 -05:00
FieldSelector : nodeSelector ,
} )
if err != nil {
return err
}
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2021-02-01 02:32:41 -05:00
for _ , pod := range list . Items {
2021-11-03 00:03:19 -04:00
if ! isPodShutdown ( & pod ) {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Expecting pod (%v/%v) to be shutdown, but it's not currently: Pod Status %+v" , pod . Namespace , pod . Name , pod . Status )
return fmt . Errorf ( "pod (%v/%v) should be shutdown, phase: %s" , pod . Namespace , pod . Name , pod . Status . Phase )
2021-02-01 02:32:41 -05:00
}
}
return nil
} ,
// Critical pod starts shutdown after (nodeShutdownGracePeriod-nodeShutdownGracePeriodCriticalPods)
podStatusUpdateTimeout + ( nodeShutdownGracePeriod - nodeShutdownGracePeriodCriticalPods ) ,
2022-12-12 04:11:10 -05:00
pollInterval ) . Should ( gomega . Succeed ( ) )
2022-06-06 21:12:59 -04:00
2023-10-07 07:57:18 -04:00
ginkgo . By ( "Verify that all pod ready to start condition are set to false after terminating" )
// All pod ready to start condition should set to false
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
FieldSelector : nodeSelector ,
} )
if err != nil {
return err
}
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) )
for _ , pod := range list . Items {
if ! isPodReadyToStartConditionSetToFalse ( & pod ) {
framework . Logf ( "Expecting pod (%v/%v) 's ready to start condition set to false, " +
"but it's not currently: Pod Condition %+v" , pod . Namespace , pod . Name , pod . Status . Conditions )
2025-02-21 13:46:17 -05:00
return fmt . Errorf ( "pod (%v/%v) 's ready to start condition should be false, condition: %v, phase: %s" ,
2023-10-07 07:57:18 -04:00
pod . Namespace , pod . Name , pod . Status . Conditions , pod . Status . Phase )
}
}
return nil
} ,
) . Should ( gomega . Succeed ( ) )
2021-02-01 02:32:41 -05:00
} )
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should be able to handle a cancelled shutdown" , func ( ctx context . Context ) {
2021-02-01 02:32:41 -05:00
ginkgo . By ( "Emitting Shutdown signal" )
err := emitSignalPrepareForShutdown ( true )
framework . ExpectNoError ( err )
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
isReady := getNodeReadyStatus ( ctx , f )
2021-02-01 02:32:41 -05:00
if isReady {
return fmt . Errorf ( "node did not become shutdown as expected" )
}
return nil
2022-12-12 04:11:10 -05:00
} , nodeStatusUpdateTimeout , pollInterval ) . Should ( gomega . Succeed ( ) )
2021-02-01 02:32:41 -05:00
ginkgo . By ( "Emitting Shutdown false signal; cancelling the shutdown" )
err = emitSignalPrepareForShutdown ( false )
framework . ExpectNoError ( err )
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
isReady := getNodeReadyStatus ( ctx , f )
2021-02-01 02:32:41 -05:00
if ! isReady {
return fmt . Errorf ( "node did not recover as expected" )
}
return nil
2022-12-12 04:11:10 -05:00
} , nodeStatusUpdateTimeout , pollInterval ) . Should ( gomega . Succeed ( ) )
2021-02-01 02:32:41 -05:00
} )
} )
2021-11-12 03:49:33 -05:00
2024-02-19 11:26:33 -05:00
framework . Context ( "when gracefully shutting down with Pod priority" , framework . WithFlaky ( ) , func ( ) {
2021-11-12 03:49:33 -05:00
const (
2021-11-25 01:24:32 -05:00
pollInterval = 1 * time . Second
2023-03-06 18:38:45 -05:00
podStatusUpdateTimeout = 30 * time . Second
2021-11-25 01:24:32 -05:00
priorityClassesCreateTimeout = 10 * time . Second
2021-11-12 03:49:33 -05:00
)
var (
customClassA = getPriorityClass ( "custom-class-a" , 100000 )
customClassB = getPriorityClass ( "custom-class-b" , 10000 )
customClassC = getPriorityClass ( "custom-class-c" , 1000 )
)
2022-12-12 04:11:10 -05:00
tempSetCurrentKubeletConfig ( f , func ( ctx context . Context , initialConfig * kubeletconfig . KubeletConfiguration ) {
2025-09-11 02:14:01 -04:00
if initialConfig . FeatureGates == nil {
initialConfig . FeatureGates = map [ string ] bool { }
2021-11-12 03:49:33 -05:00
}
2025-09-11 02:14:01 -04:00
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdown ) ] = true
initialConfig . FeatureGates [ string ( features . GracefulNodeShutdownBasedOnPodPriority ) ] = true
2021-11-12 03:49:33 -05:00
initialConfig . ShutdownGracePeriodByPodPriority = [ ] kubeletconfig . ShutdownGracePeriodByPodPriority {
{
Priority : scheduling . SystemCriticalPriority ,
ShutdownGracePeriodSeconds : int64 ( podStatusUpdateTimeout / time . Second ) ,
} ,
{
Priority : customClassA . Value ,
ShutdownGracePeriodSeconds : int64 ( podStatusUpdateTimeout / time . Second ) ,
} ,
{
Priority : customClassB . Value ,
ShutdownGracePeriodSeconds : int64 ( podStatusUpdateTimeout / time . Second ) ,
} ,
{
Priority : customClassC . Value ,
ShutdownGracePeriodSeconds : int64 ( podStatusUpdateTimeout / time . Second ) ,
} ,
{
Priority : scheduling . DefaultPriorityWhenNoDefaultClassExists ,
ShutdownGracePeriodSeconds : int64 ( podStatusUpdateTimeout / time . Second ) ,
} ,
}
} )
2022-12-12 04:11:10 -05:00
ginkgo . BeforeEach ( func ( ctx context . Context ) {
2021-11-12 03:49:33 -05:00
ginkgo . By ( "Wait for the node to be ready" )
2022-12-12 04:11:10 -05:00
waitForNodeReady ( ctx )
2021-11-25 01:24:32 -05:00
customClasses := [ ] * schedulingv1 . PriorityClass { customClassA , customClassB , customClassC }
for _ , customClass := range customClasses {
2022-12-12 04:11:10 -05:00
_ , err := f . ClientSet . SchedulingV1 ( ) . PriorityClasses ( ) . Create ( ctx , customClass , metav1 . CreateOptions { } )
2021-11-25 01:24:32 -05:00
if err != nil && ! apierrors . IsAlreadyExists ( err ) {
framework . ExpectNoError ( err )
}
2021-11-12 03:49:33 -05:00
}
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
2021-11-25 01:24:32 -05:00
for _ , customClass := range customClasses {
2022-12-12 04:11:10 -05:00
_ , err := f . ClientSet . SchedulingV1 ( ) . PriorityClasses ( ) . Get ( ctx , customClass . Name , metav1 . GetOptions { } )
2021-11-25 01:24:32 -05:00
if err != nil {
return err
}
}
return nil
2022-12-12 04:11:10 -05:00
} , priorityClassesCreateTimeout , pollInterval ) . Should ( gomega . Succeed ( ) )
2021-11-12 03:49:33 -05:00
} )
ginkgo . AfterEach ( func ( ) {
ginkgo . By ( "Emitting Shutdown false signal; cancelling the shutdown" )
err := emitSignalPrepareForShutdown ( false )
framework . ExpectNoError ( err )
} )
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should be able to gracefully shutdown pods with various grace periods" , func ( ctx context . Context ) {
2022-12-12 04:11:10 -05:00
nodeName := getNodeName ( ctx , f )
2021-11-12 03:49:33 -05:00
nodeSelector := fields . Set {
"spec.nodeName" : nodeName ,
} . AsSelector ( ) . String ( )
2023-02-10 19:54:31 -05:00
var (
period5Name = "period-5-" + string ( uuid . NewUUID ( ) )
periodC5Name = "period-c-5-" + string ( uuid . NewUUID ( ) )
periodB5Name = "period-b-5-" + string ( uuid . NewUUID ( ) )
periodA5Name = "period-a-5-" + string ( uuid . NewUUID ( ) )
periodCritical5Name = "period-critical-5-" + string ( uuid . NewUUID ( ) )
)
2021-11-12 03:49:33 -05:00
// Define test pods
pods := [ ] * v1 . Pod {
2023-02-10 19:54:31 -05:00
getGracePeriodOverrideTestPod ( period5Name , nodeName , 5 , "" ) ,
getGracePeriodOverrideTestPod ( periodC5Name , nodeName , 5 , customClassC . Name ) ,
getGracePeriodOverrideTestPod ( periodB5Name , nodeName , 5 , customClassB . Name ) ,
getGracePeriodOverrideTestPod ( periodA5Name , nodeName , 5 , customClassA . Name ) ,
getGracePeriodOverrideTestPod ( periodCritical5Name , nodeName , 5 , scheduling . SystemNodeCritical ) ,
2021-11-12 03:49:33 -05:00
}
// Expected down steps
downSteps := [ ] [ ] string {
{
2023-02-10 19:54:31 -05:00
period5Name ,
2021-11-12 03:49:33 -05:00
} ,
{
2023-02-10 19:54:31 -05:00
period5Name ,
periodC5Name ,
2021-11-12 03:49:33 -05:00
} ,
{
2023-02-10 19:54:31 -05:00
period5Name ,
periodC5Name ,
2023-03-06 18:38:45 -05:00
periodB5Name ,
2021-11-12 03:49:33 -05:00
} ,
{
2023-02-10 19:54:31 -05:00
period5Name ,
periodC5Name ,
2023-03-06 18:38:45 -05:00
periodB5Name ,
2023-02-10 19:54:31 -05:00
periodA5Name ,
2021-11-12 03:49:33 -05:00
} ,
{
2023-02-10 19:54:31 -05:00
period5Name ,
periodC5Name ,
2023-03-06 18:38:45 -05:00
periodB5Name ,
2023-02-10 19:54:31 -05:00
periodA5Name ,
periodCritical5Name ,
2021-11-12 03:49:33 -05:00
} ,
}
ginkgo . By ( "Creating batch pods" )
2022-12-12 04:11:10 -05:00
e2epod . NewPodClient ( f ) . CreateBatch ( ctx , pods )
2021-11-12 03:49:33 -05:00
2022-12-12 04:11:10 -05:00
list , err := e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
2021-11-12 03:49:33 -05:00
FieldSelector : nodeSelector ,
} )
framework . ExpectNoError ( err )
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2021-11-12 03:49:33 -05:00
ginkgo . By ( "Verifying batch pods are running" )
for _ , pod := range list . Items {
if podReady , err := testutils . PodRunningReady ( & pod ) ; err != nil || ! podReady {
2023-02-10 19:54:31 -05:00
framework . Failf ( "Failed to start batch pod: (%v/%v)" , pod . Namespace , pod . Name )
2021-11-12 03:49:33 -05:00
}
}
ginkgo . By ( "Emitting shutdown signal" )
err = emitSignalPrepareForShutdown ( true )
framework . ExpectNoError ( err )
ginkgo . By ( "Verifying that pods are shutdown" )
for _ , step := range downSteps {
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) error {
list , err = e2epod . NewPodClient ( f ) . List ( ctx , metav1 . ListOptions {
2021-11-12 03:49:33 -05:00
FieldSelector : nodeSelector ,
} )
if err != nil {
return err
}
2023-10-10 22:37:36 -04:00
gomega . Expect ( list . Items ) . To ( gomega . HaveLen ( len ( pods ) ) , "the number of pods is not as expected" )
2021-11-12 03:49:33 -05:00
for _ , pod := range list . Items {
shouldShutdown := false
for _ , podName := range step {
if podName == pod . Name {
shouldShutdown = true
break
}
}
if ! shouldShutdown {
if pod . Status . Phase != v1 . PodRunning {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Expecting pod to be running, but it's not currently. Pod: (%v/%v), Pod Status Phase: %q, Pod Status Reason: %q" , pod . Namespace , pod . Name , pod . Status . Phase , pod . Status . Reason )
return fmt . Errorf ( "pod (%v/%v) should not be shutdown, phase: %s, reason: %s" , pod . Namespace , pod . Name , pod . Status . Phase , pod . Status . Reason )
2021-11-12 03:49:33 -05:00
}
} else {
if pod . Status . Reason != podShutdownReason {
2023-02-10 19:54:31 -05:00
framework . Logf ( "Expecting pod to be shutdown, but it's not currently. Pod: (%v/%v), Pod Status Phase: %q, Pod Status Reason: %q" , pod . Namespace , pod . Name , pod . Status . Phase , pod . Status . Reason )
2021-11-12 03:49:33 -05:00
for _ , item := range list . Items {
framework . Logf ( "DEBUG %s, %s, %s" , item . Name , item . Status . Phase , pod . Status . Reason )
}
2023-02-10 19:54:31 -05:00
return fmt . Errorf ( "pod (%v/%v) should be shutdown, reason: %s" , pod . Namespace , pod . Name , pod . Status . Reason )
2021-11-12 03:49:33 -05:00
}
}
}
return nil
2022-12-12 04:11:10 -05:00
} , podStatusUpdateTimeout , pollInterval ) . Should ( gomega . Succeed ( ) )
2021-11-12 03:49:33 -05:00
}
2022-03-11 04:30:10 -05:00
ginkgo . By ( "should have state file" )
stateFile := "/var/lib/kubelet/graceful_node_shutdown_state"
_ , err = os . Stat ( stateFile )
framework . ExpectNoError ( err )
2021-11-12 03:49:33 -05:00
} )
} )
2021-02-01 02:32:41 -05:00
} )
2021-11-12 03:49:33 -05:00
func getPriorityClass ( name string , value int32 ) * schedulingv1 . PriorityClass {
priority := & schedulingv1 . PriorityClass {
TypeMeta : metav1 . TypeMeta {
Kind : "PriorityClass" ,
APIVersion : "scheduling.k8s.io/v1" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
} ,
Value : value ,
}
return priority
}
2023-04-16 09:21:49 -04:00
// getGracePeriodOverrideTestPod returns a new Pod object containing a container
// runs a shell script, hangs the process until a SIGTERM signal is received.
// The script waits for $PID to ensure that the process does not exist.
// If priorityClassName is scheduling.SystemNodeCritical, the Pod is marked as critical and a comment is added.
2021-11-12 03:49:33 -05:00
func getGracePeriodOverrideTestPod ( name string , node string , gracePeriod int64 , priorityClassName string ) * v1 . Pod {
2021-02-01 02:32:41 -05:00
pod := & v1 . Pod {
TypeMeta : metav1 . TypeMeta {
Kind : "Pod" ,
APIVersion : "v1" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : name ,
Image : busyboxImage ,
Command : [ ] string { "sh" , "-c" } ,
Args : [ ] string { `
2023-04-16 09:21:49 -04:00
sleep 9999999 &
PID = $ !
_term ( ) {
echo "Caught SIGTERM signal!"
wait $ PID
}
trap _term SIGTERM
wait $ PID
` } ,
2021-02-01 02:32:41 -05:00
} ,
} ,
TerminationGracePeriodSeconds : & gracePeriod ,
NodeName : node ,
} ,
}
2021-11-12 03:49:33 -05:00
if priorityClassName == scheduling . SystemNodeCritical {
2021-02-01 02:32:41 -05:00
pod . ObjectMeta . Annotations = map [ string ] string {
kubelettypes . ConfigSourceAnnotationKey : kubelettypes . FileSource ,
}
2021-11-12 03:49:33 -05:00
pod . Spec . PriorityClassName = priorityClassName
2023-10-10 22:37:36 -04:00
if ! kubelettypes . IsCriticalPod ( pod ) {
framework . Failf ( "pod %q should be a critical pod" , pod . Name )
}
2021-02-01 02:32:41 -05:00
} else {
2021-11-12 03:49:33 -05:00
pod . Spec . PriorityClassName = priorityClassName
2023-10-10 22:37:36 -04:00
if kubelettypes . IsCriticalPod ( pod ) {
framework . Failf ( "pod %q should not be a critical pod" , pod . Name )
}
2021-02-01 02:32:41 -05:00
}
return pod
}
// Emits a fake PrepareForShutdown dbus message on system dbus. Will cause kubelet to react to an active shutdown event.
func emitSignalPrepareForShutdown ( b bool ) error {
2022-01-27 09:17:07 -05:00
conn , err := dbus . ConnectSystemBus ( )
if err != nil {
return err
}
defer conn . Close ( )
return conn . Emit ( "/org/freedesktop/login1" , "org.freedesktop.login1.Manager.PrepareForShutdown" , b )
2022-01-05 22:37:06 -05:00
}
2021-11-03 00:03:19 -04:00
const (
// https://github.com/kubernetes/kubernetes/blob/1dd781ddcad454cc381806fbc6bd5eba8fa368d7/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go#L43-L44
podShutdownReason = "Terminated"
podShutdownMessage = "Pod was terminated in response to imminent node shutdown."
)
func isPodShutdown ( pod * v1 . Pod ) bool {
if pod == nil {
return false
}
hasContainersNotReadyCondition := false
for _ , cond := range pod . Status . Conditions {
if cond . Type == v1 . ContainersReady && cond . Status == v1 . ConditionFalse {
hasContainersNotReadyCondition = true
}
}
2021-12-08 20:53:34 -05:00
return pod . Status . Message == podShutdownMessage && pod . Status . Reason == podShutdownReason && hasContainersNotReadyCondition && pod . Status . Phase == v1 . PodFailed
2021-11-03 00:03:19 -04:00
}
2022-03-08 22:54:25 -05:00
// Pods should never report failed phase and have ready condition = true (https://github.com/kubernetes/kubernetes/issues/108594)
func isPodStatusAffectedByIssue108594 ( pod * v1 . Pod ) bool {
return pod . Status . Phase == v1 . PodFailed && podutils . IsPodReady ( pod )
}
2023-10-07 07:57:18 -04:00
func isPodReadyToStartConditionSetToFalse ( pod * v1 . Pod ) bool {
if pod == nil {
return false
}
readyToStartConditionSetToFalse := false
for _ , cond := range pod . Status . Conditions {
if cond . Status == v1 . ConditionFalse {
readyToStartConditionSetToFalse = true
}
}
return readyToStartConditionSetToFalse
}