Merge pull request #1604 from brendandburns/resource

Add a least-requested priority function
This commit is contained in:
Daniel Smith 2014-10-08 17:26:57 -07:00
commit 1a1b0699bc
10 changed files with 218 additions and 25 deletions

View File

@ -63,18 +63,18 @@ func (g *genericScheduler) selectHost(priorityList HostPriorityList) (string, er
return hosts[ix], nil return hosts[ix], nil
} }
func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicate, nodes []string) ([]string, error) { func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicate, nodes api.MinionList) (api.MinionList, error) {
filtered := []string{} filtered := []api.Minion{}
machineToPods, err := MapPodsToMachines(podLister) machineToPods, err := MapPodsToMachines(podLister)
if err != nil { if err != nil {
return nil, err return api.MinionList{}, err
} }
for _, node := range nodes { for _, node := range nodes.Items {
fits := true fits := true
for _, predicate := range predicates { for _, predicate := range predicates {
fit, err := predicate(pod, machineToPods[node], node) fit, err := predicate(pod, machineToPods[node.ID], node.ID)
if err != nil { if err != nil {
return nil, err return api.MinionList{}, err
} }
if !fit { if !fit {
fits = false fits = false
@ -85,7 +85,7 @@ func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicat
filtered = append(filtered, node) filtered = append(filtered, node)
} }
} }
return filtered, nil return api.MinionList{Items: filtered}, nil
} }
func getMinHosts(list HostPriorityList) []string { func getMinHosts(list HostPriorityList) []string {
@ -109,9 +109,9 @@ func EqualPriority(pod api.Pod, podLister PodLister, minionLister MinionLister)
fmt.Errorf("failed to list nodes: %v", err) fmt.Errorf("failed to list nodes: %v", err)
return []HostPriority{}, err return []HostPriority{}, err
} }
for _, minion := range nodes { for _, minion := range nodes.Items {
result = append(result, HostPriority{ result = append(result, HostPriority{
host: minion, host: minion.ID,
score: 1, score: 1,
}) })
} }

View File

@ -45,19 +45,29 @@ func numericPriority(pod api.Pod, podLister PodLister, minionLister MinionLister
fmt.Errorf("failed to list nodes: %v", err) fmt.Errorf("failed to list nodes: %v", err)
return nil, err return nil, err
} }
for _, minion := range nodes { for _, minion := range nodes.Items {
score, err := strconv.Atoi(minion) score, err := strconv.Atoi(minion.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = append(result, HostPriority{ result = append(result, HostPriority{
host: minion, host: minion.ID,
score: score, score: score,
}) })
} }
return result, nil 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) { func TestGenericScheduler(t *testing.T) {
tests := []struct { tests := []struct {
predicates []FitPredicate predicates []FitPredicate
@ -112,7 +122,7 @@ func TestGenericScheduler(t *testing.T) {
for _, test := range tests { for _, test := range tests {
random := rand.New(rand.NewSource(0)) random := rand.New(rand.NewSource(0))
scheduler := NewGenericScheduler(test.predicates, test.prioritizer, FakePodLister([]api.Pod{}), random) 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 test.expectsErr {
if err == nil { if err == nil {
t.Error("Unexpected non-error") t.Error("Unexpected non-error")

View File

@ -23,15 +23,15 @@ import (
// MinionLister interface represents anything that can list minions for a scheduler. // MinionLister interface represents anything that can list minions for a scheduler.
type MinionLister interface { type MinionLister interface {
List() (machines []string, err error) List() (list api.MinionList, err error)
} }
// FakeMinionLister implements MinionLister on a []string for test purposes. // FakeMinionLister implements MinionLister on a []string for test purposes.
type FakeMinionLister []string type FakeMinionLister api.MinionList
// List returns minions as a []string. // List returns minions as a []string.
func (f FakeMinionLister) List() ([]string, error) { func (f FakeMinionLister) List() (api.MinionList, error) {
return []string(f), nil return api.MinionList(f), nil
} }
// PodLister interface represents anything that can list pods for a scheduler. // PodLister interface represents anything that can list pods for a scheduler.

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -43,8 +43,8 @@ func CalculateSpreadPriority(pod api.Pod, podLister PodLister, minionLister Mini
} }
result := []HostPriority{} result := []HostPriority{}
for _, minion := range minions { for _, minion := range minions.Items {
result = append(result, HostPriority{host: minion, score: counts[minion]}) result = append(result, HostPriority{host: minion.ID, score: counts[minion.ID]})
} }
return result, nil return result, nil
} }

View File

@ -100,7 +100,7 @@ func TestSpreadPriority(t *testing.T) {
} }
for _, test := range tests { 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 { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }

View File

@ -202,9 +202,9 @@ type storeToMinionLister struct {
cache.Store 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() { for _, m := range s.Store.List() {
machines = append(machines, m.(*api.Minion).ID) machines.Items = append(machines.Items, *(m.(*api.Minion)))
} }
return machines, nil return machines, nil
} }

View File

@ -223,10 +223,14 @@ func TestStoreToMinionLister(t *testing.T) {
} }
sml := storeToMinionLister{store} sml := storeToMinionLister{store}
got, err := sml.List() gotNodes, err := sml.List()
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) 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) { if !ids.HasAll(got...) || len(got) != len(ids) {
t.Errorf("Expected %v, got %v", ids, got) t.Errorf("Expected %v, got %v", ids, got)
} }

View File

@ -81,8 +81,10 @@ func TestScheduler(t *testing.T) {
var gotPod *api.Pod var gotPod *api.Pod
var gotBinding *api.Binding var gotBinding *api.Binding
c := &Config{ c := &Config{
MinionLister: scheduler.FakeMinionLister{"machine1"}, MinionLister: scheduler.FakeMinionLister(
Algorithm: item.algo, api.MinionList{Items: []api.Minion{{TypeMeta: api.TypeMeta{ID: "machine1"}}}},
),
Algorithm: item.algo,
Binder: fakeBinder{func(b *api.Binding) error { Binder: fakeBinder{func(b *api.Binding) error {
gotBinding = b gotBinding = b
return item.injectBindError return item.injectBindError