mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
Update test logic to simulate NodeReady/False and NodeReady/Unknown events correctly
- optimize code to use one loop to spin up goroutines - add `defer cleanupTest()` to avoid goroutine leaks - use only one heartbeat channel
This commit is contained in:
parent
bef8d426f2
commit
b6b92b6075
@ -19,11 +19,13 @@ package scheduler
|
|||||||
// This file tests the Taint feature.
|
// This file tests the Taint feature.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -584,6 +586,7 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
nodeCount := 3
|
nodeCount := 3
|
||||||
zero := int64(0)
|
zero := int64(0)
|
||||||
gracePeriod := int64(1)
|
gracePeriod := int64(1)
|
||||||
|
heartbeatInternal := time.Second * 2
|
||||||
testPod := &v1.Pod{
|
testPod := &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "testpod1", DeletionGracePeriodSeconds: &zero},
|
ObjectMeta: metav1.ObjectMeta{Name: "testpod1", DeletionGracePeriodSeconds: &zero},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
@ -657,7 +660,6 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
context := initTestMaster(t, "taint-based-evictions", admission)
|
context := initTestMaster(t, "taint-based-evictions", admission)
|
||||||
defer cleanupTest(t, context)
|
|
||||||
|
|
||||||
// Build clientset and informers for controllers.
|
// Build clientset and informers for controllers.
|
||||||
externalClientset := kubernetes.NewForConfigOrDie(&restclient.Config{
|
externalClientset := kubernetes.NewForConfigOrDie(&restclient.Config{
|
||||||
@ -669,6 +671,7 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
podTolerations.SetExternalKubeInformerFactory(externalInformers)
|
podTolerations.SetExternalKubeInformerFactory(externalInformers)
|
||||||
|
|
||||||
context = initTestScheduler(t, context, true, nil)
|
context = initTestScheduler(t, context, true, nil)
|
||||||
|
defer cleanupTest(t, context)
|
||||||
cs := context.clientSet
|
cs := context.clientSet
|
||||||
informers := context.informerFactory
|
informers := context.informerFactory
|
||||||
_, err := cs.CoreV1().Namespaces().Create(context.ns)
|
_, err := cs.CoreV1().Namespaces().Create(context.ns)
|
||||||
@ -726,8 +729,9 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
Allocatable: nodeRes,
|
Allocatable: nodeRes,
|
||||||
Conditions: []v1.NodeCondition{
|
Conditions: []v1.NodeCondition{
|
||||||
{
|
{
|
||||||
Type: v1.NodeReady,
|
Type: v1.NodeReady,
|
||||||
Status: v1.ConditionTrue,
|
Status: v1.ConditionTrue,
|
||||||
|
LastHeartbeatTime: metav1.Now(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -737,33 +741,6 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regularly send heartbeat event to APIServer so that the cluster doesn't enter fullyDisruption mode.
|
|
||||||
// TODO(Huang-Wei): use "NodeDisruptionExclusion" feature to simply the below logic when it's beta.
|
|
||||||
var heartbeatChans []chan struct{}
|
|
||||||
for i := 0; i < nodeCount; i++ {
|
|
||||||
heartbeatChans = append(heartbeatChans, make(chan struct{}))
|
|
||||||
}
|
|
||||||
for i := 0; i < nodeCount; i++ {
|
|
||||||
// Spin up <nodeCount> goroutines to send heartbeat event to APIServer periodically.
|
|
||||||
go func(i int) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-heartbeatChans[i]:
|
|
||||||
return
|
|
||||||
case <-time.Tick(2 * time.Second):
|
|
||||||
nodes[i].Status.Conditions = []v1.NodeCondition{
|
|
||||||
{
|
|
||||||
Type: v1.NodeReady,
|
|
||||||
Status: v1.ConditionTrue,
|
|
||||||
LastHeartbeatTime: metav1.Now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
updateNodeStatus(cs, nodes[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
neededNode := nodes[1]
|
neededNode := nodes[1]
|
||||||
if test.pod != nil {
|
if test.pod != nil {
|
||||||
test.pod.Name = fmt.Sprintf("testpod-%d", i)
|
test.pod.Name = fmt.Sprintf("testpod-%d", i)
|
||||||
@ -790,18 +767,53 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regularly send heartbeat event to APIServer so that the cluster doesn't enter fullyDisruption mode.
|
||||||
|
// TODO(Huang-Wei): use "NodeDisruptionExclusion" feature to simply the below logic when it's beta.
|
||||||
for i := 0; i < nodeCount; i++ {
|
for i := 0; i < nodeCount; i++ {
|
||||||
// Stop the neededNode's heartbeat goroutine.
|
var conditions []v1.NodeCondition
|
||||||
if neededNode.Name == fmt.Sprintf("node-%d", i) {
|
// If current node is not <neededNode>
|
||||||
heartbeatChans[i] <- struct{}{}
|
if neededNode.Name != nodes[i].Name {
|
||||||
break
|
conditions = []v1.NodeCondition{
|
||||||
|
{
|
||||||
|
Type: v1.NodeReady,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c, err := nodeReadyStatus(test.nodeConditions)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// Need to distinguish NodeReady/False and NodeReady/Unknown.
|
||||||
|
// If we try to update the node with condition NotReady/False, i.e. expect a NotReady:NoExecute taint
|
||||||
|
// we need to keep sending the update event to keep it alive, rather than just sending once.
|
||||||
|
if c == v1.ConditionFalse {
|
||||||
|
conditions = test.nodeConditions
|
||||||
|
} else if c == v1.ConditionUnknown {
|
||||||
|
// If it's expected to update the node with condition NotReady/Unknown,
|
||||||
|
// i.e. expect a Unreachable:NoExecute taint,
|
||||||
|
// we need to only send the update event once to simulate the network unreachable scenario.
|
||||||
|
nodeCopy := nodeCopyWithConditions(nodes[i], test.nodeConditions)
|
||||||
|
if err := updateNodeStatus(cs, nodeCopy); err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
t.Errorf("Cannot update node: %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// Keeping sending NodeReady/True or NodeReady/False events.
|
||||||
neededNode.Status.Conditions = test.nodeConditions
|
go func(i int) {
|
||||||
// Update node condition.
|
for {
|
||||||
err = updateNodeStatus(cs, neededNode)
|
select {
|
||||||
if err != nil {
|
case <-context.ctx.Done():
|
||||||
t.Fatalf("Cannot update node: %v", err)
|
return
|
||||||
|
case <-time.Tick(heartbeatInternal):
|
||||||
|
nodeCopy := nodeCopyWithConditions(nodes[i], conditions)
|
||||||
|
if err := updateNodeStatus(cs, nodeCopy); err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
t.Errorf("Cannot update node: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := waitForNodeTaints(cs, neededNode, test.nodeTaints); err != nil {
|
if err := waitForNodeTaints(cs, neededNode, test.nodeTaints); err != nil {
|
||||||
@ -826,10 +838,6 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
cleanupPods(cs, t, []*v1.Pod{test.pod})
|
cleanupPods(cs, t, []*v1.Pod{test.pod})
|
||||||
}
|
}
|
||||||
// Close all heartbeat channels.
|
|
||||||
for i := 0; i < nodeCount; i++ {
|
|
||||||
close(heartbeatChans[i])
|
|
||||||
}
|
|
||||||
cleanupNodes(cs, t)
|
cleanupNodes(cs, t)
|
||||||
waitForSchedulerCacheCleanup(context.scheduler, t)
|
waitForSchedulerCacheCleanup(context.scheduler, t)
|
||||||
})
|
})
|
||||||
@ -844,3 +852,26 @@ func getTolerationSeconds(tolerations []v1.Toleration) (int64, error) {
|
|||||||
}
|
}
|
||||||
return 0, fmt.Errorf("cannot find toleration")
|
return 0, fmt.Errorf("cannot find toleration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodeReadyStatus returns the status of first condition with type NodeReady.
|
||||||
|
// If none of the condition is of type NodeReady, returns an error.
|
||||||
|
func nodeReadyStatus(conditions []v1.NodeCondition) (v1.ConditionStatus, error) {
|
||||||
|
for _, c := range conditions {
|
||||||
|
if c.Type != v1.NodeReady {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Just return the first condition with type NodeReady
|
||||||
|
return c.Status, nil
|
||||||
|
}
|
||||||
|
return v1.ConditionFalse, errors.New("None of the conditions is of type NodeReady")
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeCopyWithConditions(node *v1.Node, conditions []v1.NodeCondition) *v1.Node {
|
||||||
|
copy := node.DeepCopy()
|
||||||
|
copy.ResourceVersion = "0"
|
||||||
|
copy.Status.Conditions = conditions
|
||||||
|
for i := range copy.Status.Conditions {
|
||||||
|
copy.Status.Conditions[i].LastHeartbeatTime = metav1.Now()
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user