mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-14 06:15:45 +00:00
feat: implement node affinity priority as score plugin
+ Add DefaultNormalizeScore function + Implement NodeAffinity as score plugin
This commit is contained in:
parent
bb7bad49f5
commit
75872b8e79
@ -14,7 +14,6 @@ go_library(
|
|||||||
"least_requested.go",
|
"least_requested.go",
|
||||||
"metadata.go",
|
"metadata.go",
|
||||||
"most_requested.go",
|
"most_requested.go",
|
||||||
"node_affinity.go",
|
|
||||||
"node_prefer_avoid_pods.go",
|
"node_prefer_avoid_pods.go",
|
||||||
"priorities.go",
|
"priorities.go",
|
||||||
"reduce.go",
|
"reduce.go",
|
||||||
@ -55,7 +54,6 @@ go_test(
|
|||||||
"least_requested_test.go",
|
"least_requested_test.go",
|
||||||
"metadata_test.go",
|
"metadata_test.go",
|
||||||
"most_requested_test.go",
|
"most_requested_test.go",
|
||||||
"node_affinity_test.go",
|
|
||||||
"node_prefer_avoid_pods_test.go",
|
"node_prefer_avoid_pods_test.go",
|
||||||
"requested_to_capacity_ratio_test.go",
|
"requested_to_capacity_ratio_test.go",
|
||||||
"resource_limits_test.go",
|
"resource_limits_test.go",
|
||||||
|
@ -57,7 +57,6 @@ func NewMetadataFactory(
|
|||||||
// priorityMetadata is a type that is passed as metadata for priority functions
|
// priorityMetadata is a type that is passed as metadata for priority functions
|
||||||
type priorityMetadata struct {
|
type priorityMetadata struct {
|
||||||
podLimits *schedulernodeinfo.Resource
|
podLimits *schedulernodeinfo.Resource
|
||||||
affinity *v1.Affinity
|
|
||||||
podSelector labels.Selector
|
podSelector labels.Selector
|
||||||
controllerRef *metav1.OwnerReference
|
controllerRef *metav1.OwnerReference
|
||||||
podFirstServiceSelector labels.Selector
|
podFirstServiceSelector labels.Selector
|
||||||
@ -87,7 +86,6 @@ func (pmf *MetadataFactory) PriorityMetadata(
|
|||||||
}
|
}
|
||||||
return &priorityMetadata{
|
return &priorityMetadata{
|
||||||
podLimits: getResourceLimits(pod),
|
podLimits: getResourceLimits(pod),
|
||||||
affinity: pod.Spec.Affinity,
|
|
||||||
podSelector: getSelector(pod, pmf.serviceLister, pmf.controllerLister, pmf.replicaSetLister, pmf.statefulSetLister),
|
podSelector: getSelector(pod, pmf.serviceLister, pmf.controllerLister, pmf.replicaSetLister, pmf.statefulSetLister),
|
||||||
controllerRef: metav1.GetControllerOf(pod),
|
controllerRef: metav1.GetControllerOf(pod),
|
||||||
podFirstServiceSelector: getFirstServiceSelector(pod, pmf.serviceLister),
|
podFirstServiceSelector: getFirstServiceSelector(pod, pmf.serviceLister),
|
||||||
|
@ -139,7 +139,6 @@ func TestPriorityMetadata(t *testing.T) {
|
|||||||
pod: podWithTolerationsAndAffinity,
|
pod: podWithTolerationsAndAffinity,
|
||||||
expected: &priorityMetadata{
|
expected: &priorityMetadata{
|
||||||
podLimits: nonPodLimits,
|
podLimits: nonPodLimits,
|
||||||
affinity: podAffinity,
|
|
||||||
podSelector: labels.NewSelector(),
|
podSelector: labels.NewSelector(),
|
||||||
},
|
},
|
||||||
name: "Produce a priorityMetadata with default requests",
|
name: "Produce a priorityMetadata with default requests",
|
||||||
@ -148,7 +147,6 @@ func TestPriorityMetadata(t *testing.T) {
|
|||||||
pod: podWithTolerationsAndRequests,
|
pod: podWithTolerationsAndRequests,
|
||||||
expected: &priorityMetadata{
|
expected: &priorityMetadata{
|
||||||
podLimits: nonPodLimits,
|
podLimits: nonPodLimits,
|
||||||
affinity: nil,
|
|
||||||
podSelector: labels.NewSelector(),
|
podSelector: labels.NewSelector(),
|
||||||
},
|
},
|
||||||
name: "Produce a priorityMetadata with tolerations and requests",
|
name: "Produce a priorityMetadata with tolerations and requests",
|
||||||
@ -157,7 +155,6 @@ func TestPriorityMetadata(t *testing.T) {
|
|||||||
pod: podWithAffinityAndRequests,
|
pod: podWithAffinityAndRequests,
|
||||||
expected: &priorityMetadata{
|
expected: &priorityMetadata{
|
||||||
podLimits: specifiedPodLimits,
|
podLimits: specifiedPodLimits,
|
||||||
affinity: podAffinity,
|
|
||||||
podSelector: labels.NewSelector(),
|
podSelector: labels.NewSelector(),
|
||||||
},
|
},
|
||||||
name: "Produce a priorityMetadata with affinity and requests",
|
name: "Produce a priorityMetadata with affinity and requests",
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 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 priorities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
|
||||||
schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CalculateNodeAffinityPriorityMap prioritizes nodes according to node affinity scheduling preferences
|
|
||||||
// indicated in PreferredDuringSchedulingIgnoredDuringExecution. Each time a node matches a preferredSchedulingTerm,
|
|
||||||
// it will get an add of preferredSchedulingTerm.Weight. Thus, the more preferredSchedulingTerms
|
|
||||||
// the node satisfies and the more the preferredSchedulingTerm that is satisfied weights, the higher
|
|
||||||
// score the node gets.
|
|
||||||
func CalculateNodeAffinityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulernodeinfo.NodeInfo) (framework.NodeScore, error) {
|
|
||||||
node := nodeInfo.Node()
|
|
||||||
if node == nil {
|
|
||||||
return framework.NodeScore{}, fmt.Errorf("node not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// default is the podspec.
|
|
||||||
affinity := pod.Spec.Affinity
|
|
||||||
if priorityMeta, ok := meta.(*priorityMetadata); ok {
|
|
||||||
// We were able to parse metadata, use affinity from there.
|
|
||||||
affinity = priorityMeta.affinity
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int32
|
|
||||||
// A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects.
|
|
||||||
// An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an
|
|
||||||
// empty PreferredSchedulingTerm matches all objects.
|
|
||||||
if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
|
||||||
// Match PreferredDuringSchedulingIgnoredDuringExecution term by term.
|
|
||||||
for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
|
|
||||||
preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i]
|
|
||||||
if preferredSchedulingTerm.Weight == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Avoid computing it for all nodes if this becomes a performance problem.
|
|
||||||
nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions)
|
|
||||||
if err != nil {
|
|
||||||
return framework.NodeScore{}, err
|
|
||||||
}
|
|
||||||
if nodeSelector.Matches(labels.Set(node.Labels)) {
|
|
||||||
count += preferredSchedulingTerm.Weight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return framework.NodeScore{
|
|
||||||
Name: node.Name,
|
|
||||||
Score: int64(count),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateNodeAffinityPriorityReduce is a reduce function for node affinity priority calculation.
|
|
||||||
var CalculateNodeAffinityPriorityReduce = NormalizeReduce(framework.MaxNodeScore, false)
|
|
@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 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 priorities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
|
||||||
nodeinfosnapshot "k8s.io/kubernetes/pkg/scheduler/nodeinfo/snapshot"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNodeAffinityPriority(t *testing.T) {
|
|
||||||
label1 := map[string]string{"foo": "bar"}
|
|
||||||
label2 := map[string]string{"key": "value"}
|
|
||||||
label3 := map[string]string{"az": "az1"}
|
|
||||||
label4 := map[string]string{"abc": "az11", "def": "az22"}
|
|
||||||
label5 := map[string]string{"foo": "bar", "key": "value", "az": "az1"}
|
|
||||||
|
|
||||||
affinity1 := &v1.Affinity{
|
|
||||||
NodeAffinity: &v1.NodeAffinity{
|
|
||||||
PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{{
|
|
||||||
Weight: 2,
|
|
||||||
Preference: v1.NodeSelectorTerm{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{{
|
|
||||||
Key: "foo",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"bar"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
affinity2 := &v1.Affinity{
|
|
||||||
NodeAffinity: &v1.NodeAffinity{
|
|
||||||
PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
|
|
||||||
{
|
|
||||||
Weight: 2,
|
|
||||||
Preference: v1.NodeSelectorTerm{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Weight: 4,
|
|
||||||
Preference: v1.NodeSelectorTerm{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: "key",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"value"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Weight: 5,
|
|
||||||
Preference: v1.NodeSelectorTerm{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "key",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"value"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "az",
|
|
||||||
Operator: v1.NodeSelectorOpIn,
|
|
||||||
Values: []string{"az1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
pod *v1.Pod
|
|
||||||
nodes []*v1.Node
|
|
||||||
expectedList framework.NodeScoreList
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
pod: &v1.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Annotations: map[string]string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}},
|
|
||||||
},
|
|
||||||
expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
|
|
||||||
name: "all machines are same priority as NodeAffinity is nil",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pod: &v1.Pod{
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Affinity: affinity1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label4}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}},
|
|
||||||
},
|
|
||||||
expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
|
|
||||||
name: "no machine macthes preferred scheduling requirements in NodeAffinity of pod so all machines' priority is zero",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pod: &v1.Pod{
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Affinity: affinity1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}},
|
|
||||||
},
|
|
||||||
expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
|
|
||||||
name: "only machine1 matches the preferred scheduling requirements of pod",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pod: &v1.Pod{
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Affinity: affinity2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: label5}},
|
|
||||||
{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}},
|
|
||||||
},
|
|
||||||
expectedList: []framework.NodeScore{{Name: "machine1", Score: 18}, {Name: "machine5", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 36}},
|
|
||||||
name: "all machines matches the preferred scheduling requirements of pod but with different priorities ",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
snapshot := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(nil, test.nodes))
|
|
||||||
list, err := runMapReducePriority(CalculateNodeAffinityPriorityMap, CalculateNodeAffinityPriorityReduce, nil, test.pod, snapshot, test.nodes)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(test.expectedList, list) {
|
|
||||||
t.Errorf("expected %#v, \ngot %#v", test.expectedList, list)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -83,7 +83,7 @@ func init() {
|
|||||||
scheduler.RegisterPriorityMapReduceFunction(priorities.NodePreferAvoidPodsPriority, priorities.CalculateNodePreferAvoidPodsPriorityMap, nil, 10000)
|
scheduler.RegisterPriorityMapReduceFunction(priorities.NodePreferAvoidPodsPriority, priorities.CalculateNodePreferAvoidPodsPriorityMap, nil, 10000)
|
||||||
|
|
||||||
// Prioritizes nodes that have labels matching NodeAffinity
|
// Prioritizes nodes that have labels matching NodeAffinity
|
||||||
scheduler.RegisterPriorityMapReduceFunction(priorities.NodeAffinityPriority, priorities.CalculateNodeAffinityPriorityMap, priorities.CalculateNodeAffinityPriorityReduce, 1)
|
scheduler.RegisterPriorityMapReduceFunction(priorities.NodeAffinityPriority, nil, nil, 1)
|
||||||
|
|
||||||
// Prioritizes nodes that marked with taint which pod can tolerate.
|
// Prioritizes nodes that marked with taint which pod can tolerate.
|
||||||
scheduler.RegisterPriorityMapReduceFunction(priorities.TaintTolerationPriority, nil, nil, 1)
|
scheduler.RegisterPriorityMapReduceFunction(priorities.TaintTolerationPriority, nil, nil, 1)
|
||||||
|
@ -47,6 +47,7 @@ filegroup(
|
|||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/defaultpodtopologyspread:all-srcs",
|
"//pkg/scheduler/framework/plugins/defaultpodtopologyspread:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/examples:all-srcs",
|
"//pkg/scheduler/framework/plugins/examples:all-srcs",
|
||||||
|
"//pkg/scheduler/framework/plugins/helper:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/imagelocality:all-srcs",
|
"//pkg/scheduler/framework/plugins/imagelocality:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/interpodaffinity:all-srcs",
|
"//pkg/scheduler/framework/plugins/interpodaffinity:all-srcs",
|
||||||
"//pkg/scheduler/framework/plugins/migration:all-srcs",
|
"//pkg/scheduler/framework/plugins/migration:all-srcs",
|
||||||
|
30
pkg/scheduler/framework/plugins/helper/BUILD
Normal file
30
pkg/scheduler/framework/plugins/helper/BUILD
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["normalize_score.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//pkg/scheduler/framework/v1alpha1:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["normalize_score_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = ["//pkg/scheduler/framework/v1alpha1: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"],
|
||||||
|
)
|
54
pkg/scheduler/framework/plugins/helper/normalize_score.go
Normal file
54
pkg/scheduler/framework/plugins/helper/normalize_score.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
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 helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultNormalizeScore generates a Normalize Score function that can normalize the
|
||||||
|
// scores to [0, maxPriority]. If reverse is set to true, it reverses the scores by
|
||||||
|
// subtracting it from maxPriority.
|
||||||
|
func DefaultNormalizeScore(maxPriority int64, reverse bool, scores framework.NodeScoreList) *framework.Status {
|
||||||
|
var maxCount int64
|
||||||
|
for i := range scores {
|
||||||
|
if scores[i].Score > maxCount {
|
||||||
|
maxCount = scores[i].Score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxCount == 0 {
|
||||||
|
if reverse {
|
||||||
|
for i := range scores {
|
||||||
|
scores[i].Score = maxPriority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range scores {
|
||||||
|
score := scores[i].Score
|
||||||
|
|
||||||
|
score = maxPriority * score / maxCount
|
||||||
|
if reverse {
|
||||||
|
score = maxPriority - score
|
||||||
|
}
|
||||||
|
|
||||||
|
scores[i].Score = score
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
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 helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefaultNormalizeScore(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
reverse bool
|
||||||
|
scores []int64
|
||||||
|
expectedScores []int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scores: []int64{1, 2, 3, 4},
|
||||||
|
expectedScores: []int64{25, 50, 75, 100},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reverse: true,
|
||||||
|
scores: []int64{1, 2, 3, 4},
|
||||||
|
expectedScores: []int64{75, 50, 25, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scores: []int64{1000, 10, 20, 30},
|
||||||
|
expectedScores: []int64{100, 1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reverse: true,
|
||||||
|
scores: []int64{1000, 10, 20, 30},
|
||||||
|
expectedScores: []int64{0, 99, 98, 97},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scores: []int64{1, 1, 1, 1},
|
||||||
|
expectedScores: []int64{100, 100, 100, 100},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scores: []int64{1000, 1, 1, 1},
|
||||||
|
expectedScores: []int64{100, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
scores := framework.NodeScoreList{}
|
||||||
|
for _, score := range test.scores {
|
||||||
|
scores = append(scores, framework.NodeScore{Score: score})
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedScores := framework.NodeScoreList{}
|
||||||
|
for _, score := range test.expectedScores {
|
||||||
|
expectedScores = append(expectedScores, framework.NodeScore{Score: score})
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultNormalizeScore(framework.MaxNodeScore, test.reverse, scores)
|
||||||
|
if !reflect.DeepEqual(scores, expectedScores) {
|
||||||
|
t.Errorf("test %d, expected %v, got %v", i, expectedScores, scores)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,14 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity",
|
importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/apis/core/v1/helper:go_default_library",
|
||||||
"//pkg/scheduler/algorithm/predicates:go_default_library",
|
"//pkg/scheduler/algorithm/predicates:go_default_library",
|
||||||
"//pkg/scheduler/algorithm/priorities:go_default_library",
|
"//pkg/scheduler/framework/plugins/helper:go_default_library",
|
||||||
"//pkg/scheduler/framework/plugins/migration:go_default_library",
|
"//pkg/scheduler/framework/plugins/migration:go_default_library",
|
||||||
"//pkg/scheduler/framework/v1alpha1:go_default_library",
|
"//pkg/scheduler/framework/v1alpha1:go_default_library",
|
||||||
"//pkg/scheduler/nodeinfo:go_default_library",
|
"//pkg/scheduler/nodeinfo:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -21,9 +21,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/algorithm/priorities"
|
pluginhelper "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration"
|
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration"
|
||||||
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
"k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
||||||
@ -58,16 +60,43 @@ func (pl *NodeAffinity) Score(ctx context.Context, state *framework.CycleState,
|
|||||||
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
|
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := migration.PriorityMetadata(state)
|
node := nodeInfo.Node()
|
||||||
s, err := priorities.CalculateNodeAffinityPriorityMap(pod, meta, nodeInfo)
|
if node == nil {
|
||||||
return s.Score, migration.ErrorToFrameworkStatus(err)
|
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
affinity := pod.Spec.Affinity
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
// A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects.
|
||||||
|
// An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an
|
||||||
|
// empty PreferredSchedulingTerm matches all objects.
|
||||||
|
if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
|
// Match PreferredDuringSchedulingIgnoredDuringExecution term by term.
|
||||||
|
for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
|
||||||
|
preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i]
|
||||||
|
if preferredSchedulingTerm.Weight == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Avoid computing it for all nodes if this becomes a performance problem.
|
||||||
|
nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions)
|
||||||
|
if err != nil {
|
||||||
|
return 0, framework.NewStatus(framework.Error, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeSelector.Matches(labels.Set(node.Labels)) {
|
||||||
|
count += int64(preferredSchedulingTerm.Weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeScore invoked after scoring all nodes.
|
// NormalizeScore invoked after scoring all nodes.
|
||||||
func (pl *NodeAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
|
func (pl *NodeAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
|
||||||
// Note that CalculateNodeAffinityPriorityReduce doesn't use priority metadata, hence passing nil here.
|
return pluginhelper.DefaultNormalizeScore(framework.MaxNodeScore, false, scores)
|
||||||
err := priorities.CalculateNodeAffinityPriorityReduce(pod, nil, pl.handle.SnapshotSharedLister(), scores)
|
|
||||||
return migration.ErrorToFrameworkStatus(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScoreExtensions of the Score plugin.
|
// ScoreExtensions of the Score plugin.
|
||||||
|
Loading…
Reference in New Issue
Block a user