Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a least-requested priority function #1604

Merged
merged 1 commit into from
Oct 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 9 additions & 9 deletions pkg/scheduler/generic_scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,18 @@ func (g *genericScheduler) selectHost(priorityList HostPriorityList) (string, er
return hosts[ix], nil
}

func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicate, nodes []string) ([]string, error) {
filtered := []string{}
func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicate, nodes api.MinionList) (api.MinionList, error) {
filtered := []api.Minion{}
machineToPods, err := MapPodsToMachines(podLister)
if err != nil {
return nil, err
return api.MinionList{}, err
}
for _, node := range nodes {
for _, node := range nodes.Items {
fits := true
for _, predicate := range predicates {
fit, err := predicate(pod, machineToPods[node], node)
fit, err := predicate(pod, machineToPods[node.ID], node.ID)
if err != nil {
return nil, err
return api.MinionList{}, err
}
if !fit {
fits = false
Expand All @@ -85,7 +85,7 @@ func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicat
filtered = append(filtered, node)
}
}
return filtered, nil
return api.MinionList{Items: filtered}, nil
}

func getMinHosts(list HostPriorityList) []string {
Expand All @@ -109,9 +109,9 @@ func EqualPriority(pod api.Pod, podLister PodLister, minionLister MinionLister)
fmt.Errorf("failed to list nodes: %v", err)
return []HostPriority{}, err
}
for _, minion := range nodes {
for _, minion := range nodes.Items {
result = append(result, HostPriority{
host: minion,
host: minion.ID,
score: 1,
})
}
Expand Down
18 changes: 14 additions & 4 deletions pkg/scheduler/generic_scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,29 @@ func numericPriority(pod api.Pod, podLister PodLister, minionLister MinionLister
fmt.Errorf("failed to list nodes: %v", err)
return nil, err
}
for _, minion := range nodes {
score, err := strconv.Atoi(minion)
for _, minion := range nodes.Items {
score, err := strconv.Atoi(minion.ID)
if err != nil {
return nil, err
}
result = append(result, HostPriority{
host: minion,
host: minion.ID,
score: score,
})
}
return result, nil
}

func makeMinionList(nodeNames []string) api.MinionList {
result := api.MinionList{
Items: make([]api.Minion, len(nodeNames)),
}
for ix := range nodeNames {
result.Items[ix].ID = nodeNames[ix]
}
return result
}

func TestGenericScheduler(t *testing.T) {
tests := []struct {
predicates []FitPredicate
Expand Down Expand Up @@ -112,7 +122,7 @@ func TestGenericScheduler(t *testing.T) {
for _, test := range tests {
random := rand.New(rand.NewSource(0))
scheduler := NewGenericScheduler(test.predicates, test.prioritizer, FakePodLister([]api.Pod{}), random)
machine, err := scheduler.Schedule(test.pod, FakeMinionLister(test.nodes))
machine, err := scheduler.Schedule(test.pod, FakeMinionLister(makeMinionList(test.nodes)))
if test.expectsErr {
if err == nil {
t.Error("Unexpected non-error")
Expand Down
8 changes: 4 additions & 4 deletions pkg/scheduler/listers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import (

// MinionLister interface represents anything that can list minions for a scheduler.
type MinionLister interface {
List() (machines []string, err error)
List() (list api.MinionList, err error)
}

// FakeMinionLister implements MinionLister on a []string for test purposes.
type FakeMinionLister []string
type FakeMinionLister api.MinionList

// List returns minions as a []string.
func (f FakeMinionLister) List() ([]string, error) {
return []string(f), nil
func (f FakeMinionLister) List() (api.MinionList, error) {
return api.MinionList(f), nil
}

// PodLister interface represents anything that can list pods for a scheduler.
Expand Down
63 changes: 63 additions & 0 deletions pkg/scheduler/priorities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2014 Google Inc. 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 scheduler

import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/golang/glog"
)

// Calculate the occupancy on a node. 'node' has information about the resources on the node.
// 'pods' is a list of pods currently scheduled on the node.
func calculateOccupancy(node api.Minion, pods []api.Pod) HostPriority {
totalCPU := 0
totalMemory := 0
for ix := range pods {
for cIx := range pods[ix].DesiredState.Manifest.Containers {
container := &(pods[ix].DesiredState.Manifest.Containers[cIx])
totalCPU += container.CPU
totalMemory += container.Memory
}
}
percentageCPU := (totalCPU * 100) / resources.GetIntegerResource(node.NodeResources.Capacity, resources.CPU, 0)
percentageMemory := (totalMemory * 100) / resources.GetIntegerResource(node.NodeResources.Capacity, resources.Memory, 0)
glog.V(4).Infof("Least Requested Priority, AbsoluteRequested: (%d, %d) Percentage:(%d\\%m, %d\\%)", totalCPU, totalMemory, percentageCPU, percentageMemory)

return HostPriority{
host: node.ID,
score: int((percentageCPU + percentageMemory) / 2),
}
}

// LeastRequestedPriority is a priority function that favors nodes with fewer requested resources.
// It calculates the percentage of memory and CPU requested by pods scheduled on the node, and prioritizes
// based on the minimum of the average of the fraction of requested to capacity.
// Details: (Sum(requested cpu) / Capacity + Sum(requested memory) / Capacity) * 50
func LeastRequestedPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) {
nodes, err := minionLister.List()
if err != nil {
return HostPriorityList{}, err
}
podsToMachines, err := MapPodsToMachines(podLister)

list := HostPriorityList{}
for _, node := range nodes.Items {
list = append(list, calculateOccupancy(node, podsToMachines[node.ID]))
}
return list, nil
}
114 changes: 114 additions & 0 deletions pkg/scheduler/priorities_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Copyright 2014 Google Inc. 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 scheduler

import (
"reflect"
"testing"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)

func makeMinion(node string, cpu, memory int) api.Minion {
return api.Minion{
TypeMeta: api.TypeMeta{ID: node},
NodeResources: api.NodeResources{
Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(cpu),
resources.Memory: util.NewIntOrStringFromInt(memory),
},
},
}
}

func TestLeastRequested(t *testing.T) {
labels1 := map[string]string{
"foo": "bar",
"baz": "blah",
}
labels2 := map[string]string{
"bar": "foo",
"baz": "blah",
}
machine1State := api.PodState{
Host: "machine1",
}
machine2State := api.PodState{
Host: "machine2",
}
cpuOnly := api.PodState{
Manifest: api.ContainerManifest{
Containers: []api.Container{
{CPU: 1000},
{CPU: 2000},
},
},
}
cpuAndMemory := api.PodState{
Manifest: api.ContainerManifest{
Containers: []api.Container{
{CPU: 1000, Memory: 2000},
{CPU: 2000, Memory: 3000},
},
},
}
tests := []struct {
pod api.Pod
pods []api.Pod
nodes []api.Minion
expectedList HostPriorityList
test string
}{
{
nodes: []api.Minion{makeMinion("machine1", 4000, 10000), makeMinion("machine2", 4000, 10000)},
expectedList: []HostPriority{{"machine1", 0}, {"machine2", 0}},
test: "nothing scheduled",
},
{
nodes: []api.Minion{makeMinion("machine1", 4000, 10000), makeMinion("machine2", 4000, 10000)},
expectedList: []HostPriority{{"machine1", 0}, {"machine2", 0}},
test: "no resources requested",
pods: []api.Pod{
{CurrentState: machine1State, Labels: labels2},
{CurrentState: machine1State, Labels: labels1},
{CurrentState: machine2State, Labels: labels1},
{CurrentState: machine2State, Labels: labels1},
},
},
{
nodes: []api.Minion{makeMinion("machine1", 4000, 10000), makeMinion("machine2", 4000, 10000)},
expectedList: []HostPriority{{"machine1", 37 /* int(75% / 2) */}, {"machine2", 62 /* int( 75% + 50% / 2) */}},
test: "no resources requested",
pods: []api.Pod{
{DesiredState: cpuOnly, CurrentState: machine1State},
{DesiredState: cpuAndMemory, CurrentState: machine2State},
},
},
}

for _, test := range tests {
list, err := LeastRequestedPriority(test.pod, FakePodLister(test.pods), FakeMinionLister(api.MinionList{Items: test.nodes}))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectedList, list) {
t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list)
}
}
}
4 changes: 2 additions & 2 deletions pkg/scheduler/spreading.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func CalculateSpreadPriority(pod api.Pod, podLister PodLister, minionLister Mini
}

result := []HostPriority{}
for _, minion := range minions {
result = append(result, HostPriority{host: minion, score: counts[minion]})
for _, minion := range minions.Items {
result = append(result, HostPriority{host: minion.ID, score: counts[minion.ID]})
}
return result, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/scheduler/spreading_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestSpreadPriority(t *testing.T) {
}

for _, test := range tests {
list, err := CalculateSpreadPriority(test.pod, FakePodLister(test.pods), FakeMinionLister(test.nodes))
list, err := CalculateSpreadPriority(test.pod, FakePodLister(test.pods), FakeMinionLister(makeMinionList(test.nodes)))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions plugin/pkg/scheduler/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,9 @@ type storeToMinionLister struct {
cache.Store
}

func (s *storeToMinionLister) List() (machines []string, err error) {
func (s *storeToMinionLister) List() (machines api.MinionList, err error) {
for _, m := range s.Store.List() {
machines = append(machines, m.(*api.Minion).ID)
machines.Items = append(machines.Items, *(m.(*api.Minion)))
}
return machines, nil
}
Expand Down
6 changes: 5 additions & 1 deletion plugin/pkg/scheduler/factory/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,14 @@ func TestStoreToMinionLister(t *testing.T) {
}
sml := storeToMinionLister{store}

got, err := sml.List()
gotNodes, err := sml.List()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
got := make([]string, len(gotNodes.Items))
for ix := range gotNodes.Items {
got[ix] = gotNodes.Items[ix].ID
}
if !ids.HasAll(got...) || len(got) != len(ids) {
t.Errorf("Expected %v, got %v", ids, got)
}
Expand Down
6 changes: 4 additions & 2 deletions plugin/pkg/scheduler/scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ func TestScheduler(t *testing.T) {
var gotPod *api.Pod
var gotBinding *api.Binding
c := &Config{
MinionLister: scheduler.FakeMinionLister{"machine1"},
Algorithm: item.algo,
MinionLister: scheduler.FakeMinionLister(
api.MinionList{Items: []api.Minion{{TypeMeta: api.TypeMeta{ID: "machine1"}}}},
),
Algorithm: item.algo,
Binder: fakeBinder{func(b *api.Binding) error {
gotBinding = b
return item.injectBindError
Expand Down