diff --git a/pkg/scheduler/api/compatibility/compatibility_test.go b/pkg/scheduler/api/compatibility/compatibility_test.go index 81680b9e861..4d617c43cca 100644 --- a/pkg/scheduler/api/compatibility/compatibility_test.go +++ b/pkg/scheduler/api/compatibility/compatibility_test.go @@ -65,7 +65,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { ] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsPorts", "TestServiceAffinity", "TestLabelsPresence", @@ -79,6 +78,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPlugins: map[string][]kubeschedulerconfig.Plugin{ "FilterPlugin": { {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, }, }, @@ -109,7 +109,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }`, wantPredicates: sets.NewString( "PodFitsHostPorts", - "PodFitsResources", "TestServiceAffinity", "TestLabelsPresence", ), @@ -125,6 +124,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, }, }, @@ -160,7 +160,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { ] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "MaxEBSVolumeCount", @@ -183,6 +182,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, }, }, @@ -222,7 +222,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { ] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -247,6 +246,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, }, @@ -291,7 +291,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { ] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -319,6 +318,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, }, @@ -372,7 +372,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -400,6 +399,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, }, @@ -465,7 +465,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -494,6 +493,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, }, @@ -560,7 +560,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -589,6 +588,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -660,7 +660,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -690,6 +689,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -773,7 +773,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -804,6 +803,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -888,7 +888,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -920,6 +919,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -1003,7 +1003,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -1036,6 +1035,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -1123,7 +1123,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }] }`, wantPredicates: sets.NewString( - "PodFitsResources", "PodFitsHostPorts", "NoVolumeZoneConflict", "CheckNodeMemoryPressure", @@ -1156,6 +1155,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "FilterPlugin": { {Name: "NodeName"}, {Name: "NodeAffinity"}, + {Name: "NodeResources"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "VolumeBinding"}, @@ -1185,6 +1185,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { filterToPredicateMap := map[string]string{ "TaintToleration": "PodToleratesNodeTaints", "NodeName": "HostName", + "NodeResources": "PodFitsResources", "NodeAffinity": "MatchNodeSelector", "VolumeBinding": "CheckVolumeBinding", "VolumeRestrictions": "NoDiskConflict", diff --git a/pkg/scheduler/framework/plugins/BUILD b/pkg/scheduler/framework/plugins/BUILD index 22d5d18877b..1b549c556e4 100644 --- a/pkg/scheduler/framework/plugins/BUILD +++ b/pkg/scheduler/framework/plugins/BUILD @@ -12,6 +12,7 @@ go_library( "//pkg/scheduler/apis/config:go_default_library", "//pkg/scheduler/framework/plugins/nodeaffinity:go_default_library", "//pkg/scheduler/framework/plugins/nodename:go_default_library", + "//pkg/scheduler/framework/plugins/noderesources:go_default_library", "//pkg/scheduler/framework/plugins/tainttoleration:go_default_library", "//pkg/scheduler/framework/plugins/volumebinding:go_default_library", "//pkg/scheduler/framework/plugins/volumerestrictions:go_default_library", @@ -39,6 +40,7 @@ filegroup( "//pkg/scheduler/framework/plugins/migration:all-srcs", "//pkg/scheduler/framework/plugins/nodeaffinity:all-srcs", "//pkg/scheduler/framework/plugins/nodename:all-srcs", + "//pkg/scheduler/framework/plugins/noderesources:all-srcs", "//pkg/scheduler/framework/plugins/tainttoleration:all-srcs", "//pkg/scheduler/framework/plugins/volumebinding:all-srcs", "//pkg/scheduler/framework/plugins/volumerestrictions:all-srcs", diff --git a/pkg/scheduler/framework/plugins/default_registry.go b/pkg/scheduler/framework/plugins/default_registry.go index da565fd76e2..b99e459263a 100644 --- a/pkg/scheduler/framework/plugins/default_registry.go +++ b/pkg/scheduler/framework/plugins/default_registry.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodename" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions" @@ -56,6 +57,7 @@ type RegistryArgs struct { func NewDefaultRegistry(args *RegistryArgs) framework.Registry { return framework.Registry{ tainttoleration.Name: tainttoleration.New, + noderesources.Name: noderesources.New, nodename.Name: nodename.New, nodeaffinity.Name: nodeaffinity.New, volumebinding.Name: func(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) { @@ -94,6 +96,11 @@ func NewDefaultConfigProducerRegistry() *ConfigProducerRegistry { plugins.Filter = appendToPluginSet(plugins.Filter, tainttoleration.Name, nil) return }) + registry.RegisterPredicate(predicates.PodFitsResourcesPred, + func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) { + plugins.Filter = appendToPluginSet(plugins.Filter, noderesources.Name, nil) + return + }) registry.RegisterPredicate(predicates.HostNamePred, func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) { plugins.Filter = appendToPluginSet(plugins.Filter, nodename.Name, nil) diff --git a/pkg/scheduler/framework/plugins/migration/utils.go b/pkg/scheduler/framework/plugins/migration/utils.go index 3aa3870af7d..e0a958e1e30 100644 --- a/pkg/scheduler/framework/plugins/migration/utils.go +++ b/pkg/scheduler/framework/plugins/migration/utils.go @@ -104,3 +104,18 @@ func PriorityMetadata(state *framework.CycleState) interface{} { } return meta } + +// PredicateMetadata returns predicate metadata stored in CycleState. +func PredicateMetadata(state *framework.CycleState) interface{} { + if state == nil { + return nil + } + + var meta interface{} + if s, err := state.Read(PredicatesStateKey); err == nil { + meta = s.(*PredicatesStateData).Reference + } else { + klog.Errorf("reading key %q from CycleState, continuing without metadata: %v", PredicatesStateKey, err) + } + return meta +} diff --git a/pkg/scheduler/framework/plugins/noderesources/BUILD b/pkg/scheduler/framework/plugins/noderesources/BUILD new file mode 100644 index 00000000000..fd9ac774519 --- /dev/null +++ b/pkg/scheduler/framework/plugins/noderesources/BUILD @@ -0,0 +1,49 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["node_resources.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources", + 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", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["node_resources_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/features:go_default_library", + "//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/api/resource:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//staging/src/k8s.io/component-base/featuregate/testing:go_default_library", + ], +) diff --git a/pkg/scheduler/framework/plugins/noderesources/node_resources.go b/pkg/scheduler/framework/plugins/noderesources/node_resources.go new file mode 100644 index 00000000000..2db29b36da7 --- /dev/null +++ b/pkg/scheduler/framework/plugins/noderesources/node_resources.go @@ -0,0 +1,56 @@ +/* +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 noderesources + +import ( + "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" +) + +// NodeResources is a plugin that checks if a node has sufficient resources. +type NodeResources struct{} + +var _ = framework.FilterPlugin(&NodeResources{}) + +// Name is the name of the plugin used in the plugin registry and configurations. +const Name = "NodeResources" + +// Name returns name of the plugin. It is used in logs, etc. +func (pl *NodeResources) Name() string { + return Name +} + +// Filter invoked at the filter extension point. +func (pl *NodeResources) Filter(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.PodFitsResources(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 &NodeResources{}, nil +} diff --git a/pkg/scheduler/framework/plugins/noderesources/node_resources_test.go b/pkg/scheduler/framework/plugins/noderesources/node_resources_test.go new file mode 100644 index 00000000000..0a91fbb2a19 --- /dev/null +++ b/pkg/scheduler/framework/plugins/noderesources/node_resources_test.go @@ -0,0 +1,464 @@ +/* +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 noderesources + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration" + framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" + schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo" +) + +var ( + extendedResourceA = v1.ResourceName("example.com/aaa") + extendedResourceB = v1.ResourceName("example.com/bbb") + kubernetesIOResourceA = v1.ResourceName("kubernetes.io/something") + kubernetesIOResourceB = v1.ResourceName("subdomain.kubernetes.io/something") + hugePageResourceA = v1helper.HugePageResourceName(resource.MustParse("2Mi")) +) + +func makeResources(milliCPU, memory, pods, extendedA, storage, hugePageA int64) v1.NodeResources { + return v1.NodeResources{ + Capacity: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), + v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), + extendedResourceA: *resource.NewQuantity(extendedA, resource.DecimalSI), + v1.ResourceEphemeralStorage: *resource.NewQuantity(storage, resource.BinarySI), + hugePageResourceA: *resource.NewQuantity(hugePageA, resource.BinarySI), + }, + } +} + +func makeAllocatableResources(milliCPU, memory, pods, extendedA, storage, hugePageA int64) v1.ResourceList { + return v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), + v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), + extendedResourceA: *resource.NewQuantity(extendedA, resource.DecimalSI), + v1.ResourceEphemeralStorage: *resource.NewQuantity(storage, resource.BinarySI), + hugePageResourceA: *resource.NewQuantity(hugePageA, resource.BinarySI), + } +} + +func newResourcePod(usage ...schedulernodeinfo.Resource) *v1.Pod { + containers := []v1.Container{} + for _, req := range usage { + containers = append(containers, v1.Container{ + Resources: v1.ResourceRequirements{Requests: req.ResourceList()}, + }) + } + return &v1.Pod{ + Spec: v1.PodSpec{ + Containers: containers, + }, + } +} + +func newResourceInitPod(pod *v1.Pod, usage ...schedulernodeinfo.Resource) *v1.Pod { + pod.Spec.InitContainers = newResourcePod(usage...).Spec.Containers + return pod +} + +func newResourceOverheadPod(pod *v1.Pod, overhead v1.ResourceList) *v1.Pod { + pod.Spec.Overhead = overhead + return pod +} +func TestNodeResources(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)() + + enoughPodsTests := []struct { + pod *v1.Pod + nodeInfo *schedulernodeinfo.NodeInfo + name string + ignoredExtendedResources sets.String + wantStatus *framework.Status + }{ + { + pod: &v1.Pod{}, + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 10, Memory: 20})), + name: "no resources requested always fits", + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 10, Memory: 20})), + name: "too many resources fails", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 2, 10, 10).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 3, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 8, Memory: 19})), + name: "too many resources fails due to init container cpu", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 3, 8, 10).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 3, Memory: 1}, schedulernodeinfo.Resource{MilliCPU: 2, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 8, Memory: 19})), + name: "too many resources fails due to highest init container cpu", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 3, 8, 10).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 1, Memory: 3}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 9, Memory: 19})), + name: "too many resources fails due to init container memory", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceMemory, 3, 19, 20).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 1, Memory: 3}, schedulernodeinfo.Resource{MilliCPU: 1, Memory: 2}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 9, Memory: 19})), + name: "too many resources fails due to highest init container memory", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceMemory, 3, 19, 20).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 9, Memory: 19})), + name: "init container fits because it's the max, not sum, of containers and init containers", + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}, schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 9, Memory: 19})), + name: "multiple init containers fit because it's the max, not sum, of containers and init containers", + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 5})), + name: "both resources fit", + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 2, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 9, Memory: 5})), + name: "one resource memory fits", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 2, 9, 10).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 2}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 19})), + name: "one resource cpu fits", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceMemory, 2, 19, 20).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 19})), + name: "equal edge case", + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 4, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 5, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 19})), + name: "equal edge case for init container", + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo(newResourcePod(schedulernodeinfo.Resource{})), + name: "extended resource fits", + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), schedulernodeinfo.Resource{ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo(newResourcePod(schedulernodeinfo.Resource{})), + name: "extended resource fits for init container", + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 0}})), + name: "extended resource capacity enforced", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 10, 0, 5).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 0}})), + name: "extended resource capacity enforced for init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 10, 0, 5).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 5}})), + name: "extended resource allocatable enforced", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 1, 5, 5).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 5}})), + name: "extended resource allocatable enforced for init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 1, 5, 5).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}, + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), + name: "extended resource allocatable enforced for multiple containers", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 6, 2, 5).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}, + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), + name: "extended resource allocatable admits multiple init containers", + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 6}}, + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), + name: "extended resource allocatable enforced for multiple init containers", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceA, 6, 2, 5).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceB: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0})), + name: "extended resource allocatable enforced for unknown resource", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceB, 1, 0, 0).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceB: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0})), + name: "extended resource allocatable enforced for unknown resource for init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceB, 1, 0, 0).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{kubernetesIOResourceA: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0})), + name: "kubernetes.io resource capacity enforced", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(kubernetesIOResourceA, 10, 0, 0).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{kubernetesIOResourceB: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0})), + name: "kubernetes.io resource capacity enforced for init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(kubernetesIOResourceB, 10, 0, 0).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 0}})), + name: "hugepages resource capacity enforced", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(hugePageResourceA, 10, 0, 5).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{}), + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 10}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 0}})), + name: "hugepages resource capacity enforced for init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(hugePageResourceA, 10, 0, 5).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 3}}, + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 3}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 2}})), + name: "hugepages resource allocatable enforced for multiple containers", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(hugePageResourceA, 6, 2, 5).GetReason()), + }, + { + pod: newResourcePod( + schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceB: 1}}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 0, Memory: 0})), + ignoredExtendedResources: sets.NewString(string(extendedResourceB)), + name: "skip checking ignored extended resource", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(extendedResourceB, 2, 10, 10).GetReason()), + }, + { + pod: newResourceOverheadPod( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + v1.ResourceList{v1.ResourceCPU: resource.MustParse("3m"), v1.ResourceMemory: resource.MustParse("13")}, + ), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 5})), + ignoredExtendedResources: sets.NewString(string(extendedResourceB)), + name: "resources + pod overhead fits", + }, + { + pod: newResourceOverheadPod( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + v1.ResourceList{v1.ResourceCPU: resource.MustParse("1m"), v1.ResourceMemory: resource.MustParse("15")}, + ), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 5})), + ignoredExtendedResources: sets.NewString(string(extendedResourceB)), + name: "requests + overhead does not fit for memory", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceMemory, 16, 5, 20).GetReason()), + }, + } + + for _, test := range enoughPodsTests { + t.Run(test.name, func(t *testing.T) { + meta := predicates.GetPredicateMetadata(test.pod, nil) + state := framework.NewCycleState() + state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta}) + + node := v1.Node{Status: v1.NodeStatus{Capacity: makeResources(10, 20, 32, 5, 20, 5).Capacity, Allocatable: makeAllocatableResources(10, 20, 32, 5, 20, 5)}} + test.nodeInfo.SetNode(&node) + + p, _ := New(nil, nil) + gotStatus := p.(framework.FilterPlugin).Filter(state, test.pod, test.nodeInfo) + if !reflect.DeepEqual(gotStatus, test.wantStatus) { + t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus) + } + }) + } + + notEnoughPodsTests := []struct { + pod *v1.Pod + nodeInfo *schedulernodeinfo.NodeInfo + fits bool + name string + wantStatus *framework.Status + }{ + { + pod: &v1.Pod{}, + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 10, Memory: 20})), + name: "even without specified resources predicate fails when there's no space for additional pod", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourcePods, 1, 1, 1).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 5})), + name: "even if both resources fit predicate fails when there's no space for additional pod", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourcePods, 1, 1, 1).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 19})), + name: "even for equal edge case predicate fails when there's no space for additional pod", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourcePods, 1, 1, 1).GetReason()), + }, + { + pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 5, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 5, Memory: 19})), + name: "even for equal edge case predicate fails when there's no space for additional pod due to init container", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourcePods, 1, 1, 1).GetReason()), + }, + } + for _, test := range notEnoughPodsTests { + t.Run(test.name, func(t *testing.T) { + meta := predicates.GetPredicateMetadata(test.pod, nil) + state := framework.NewCycleState() + state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta}) + + node := v1.Node{Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: makeAllocatableResources(10, 20, 1, 0, 0, 0)}} + test.nodeInfo.SetNode(&node) + + p, _ := New(nil, nil) + gotStatus := p.(framework.FilterPlugin).Filter(state, test.pod, test.nodeInfo) + if !reflect.DeepEqual(gotStatus, test.wantStatus) { + t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus) + } + }) + } + + storagePodsTests := []struct { + pod *v1.Pod + nodeInfo *schedulernodeinfo.NodeInfo + name string + wantStatus *framework.Status + }{ + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 10, Memory: 10})), + name: "due to container scratch disk", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 1, 10, 10).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 2, Memory: 10})), + name: "pod fit", + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{EphemeralStorage: 25}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 2, Memory: 2})), + name: "storage ephemeral local storage request exceeds allocatable", + wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceEphemeralStorage, 25, 0, 20).GetReason()), + }, + { + pod: newResourcePod(schedulernodeinfo.Resource{EphemeralStorage: 10}), + nodeInfo: schedulernodeinfo.NewNodeInfo( + newResourcePod(schedulernodeinfo.Resource{MilliCPU: 2, Memory: 2})), + name: "pod fits", + }, + } + + for _, test := range storagePodsTests { + t.Run(test.name, func(t *testing.T) { + meta := predicates.GetPredicateMetadata(test.pod, nil) + state := framework.NewCycleState() + state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta}) + + node := v1.Node{Status: v1.NodeStatus{Capacity: makeResources(10, 20, 32, 5, 20, 5).Capacity, Allocatable: makeAllocatableResources(10, 20, 32, 5, 20, 5)}} + test.nodeInfo.SetNode(&node) + + p, _ := New(nil, nil) + gotStatus := p.(framework.FilterPlugin).Filter(state, test.pod, test.nodeInfo) + if !reflect.DeepEqual(gotStatus, test.wantStatus) { + t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus) + } + }) + } + +}