mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
migrate EvenPodsSpread Predicate to Filter plugin
This commit is contained in:
parent
de9a7d863d
commit
64ff958e69
@ -18,6 +18,7 @@ go_library(
|
|||||||
"//pkg/scheduler/framework/plugins/nodepreferavoidpods:go_default_library",
|
"//pkg/scheduler/framework/plugins/nodepreferavoidpods:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/noderesources:go_default_library",
|
"//pkg/scheduler/framework/plugins/noderesources:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/nodevolumelimits:go_default_library",
|
"//pkg/scheduler/framework/plugins/nodevolumelimits:go_default_library",
|
||||||
|
"//pkg/scheduler/framework/plugins/podtopologyspread:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/tainttoleration:go_default_library",
|
"//pkg/scheduler/framework/plugins/tainttoleration:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/volumebinding:go_default_library",
|
"//pkg/scheduler/framework/plugins/volumebinding:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/volumerestrictions:go_default_library",
|
"//pkg/scheduler/framework/plugins/volumerestrictions:go_default_library",
|
||||||
@ -52,6 +53,7 @@ filegroup(
|
|||||||
"//pkg/scheduler/framework/plugins/nodepreferavoidpods:all-srcs",
|
"//pkg/scheduler/framework/plugins/nodepreferavoidpods:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/noderesources:all-srcs",
|
"//pkg/scheduler/framework/plugins/noderesources:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/nodevolumelimits:all-srcs",
|
"//pkg/scheduler/framework/plugins/nodevolumelimits:all-srcs",
|
||||||
|
"//pkg/scheduler/framework/plugins/podtopologyspread:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/tainttoleration:all-srcs",
|
"//pkg/scheduler/framework/plugins/tainttoleration:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/volumebinding:all-srcs",
|
"//pkg/scheduler/framework/plugins/volumebinding:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/volumerestrictions:all-srcs",
|
"//pkg/scheduler/framework/plugins/volumerestrictions:all-srcs",
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodepreferavoidpods"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodepreferavoidpods"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodevolumelimits"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodevolumelimits"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions"
|
||||||
@ -73,6 +74,7 @@ func NewDefaultRegistry(args *RegistryArgs) framework.Registry {
|
|||||||
nodeports.Name: nodeports.New,
|
nodeports.Name: nodeports.New,
|
||||||
nodepreferavoidpods.Name: nodepreferavoidpods.New,
|
nodepreferavoidpods.Name: nodepreferavoidpods.New,
|
||||||
nodeaffinity.Name: nodeaffinity.New,
|
nodeaffinity.Name: nodeaffinity.New,
|
||||||
|
podtopologyspread.Name: podtopologyspread.New,
|
||||||
volumebinding.Name: func(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
|
volumebinding.Name: func(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
|
||||||
return volumebinding.NewFromVolumeBinder(args.VolumeBinder), nil
|
return volumebinding.NewFromVolumeBinder(args.VolumeBinder), nil
|
||||||
},
|
},
|
||||||
@ -113,6 +115,7 @@ func NewDefaultConfigProducerRegistry() *ConfigProducerRegistry {
|
|||||||
PredicateToConfigProducer: make(map[string]ConfigProducer),
|
PredicateToConfigProducer: make(map[string]ConfigProducer),
|
||||||
PriorityToConfigProducer: make(map[string]ConfigProducer),
|
PriorityToConfigProducer: make(map[string]ConfigProducer),
|
||||||
}
|
}
|
||||||
|
// Register Predicates.
|
||||||
registry.RegisterPredicate(predicates.GeneralPred,
|
registry.RegisterPredicate(predicates.GeneralPred,
|
||||||
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
// GeneralPredicate is a combination of predicates.
|
// GeneralPredicate is a combination of predicates.
|
||||||
@ -172,25 +175,27 @@ func NewDefaultConfigProducerRegistry() *ConfigProducerRegistry {
|
|||||||
plugins.Filter = appendToPluginSet(plugins.Filter, interpodaffinity.Name, nil)
|
plugins.Filter = appendToPluginSet(plugins.Filter, interpodaffinity.Name, nil)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
registry.RegisterPredicate(predicates.EvenPodsSpreadPred,
|
||||||
|
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
|
plugins.Filter = appendToPluginSet(plugins.Filter, podtopologyspread.Name, nil)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
// Register Priorities.
|
||||||
registry.RegisterPriority(priorities.TaintTolerationPriority,
|
registry.RegisterPriority(priorities.TaintTolerationPriority,
|
||||||
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
plugins.Score = appendToPluginSet(plugins.Score, tainttoleration.Name, &args.Weight)
|
plugins.Score = appendToPluginSet(plugins.Score, tainttoleration.Name, &args.Weight)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
registry.RegisterPriority(priorities.NodeAffinityPriority,
|
registry.RegisterPriority(priorities.NodeAffinityPriority,
|
||||||
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
plugins.Score = appendToPluginSet(plugins.Score, nodeaffinity.Name, &args.Weight)
|
plugins.Score = appendToPluginSet(plugins.Score, nodeaffinity.Name, &args.Weight)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
registry.RegisterPriority(priorities.ImageLocalityPriority,
|
registry.RegisterPriority(priorities.ImageLocalityPriority,
|
||||||
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
plugins.Score = appendToPluginSet(plugins.Score, imagelocality.Name, &args.Weight)
|
plugins.Score = appendToPluginSet(plugins.Score, imagelocality.Name, &args.Weight)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
registry.RegisterPriority(priorities.NodePreferAvoidPodsPriority,
|
registry.RegisterPriority(priorities.NodePreferAvoidPodsPriority,
|
||||||
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
|
||||||
plugins.Score = appendToPluginSet(plugins.Score, nodepreferavoidpods.Name, &args.Weight)
|
plugins.Score = appendToPluginSet(plugins.Score, nodepreferavoidpods.Name, &args.Weight)
|
||||||
|
44
pkg/scheduler/framework/plugins/podtopologyspread/BUILD
Normal file
44
pkg/scheduler/framework/plugins/podtopologyspread/BUILD
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["pod_topology_spread.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/scheduler/algorithm/predicates:go_default_library",
|
||||||
|
"//pkg/scheduler/framework/plugins/migration:go_default_library",
|
||||||
|
"//pkg/scheduler/framework/v1alpha1:go_default_library",
|
||||||
|
"//pkg/scheduler/nodeinfo:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["pod_topology_spread_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/scheduler/algorithm/predicates:go_default_library",
|
||||||
|
"//pkg/scheduler/framework/plugins/migration:go_default_library",
|
||||||
|
"//pkg/scheduler/framework/v1alpha1:go_default_library",
|
||||||
|
"//pkg/scheduler/nodeinfo:go_default_library",
|
||||||
|
"//pkg/scheduler/testing:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 podtopologyspread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration"
|
||||||
|
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PodTopologySpread is a plugin that ensures pod's topologySpreadConstraints is satisfied.
|
||||||
|
type PodTopologySpread struct{}
|
||||||
|
|
||||||
|
var _ framework.FilterPlugin = &PodTopologySpread{}
|
||||||
|
|
||||||
|
// Name is the name of the plugin used in the plugin registry and configurations.
|
||||||
|
const Name = "PodTopologySpread"
|
||||||
|
|
||||||
|
// Name returns name of the plugin. It is used in logs, etc.
|
||||||
|
func (pl *PodTopologySpread) Name() string {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter invoked at the filter extension point.
|
||||||
|
func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *nodeinfo.NodeInfo) *framework.Status {
|
||||||
|
meta, ok := migration.PredicateMetadata(cycleState).(predicates.PredicateMetadata)
|
||||||
|
if !ok {
|
||||||
|
return migration.ErrorToFrameworkStatus(fmt.Errorf("%+v convert to predicates.PredicateMetadata error", cycleState))
|
||||||
|
}
|
||||||
|
_, reasons, err := predicates.EvenPodsSpreadPredicate(pod, meta, nodeInfo)
|
||||||
|
return migration.PredicateResultToFrameworkStatus(reasons, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new plugin and returns it.
|
||||||
|
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
|
||||||
|
return &PodTopologySpread{}, nil
|
||||||
|
}
|
@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 podtopologyspread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
||||||
|
schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
||||||
|
st "k8s.io/kubernetes/pkg/scheduler/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hardSpread = v1.DoNotSchedule
|
||||||
|
|
||||||
|
func TestPodTopologySpreadFilter_SingleConstraint(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *v1.Pod
|
||||||
|
nodes []*v1.Node
|
||||||
|
existingPods []*v1.Pod
|
||||||
|
fits map[string]bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no existing pods",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no existing pods, incoming pod doesn't match itself",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "zone", hardSpread, st.MakeLabelSelector().Exists("bar").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing pods with mis-matched namespace doens't count",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Namespace("ns2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods spread across zones as 3/3, all nodes fit",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// TODO(Huang-Wei): maybe document this to remind users that typos on node labels
|
||||||
|
// can cause unexpected behavior
|
||||||
|
name: "pods spread across zones as 1/2 due to absence of label 'zone' on node-b",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zon", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods spread across nodes as 2/1/0/3, only node-x fits",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods spread across nodes as 2/1/0/3, maxSkew is 2, node-b and node-x fit",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
|
||||||
|
2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// not a desired case, but it can happen
|
||||||
|
// TODO(Huang-Wei): document this "pod-not-match-itself" case
|
||||||
|
// in this case, placement of the new pod doesn't change pod distribution of the cluster
|
||||||
|
// as the incoming pod doesn't have label "foo"
|
||||||
|
name: "pods spread across nodes as 2/1/0/3, but pod doesn't match itself",
|
||||||
|
pod: st.MakePod().Name("p").Label("bar", "").SpreadConstraint(
|
||||||
|
1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
|
||||||
|
).Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// only node-a and node-y are considered, so pods spread as 2/~1~/~0~/3
|
||||||
|
// ps: '~num~' is a markdown symbol to denote a crossline through 'num'
|
||||||
|
// but in this unit test, we don't run NodeAffinityPredicate, so node-b and node-x are
|
||||||
|
// still expected to be fits;
|
||||||
|
// the fact that node-a fits can prove the underlying logic works
|
||||||
|
name: "incoming pod has nodeAffinity, pods spread as 2/~1~/~0~/3, hence node-a fits",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").
|
||||||
|
NodeAffinityIn("node", []string{"node-a", "node-y"}).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true, // in real case, it's false
|
||||||
|
"node-x": true, // in real case, it's false
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes)
|
||||||
|
meta := predicates.GetPredicateMetadata(tt.pod, nodeInfoMap)
|
||||||
|
state := v1alpha1.NewCycleState()
|
||||||
|
state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta})
|
||||||
|
plugin, _ := New(nil, nil)
|
||||||
|
for _, node := range tt.nodes {
|
||||||
|
status := plugin.(*PodTopologySpread).Filter(context.Background(), state, tt.pod, nodeInfoMap[node.Name])
|
||||||
|
if status.IsSuccess() != tt.fits[node.Name] {
|
||||||
|
t.Errorf("[%s]: expected %v got %v", node.Name, tt.fits[node.Name], status.IsSuccess())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPodTopologySpreadFilter_MultipleConstraints(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *v1.Pod
|
||||||
|
nodes []*v1.Node
|
||||||
|
existingPods []*v1.Pod
|
||||||
|
fits map[string]bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// 1. to fulfil "zone" constraint, incoming pod can be placed on any zone (hence any node)
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-x
|
||||||
|
// intersection of (1) and (2) returns node-x
|
||||||
|
name: "two constraints on zone and node, spreads = [3/3, 2/1/0/3]",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1. to fulfil "zone" constraint, incoming pod can be placed on zone1 (node-a or node-b)
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-x
|
||||||
|
// intersection of (1) and (2) returns no node
|
||||||
|
name: "two constraints on zone and node, spreads = [3/4, 2/1/0/4]",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1. to fulfil "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y)
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-b or node-x
|
||||||
|
// intersection of (1) and (2) returns node-x
|
||||||
|
name: "constraints hold different labelSelectors, spreads = [1/0, 1/0/0/1]",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("bar", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": true,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1. to fulfil "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y)
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-a or node-b
|
||||||
|
// intersection of (1) and (2) returns no node
|
||||||
|
name: "constraints hold different labelSelectors, spreads = [1/0, 0/0/1/1]",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-x1").Node("node-x").Label("bar", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("bar", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": false,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1. to fulfil "zone" constraint, incoming pod can be placed on zone1 (node-a or node-b)
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-b or node-x
|
||||||
|
// intersection of (1) and (2) returns node-b
|
||||||
|
name: "constraints hold different labelSelectors, spreads = [2/3, 1/0/0/1]",
|
||||||
|
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": false,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1. pod doesn't match itself on "zone" constraint, so it can be put onto any zone
|
||||||
|
// 2. to fulfil "node" constraint, incoming pod can be placed on node-a or node-b
|
||||||
|
// intersection of (1) and (2) returns node-a and node-b
|
||||||
|
name: "constraints hold different labelSelectors but pod doesn't match itself on 'zone' constraint",
|
||||||
|
pod: st.MakePod().Name("p").Label("bar", "").
|
||||||
|
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
|
||||||
|
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
|
||||||
|
Obj(),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
|
||||||
|
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
|
||||||
|
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
|
||||||
|
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
|
||||||
|
},
|
||||||
|
existingPods: []*v1.Pod{
|
||||||
|
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
|
||||||
|
st.MakePod().Name("p-x1").Node("node-x").Label("bar", "").Obj(),
|
||||||
|
st.MakePod().Name("p-y1").Node("node-y").Label("bar", "").Obj(),
|
||||||
|
},
|
||||||
|
fits: map[string]bool{
|
||||||
|
"node-a": true,
|
||||||
|
"node-b": true,
|
||||||
|
"node-x": false,
|
||||||
|
"node-y": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes)
|
||||||
|
meta := predicates.GetPredicateMetadata(tt.pod, nodeInfoMap)
|
||||||
|
state := v1alpha1.NewCycleState()
|
||||||
|
state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta})
|
||||||
|
plugin, _ := New(nil, nil)
|
||||||
|
for _, node := range tt.nodes {
|
||||||
|
status := plugin.(*PodTopologySpread).Filter(context.Background(), state, tt.pod, nodeInfoMap[node.Name])
|
||||||
|
if status.IsSuccess() != tt.fits[node.Name] {
|
||||||
|
t.Errorf("[%s]: expected %v got %v", node.Name, tt.fits[node.Name], status.IsSuccess())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user