diff --git a/plugin/pkg/scheduler/algorithm/predicates/error.go b/plugin/pkg/scheduler/algorithm/predicates/error.go new file mode 100644 index 0000000000000..81923218b599a --- /dev/null +++ b/plugin/pkg/scheduler/algorithm/predicates/error.go @@ -0,0 +1,40 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 predicates + +import "fmt" + +var ( + ErrExceededMaxPodNumber = newInsufficientResourceError("PodCount") + ErrInsufficientFreeCPU = newInsufficientResourceError("CPU") + ErrInsufficientFreeMemory = newInsufficientResourceError("Memory") +) + +// InsufficientResourceError is an error type that indicates what kind of resource limit is +// hit and caused the unfitting failure. +type InsufficientResourceError struct { + // ResourceName tells the name of the resource that is insufficient + ResourceName string +} + +func newInsufficientResourceError(resourceName string) *InsufficientResourceError { + return &InsufficientResourceError{resourceName} +} + +func (e *InsufficientResourceError) Error() string { + return fmt.Sprintf("Node didn't have enough resource: %s", e.ResourceName) +} diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates.go b/plugin/pkg/scheduler/algorithm/predicates/predicates.go index f45c248ba614a..0e3a45766ac4d 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates.go @@ -256,8 +256,6 @@ type resourceRequest struct { memory int64 } -var FailedResourceType string - func getResourceRequest(pod *api.Pod) resourceRequest { result := resourceRequest{} for _, container := range pod.Spec.Containers { @@ -308,8 +306,7 @@ func (r *ResourceFit) PodFitsResources(pod *api.Pod, existingPods []*api.Pod, no if int64(len(existingPods))+1 > info.Status.Capacity.Pods().Value() { glog.V(10).Infof("Cannot schedule Pod %+v, because Node %+v is full, running %v out of %v Pods.", podName(pod), node, len(existingPods), info.Status.Capacity.Pods().Value()) - FailedResourceType = "PodExceedsMaxPodNumber" - return false, nil + return false, ErrExceededMaxPodNumber } podRequest := getResourceRequest(pod) @@ -321,13 +318,11 @@ func (r *ResourceFit) PodFitsResources(pod *api.Pod, existingPods []*api.Pod, no _, exceedingCPU, exceedingMemory := CheckPodsExceedingFreeResources(pods, info.Status.Capacity) if len(exceedingCPU) > 0 { glog.V(10).Infof("Cannot schedule Pod %+v, because Node %v does not have sufficient CPU", podName(pod), node) - FailedResourceType = "PodExceedsFreeCPU" - return false, nil + return false, ErrInsufficientFreeCPU } if len(exceedingMemory) > 0 { glog.V(10).Infof("Cannot schedule Pod %+v, because Node %v does not have sufficient Memory", podName(pod), node) - FailedResourceType = "PodExceedsFreeMemory" - return false, nil + return false, ErrInsufficientFreeMemory } glog.V(10).Infof("Schedule Pod %+v on Node %+v is allowed, Node is running only %v out of %v Pods.", podName(pod), node, len(pods)-1, info.Status.Capacity.Pods().Value()) return true, nil diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go index 5dc1a148cee0a..544fee705bee3 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -80,6 +80,7 @@ func TestPodFitsResources(t *testing.T) { existingPods []*api.Pod fits bool test string + wErr error }{ { pod: &api.Pod{}, @@ -88,6 +89,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: true, test: "no resources requested always fits", + wErr: nil, }, { pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}), @@ -96,6 +98,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: false, test: "too many resources fails", + wErr: ErrInsufficientFreeCPU, }, { pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}), @@ -104,6 +107,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: true, test: "both resources fit", + wErr: nil, }, { pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 2}), @@ -112,6 +116,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: false, test: "one resources fits", + wErr: ErrInsufficientFreeMemory, }, { pod: newResourcePod(resourceRequest{milliCPU: 5, memory: 1}), @@ -120,6 +125,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: true, test: "equal edge case", + wErr: nil, }, } @@ -128,8 +134,8 @@ func TestPodFitsResources(t *testing.T) { fit := ResourceFit{FakeNodeInfo(node)} fits, err := fit.PodFitsResources(test.pod, test.existingPods, "machine") - if err != nil { - t.Errorf("unexpected error: %v", err) + if !reflect.DeepEqual(err, test.wErr) { + t.Errorf("%s: unexpected error: %v, want: %v", test.test, err, test.wErr) } if fits != test.fits { t.Errorf("%s: expected: %v got %v", test.test, test.fits, fits) @@ -141,6 +147,7 @@ func TestPodFitsResources(t *testing.T) { existingPods []*api.Pod fits bool test string + wErr error }{ { pod: &api.Pod{}, @@ -149,6 +156,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: false, test: "even without specified resources predicate fails when there's no available ips", + wErr: ErrExceededMaxPodNumber, }, { pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}), @@ -157,6 +165,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: false, test: "even if both resources fit predicate fails when there's no available ips", + wErr: ErrExceededMaxPodNumber, }, { pod: newResourcePod(resourceRequest{milliCPU: 5, memory: 1}), @@ -165,6 +174,7 @@ func TestPodFitsResources(t *testing.T) { }, fits: false, test: "even for equal edge case predicate fails when there's no available ips", + wErr: ErrExceededMaxPodNumber, }, } for _, test := range notEnoughPodsTests { @@ -172,8 +182,8 @@ func TestPodFitsResources(t *testing.T) { fit := ResourceFit{FakeNodeInfo(node)} fits, err := fit.PodFitsResources(test.pod, test.existingPods, "machine") - if err != nil { - t.Errorf("unexpected error: %v", err) + if !reflect.DeepEqual(err, test.wErr) { + t.Errorf("%s: unexpected error: %v, want: %v", test.test, err, test.wErr) } if fits != test.fits { t.Errorf("%s: expected: %v got %v", test.test, test.fits, fits) diff --git a/plugin/pkg/scheduler/generic_scheduler.go b/plugin/pkg/scheduler/generic_scheduler.go index 563b55ea56404..b54ae91d4f1d9 100644 --- a/plugin/pkg/scheduler/generic_scheduler.go +++ b/plugin/pkg/scheduler/generic_scheduler.go @@ -127,18 +127,25 @@ func findNodesThatFit(pod *api.Pod, machineToPods map[string][]*api.Pod, predica for _, node := range nodes.Items { fits := true for name, predicate := range predicateFuncs { - predicates.FailedResourceType = "" fit, err := predicate(pod, machineToPods[node.Name], node.Name) if err != nil { - return api.NodeList{}, FailedPredicateMap{}, err + switch e := err.(type) { + case *predicates.InsufficientResourceError: + if fit { + err := fmt.Errorf("got InsufficientResourceError: %v, but also fit='true' which is unexpected", e) + return api.NodeList{}, FailedPredicateMap{}, err + } + default: + return api.NodeList{}, FailedPredicateMap{}, err + } } if !fit { fits = false if _, found := failedPredicateMap[node.Name]; !found { failedPredicateMap[node.Name] = sets.String{} } - if predicates.FailedResourceType != "" { - failedPredicateMap[node.Name].Insert(predicates.FailedResourceType) + if re, ok := err.(*predicates.InsufficientResourceError); ok { + failedPredicateMap[node.Name].Insert(re.ResourceName) break } failedPredicateMap[node.Name].Insert(name)