mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 11:13:48 +00:00
Merge pull request #121755 from kerthcet/fix/node-update-event
Fix nodeUpdate event missing some potential changes
This commit is contained in:
commit
4189053453
@ -92,8 +92,8 @@ func (sched *Scheduler) updateNodeInCache(oldObj, newObj interface{}) {
|
|||||||
|
|
||||||
nodeInfo := sched.Cache.UpdateNode(logger, oldNode, newNode)
|
nodeInfo := sched.Cache.UpdateNode(logger, oldNode, newNode)
|
||||||
// Only requeue unschedulable pods if the node became more schedulable.
|
// Only requeue unschedulable pods if the node became more schedulable.
|
||||||
if event := nodeSchedulingPropertiesChange(newNode, oldNode); event != nil {
|
for _, evt := range nodeSchedulingPropertiesChange(newNode, oldNode) {
|
||||||
sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, *event, oldNode, newNode, preCheckForNode(nodeInfo))
|
sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, evt, oldNode, newNode, preCheckForNode(nodeInfo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,24 +522,26 @@ func addAllEventHandlers(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeSchedulingPropertiesChange(newNode *v1.Node, oldNode *v1.Node) *framework.ClusterEvent {
|
func nodeSchedulingPropertiesChange(newNode *v1.Node, oldNode *v1.Node) []framework.ClusterEvent {
|
||||||
|
var events []framework.ClusterEvent
|
||||||
|
|
||||||
if nodeSpecUnschedulableChanged(newNode, oldNode) {
|
if nodeSpecUnschedulableChanged(newNode, oldNode) {
|
||||||
return &queue.NodeSpecUnschedulableChange
|
events = append(events, queue.NodeSpecUnschedulableChange)
|
||||||
}
|
}
|
||||||
if nodeAllocatableChanged(newNode, oldNode) {
|
if nodeAllocatableChanged(newNode, oldNode) {
|
||||||
return &queue.NodeAllocatableChange
|
events = append(events, queue.NodeAllocatableChange)
|
||||||
}
|
}
|
||||||
if nodeLabelsChanged(newNode, oldNode) {
|
if nodeLabelsChanged(newNode, oldNode) {
|
||||||
return &queue.NodeLabelChange
|
events = append(events, queue.NodeLabelChange)
|
||||||
}
|
}
|
||||||
if nodeTaintsChanged(newNode, oldNode) {
|
if nodeTaintsChanged(newNode, oldNode) {
|
||||||
return &queue.NodeTaintChange
|
events = append(events, queue.NodeTaintChange)
|
||||||
}
|
}
|
||||||
if nodeConditionsChanged(newNode, oldNode) {
|
if nodeConditionsChanged(newNode, oldNode) {
|
||||||
return &queue.NodeConditionChange
|
events = append(events, queue.NodeConditionChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeAllocatableChanged(newNode *v1.Node, oldNode *v1.Node) bool {
|
func nodeAllocatableChanged(newNode *v1.Node, oldNode *v1.Node) bool {
|
||||||
|
@ -524,3 +524,85 @@ func TestAdmissionCheck(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNodeSchedulingPropertiesChange(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
newNode *v1.Node
|
||||||
|
oldNode *v1.Node
|
||||||
|
wantEvents []framework.ClusterEvent
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no specific changed applied",
|
||||||
|
newNode: st.MakeNode().Unschedulable(false).Obj(),
|
||||||
|
oldNode: st.MakeNode().Unschedulable(false).Obj(),
|
||||||
|
wantEvents: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only node spec unavailable changed",
|
||||||
|
newNode: st.MakeNode().Unschedulable(false).Obj(),
|
||||||
|
oldNode: st.MakeNode().Unschedulable(true).Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeSpecUnschedulableChange},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only node allocatable changed",
|
||||||
|
newNode: st.MakeNode().Capacity(map[v1.ResourceName]string{
|
||||||
|
v1.ResourceCPU: "1000m",
|
||||||
|
v1.ResourceMemory: "100m",
|
||||||
|
v1.ResourceName("example.com/foo"): "1"},
|
||||||
|
).Obj(),
|
||||||
|
oldNode: st.MakeNode().Capacity(map[v1.ResourceName]string{
|
||||||
|
v1.ResourceCPU: "1000m",
|
||||||
|
v1.ResourceMemory: "100m",
|
||||||
|
v1.ResourceName("example.com/foo"): "2"},
|
||||||
|
).Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeAllocatableChange},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only node label changed",
|
||||||
|
newNode: st.MakeNode().Label("foo", "bar").Obj(),
|
||||||
|
oldNode: st.MakeNode().Label("foo", "fuz").Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeLabelChange},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only node taint changed",
|
||||||
|
newNode: st.MakeNode().Taints([]v1.Taint{
|
||||||
|
{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectNoSchedule},
|
||||||
|
}).Obj(),
|
||||||
|
oldNode: st.MakeNode().Taints([]v1.Taint{
|
||||||
|
{Key: v1.TaintNodeUnschedulable, Value: "foo", Effect: v1.TaintEffectNoSchedule},
|
||||||
|
}).Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeTaintChange},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only node condition changed",
|
||||||
|
newNode: st.MakeNode().Obj(),
|
||||||
|
oldNode: st.MakeNode().Condition(
|
||||||
|
v1.NodeReady,
|
||||||
|
v1.ConditionTrue,
|
||||||
|
"Ready",
|
||||||
|
"Ready",
|
||||||
|
).Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeConditionChange},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both node label and node taint changed",
|
||||||
|
newNode: st.MakeNode().
|
||||||
|
Label("foo", "bar").
|
||||||
|
Taints([]v1.Taint{
|
||||||
|
{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectNoSchedule},
|
||||||
|
}).Obj(),
|
||||||
|
oldNode: st.MakeNode().Taints([]v1.Taint{
|
||||||
|
{Key: v1.TaintNodeUnschedulable, Value: "foo", Effect: v1.TaintEffectNoSchedule},
|
||||||
|
}).Obj(),
|
||||||
|
wantEvents: []framework.ClusterEvent{queue.NodeLabelChange, queue.NodeTaintChange},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
gotEvents := nodeSchedulingPropertiesChange(tc.newNode, tc.oldNode)
|
||||||
|
if diff := cmp.Diff(tc.wantEvents, gotEvents); diff != "" {
|
||||||
|
t.Errorf("unexpected event (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ package testing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
|
resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
|
||||||
@ -760,6 +761,27 @@ func (n *NodeWrapper) Taints(taints []v1.Taint) *NodeWrapper {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unschedulable applies the unschedulable field.
|
||||||
|
func (n *NodeWrapper) Unschedulable(unschedulable bool) *NodeWrapper {
|
||||||
|
n.Spec.Unschedulable = unschedulable
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition applies the node condition.
|
||||||
|
func (n *NodeWrapper) Condition(typ v1.NodeConditionType, status v1.ConditionStatus, message, reason string) *NodeWrapper {
|
||||||
|
n.Status.Conditions = []v1.NodeCondition{
|
||||||
|
{
|
||||||
|
Type: typ,
|
||||||
|
Status: status,
|
||||||
|
Message: message,
|
||||||
|
Reason: reason,
|
||||||
|
LastHeartbeatTime: metav1.Time{Time: time.Now()},
|
||||||
|
LastTransitionTime: metav1.Time{Time: time.Now()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// PersistentVolumeClaimWrapper wraps a PersistentVolumeClaim inside.
|
// PersistentVolumeClaimWrapper wraps a PersistentVolumeClaim inside.
|
||||||
type PersistentVolumeClaimWrapper struct{ v1.PersistentVolumeClaim }
|
type PersistentVolumeClaimWrapper struct{ v1.PersistentVolumeClaim }
|
||||||
|
|
||||||
|
137
test/integration/scheduler/eventhandler/eventhandler_test.go
Normal file
137
test/integration/scheduler/eventhandler/eventhandler_test.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 eventhandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/component-helpers/scheduling/corev1"
|
||||||
|
configv1 "k8s.io/kube-scheduler/config/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler"
|
||||||
|
configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||||
|
frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||||
|
st "k8s.io/kubernetes/pkg/scheduler/testing"
|
||||||
|
schedulerutils "k8s.io/kubernetes/test/integration/scheduler"
|
||||||
|
testutils "k8s.io/kubernetes/test/integration/util"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ framework.FilterPlugin = &fooPlugin{}
|
||||||
|
|
||||||
|
type fooPlugin struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *fooPlugin) Name() string {
|
||||||
|
return "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *fooPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
|
||||||
|
taints := nodeInfo.Node().Spec.Taints
|
||||||
|
if len(taints) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if corev1.TolerationsTolerateTaint(pod.Spec.Tolerations, &nodeInfo.Node().Spec.Taints[0]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return framework.NewStatus(framework.Unschedulable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *fooPlugin) EventsToRegister() []framework.ClusterEventWithHint {
|
||||||
|
return []framework.ClusterEventWithHint{
|
||||||
|
{Event: framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeTaint}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPlugin returns a plugin factory with specified Plugin.
|
||||||
|
func newPlugin(plugin framework.Plugin) frameworkruntime.PluginFactory {
|
||||||
|
return func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) {
|
||||||
|
return plugin, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateNodeEvent(t *testing.T) {
|
||||||
|
testContext := testutils.InitTestAPIServer(t, "test-event", nil)
|
||||||
|
|
||||||
|
taints := []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectNoSchedule}}
|
||||||
|
nodeWrapper := st.MakeNode().Name("node-0").Label("kubernetes.io/hostname", "node-0").Taints(taints).Obj()
|
||||||
|
podWrapper := testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testContext.NS.Name})
|
||||||
|
fooPlugin := &fooPlugin{}
|
||||||
|
|
||||||
|
registry := frameworkruntime.Registry{
|
||||||
|
fooPlugin.Name(): newPlugin(fooPlugin),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup plugins for testing.
|
||||||
|
cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{
|
||||||
|
Profiles: []configv1.KubeSchedulerProfile{{
|
||||||
|
SchedulerName: ptr.To[string](v1.DefaultSchedulerName),
|
||||||
|
Plugins: &configv1.Plugins{
|
||||||
|
Filter: configv1.PluginSet{
|
||||||
|
Enabled: []configv1.Plugin{
|
||||||
|
{Name: fooPlugin.Name()},
|
||||||
|
},
|
||||||
|
Disabled: []configv1.Plugin{
|
||||||
|
{Name: "*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 0,
|
||||||
|
scheduler.WithProfiles(cfg.Profiles...),
|
||||||
|
scheduler.WithFrameworkOutOfTreeRegistry(registry),
|
||||||
|
)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
node, err := testutils.CreateNode(testCtx.ClientSet, nodeWrapper)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating node error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, err := testutils.CreatePausePod(testCtx.ClientSet, podWrapper)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating pod error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil {
|
||||||
|
t.Fatalf("Pod %v got scheduled: %v", pod.Name, err)
|
||||||
|
}
|
||||||
|
node, err = testCtx.ClientSet.CoreV1().Nodes().Get(testCtx.Ctx, node.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error while getting a node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update node label and node taints
|
||||||
|
node.Labels["foo"] = "bar"
|
||||||
|
node.Spec.Taints = nil
|
||||||
|
|
||||||
|
_, err = testCtx.ClientSet.CoreV1().Nodes().Update(testCtx.Ctx, node, metav1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error updating the node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil {
|
||||||
|
t.Errorf("Pod %v was not scheduled: %v", pod.Name, err)
|
||||||
|
}
|
||||||
|
}
|
27
test/integration/scheduler/eventhandler/main_test.go
Normal file
27
test/integration/scheduler/eventhandler/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 eventhandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
framework.EtcdMain(m.Run)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user