From 44703efe2e895e1208f23f2c6b07b473c7a5e100 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 25 Sep 2014 11:36:22 -0700 Subject: [PATCH 1/2] Simplify schedulers. --- pkg/scheduler/generic_scheduler.go | 17 +++++++++ pkg/scheduler/generic_scheduler_test.go | 17 --------- pkg/scheduler/random.go | 48 ----------------------- pkg/scheduler/random_test.go | 34 ----------------- pkg/scheduler/randomfit.go | 51 +------------------------ pkg/scheduler/roundrobin.go | 43 --------------------- pkg/scheduler/roundrobin_test.go | 35 ----------------- 7 files changed, 18 insertions(+), 227 deletions(-) delete mode 100644 pkg/scheduler/random.go delete mode 100644 pkg/scheduler/random_test.go delete mode 100644 pkg/scheduler/roundrobin.go delete mode 100644 pkg/scheduler/roundrobin_test.go diff --git a/pkg/scheduler/generic_scheduler.go b/pkg/scheduler/generic_scheduler.go index cecc130a76f..737a964764c 100644 --- a/pkg/scheduler/generic_scheduler.go +++ b/pkg/scheduler/generic_scheduler.go @@ -100,6 +100,23 @@ func getMinHosts(list HostPriorityList) []string { return result } +func evenPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) { + nodes, err := minionLister.List() + result := []HostPriority{} + + if err != nil { + fmt.Errorf("failed to list nodes: %v", err) + return []HostPriority{}, err + } + for _, minion := range nodes { + result = append(result, HostPriority{ + host: minion, + score: 1, + }) + } + return result, nil +} + func NewGenericScheduler(predicates []FitPredicate, prioritizer PriorityFunction, pods PodLister, random *rand.Rand) Scheduler { return &genericScheduler{ predicates: predicates, diff --git a/pkg/scheduler/generic_scheduler_test.go b/pkg/scheduler/generic_scheduler_test.go index 31ef5730c42..1b9e98e7693 100644 --- a/pkg/scheduler/generic_scheduler_test.go +++ b/pkg/scheduler/generic_scheduler_test.go @@ -29,23 +29,6 @@ func matchesPredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, e return pod.ID == node, nil } -func evenPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) { - nodes, err := minionLister.List() - result := []HostPriority{} - - if err != nil { - fmt.Errorf("failed to list nodes: %v", err) - return []HostPriority{}, err - } - for _, minion := range nodes { - result = append(result, HostPriority{ - host: minion, - score: 1, - }) - } - return result, nil -} - func numericPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) { nodes, err := minionLister.List() result := []HostPriority{} diff --git a/pkg/scheduler/random.go b/pkg/scheduler/random.go deleted file mode 100644 index f01ac52e81f..00000000000 --- a/pkg/scheduler/random.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 ( - "math/rand" - "sync" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" -) - -// RandomScheduler chooses machines uniformly at random. -type RandomScheduler struct { - random *rand.Rand - randomLock sync.Mutex -} - -func NewRandomScheduler(random *rand.Rand) Scheduler { - return &RandomScheduler{ - random: random, - } -} - -// Schedule schedules a given pod to a random machine. -func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { - machines, err := minionLister.List() - if err != nil { - return "", err - } - - s.randomLock.Lock() - defer s.randomLock.Unlock() - return machines[s.random.Int()%len(machines)], nil -} diff --git a/pkg/scheduler/random_test.go b/pkg/scheduler/random_test.go deleted file mode 100644 index 92b4ae2d3b1..00000000000 --- a/pkg/scheduler/random_test.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -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 ( - "math/rand" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" -) - -func TestRandomScheduler(t *testing.T) { - random := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomScheduler(random), - minionLister: FakeMinionLister{"m1", "m2", "m3", "m4"}, - } - st.expectSuccess(api.Pod{}) -} diff --git a/pkg/scheduler/randomfit.go b/pkg/scheduler/randomfit.go index 7ae9b4fe57d..8f30fe172b1 100644 --- a/pkg/scheduler/randomfit.go +++ b/pkg/scheduler/randomfit.go @@ -17,22 +17,12 @@ limitations under the License. package scheduler import ( - "fmt" "math/rand" - "sync" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// RandomFitScheduler is a Scheduler which schedules a Pod on a random machine which matches its requirement. -type RandomFitScheduler struct { - podLister PodLister - predicates []FitPredicate - random *rand.Rand - randomLock sync.Mutex -} - // NewRandomFitScheduler creates a random fit scheduler with the default set of fit predicates func NewRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { return NewRandomFitSchedulerWithPredicates(podLister, random, []FitPredicate{podFitsPorts}) @@ -41,11 +31,7 @@ func NewRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { // NewRandomFitScheduler creates a random fit scheduler with the specified set of fit predicates. // All predicates must be true for the pod to be considered a fit. func NewRandomFitSchedulerWithPredicates(podLister PodLister, random *rand.Rand, predicates []FitPredicate) Scheduler { - return &RandomFitScheduler{ - podLister: podLister, - random: random, - predicates: predicates, - } + return NewGenericScheduler(predicates, evenPriority, podLister, random) } func podFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { @@ -90,38 +76,3 @@ func MapPodsToMachines(lister PodLister) (map[string][]api.Pod, error) { } return machineToPods, nil } - -// Schedule schedules a pod on a random machine which matches its requirement. -func (s *RandomFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { - machines, err := minionLister.List() - if err != nil { - return "", err - } - machineToPods, err := MapPodsToMachines(s.podLister) - if err != nil { - return "", err - } - var machineOptions []string - for _, machine := range machines { - podFits := true - for _, predicate := range s.predicates { - fits, err := predicate(pod, machineToPods[machine], machine) - if err != nil { - return "", err - } - if !fits { - podFits = false - break - } - } - if podFits { - machineOptions = append(machineOptions, machine) - } - } - if len(machineOptions) == 0 { - return "", fmt.Errorf("failed to find fit for %#v", pod) - } - s.randomLock.Lock() - defer s.randomLock.Unlock() - return machineOptions[s.random.Int()%len(machineOptions)], nil -} diff --git a/pkg/scheduler/roundrobin.go b/pkg/scheduler/roundrobin.go deleted file mode 100644 index 26d3d1cd705..00000000000 --- a/pkg/scheduler/roundrobin.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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" -) - -// RoundRobinScheduler chooses machines in order. -type RoundRobinScheduler struct { - currentIndex int -} - -func NewRoundRobinScheduler() Scheduler { - return &RoundRobinScheduler{ - currentIndex: -1, - } -} - -// Schedule schedules a pod on the machine next to the last scheduled machine. -func (s *RoundRobinScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { - machines, err := minionLister.List() - if err != nil { - return "", err - } - s.currentIndex = (s.currentIndex + 1) % len(machines) - result := machines[s.currentIndex] - return result, nil -} diff --git a/pkg/scheduler/roundrobin_test.go b/pkg/scheduler/roundrobin_test.go deleted file mode 100644 index 8fc9f74b70e..00000000000 --- a/pkg/scheduler/roundrobin_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 ( - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" -) - -func TestRoundRobinScheduler(t *testing.T) { - st := schedulerTester{ - t: t, - scheduler: NewRoundRobinScheduler(), - minionLister: FakeMinionLister{"m1", "m2", "m3", "m4"}, - } - st.expectSchedule(api.Pod{}, "m1") - st.expectSchedule(api.Pod{}, "m2") - st.expectSchedule(api.Pod{}, "m3") - st.expectSchedule(api.Pod{}, "m4") -} From 1bb962961c929d7cdd88c36c57498109bf9f8737 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 25 Sep 2014 11:56:57 -0700 Subject: [PATCH 2/2] Refactor schedulers, remove schedulers, use generic scheduler. --- pkg/scheduler/generic_scheduler.go | 3 +- pkg/scheduler/generic_scheduler_test.go | 14 ++- pkg/scheduler/{randomfit.go => predicates.go} | 15 +-- pkg/scheduler/predicates_test.go | 80 ++++++++++++ pkg/scheduler/randomfit_test.go | 116 ------------------ .../{spreading_scheduler.go => spreading.go} | 0 ...ng_scheduler_test.go => spreading_test.go} | 0 plugin/pkg/scheduler/factory/factory.go | 6 +- 8 files changed, 99 insertions(+), 135 deletions(-) rename pkg/scheduler/{randomfit.go => predicates.go} (72%) create mode 100644 pkg/scheduler/predicates_test.go delete mode 100644 pkg/scheduler/randomfit_test.go rename pkg/scheduler/{spreading_scheduler.go => spreading.go} (100%) rename pkg/scheduler/{spreading_scheduler_test.go => spreading_test.go} (100%) diff --git a/pkg/scheduler/generic_scheduler.go b/pkg/scheduler/generic_scheduler.go index 737a964764c..df6946bf9fc 100644 --- a/pkg/scheduler/generic_scheduler.go +++ b/pkg/scheduler/generic_scheduler.go @@ -100,7 +100,8 @@ func getMinHosts(list HostPriorityList) []string { return result } -func evenPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) { +// EqualPriority is a prioritizer function that gives an equal weight of one to all nodes +func EqualPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) { nodes, err := minionLister.List() result := []HostPriority{} diff --git a/pkg/scheduler/generic_scheduler_test.go b/pkg/scheduler/generic_scheduler_test.go index 1b9e98e7693..a49b3d3fe26 100644 --- a/pkg/scheduler/generic_scheduler_test.go +++ b/pkg/scheduler/generic_scheduler_test.go @@ -25,6 +25,14 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) +func falsePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { + return false, nil +} + +func truePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { + return true, nil +} + func matchesPredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { return pod.ID == node, nil } @@ -61,13 +69,13 @@ func TestGenericScheduler(t *testing.T) { }{ { predicates: []FitPredicate{falsePredicate}, - prioritizer: evenPriority, + prioritizer: EqualPriority, nodes: []string{"machine1", "machine2"}, expectsErr: true, }, { predicates: []FitPredicate{truePredicate}, - prioritizer: evenPriority, + prioritizer: EqualPriority, nodes: []string{"machine1", "machine2"}, // Random choice between both, the rand seeded above with zero, chooses "machine2" expectedHost: "machine2", @@ -75,7 +83,7 @@ func TestGenericScheduler(t *testing.T) { { // Fits on a machine where the pod ID matches the machine name predicates: []FitPredicate{matchesPredicate}, - prioritizer: evenPriority, + prioritizer: EqualPriority, nodes: []string{"machine1", "machine2"}, pod: api.Pod{JSONBase: api.JSONBase{ID: "machine2"}}, expectedHost: "machine2", diff --git a/pkg/scheduler/randomfit.go b/pkg/scheduler/predicates.go similarity index 72% rename from pkg/scheduler/randomfit.go rename to pkg/scheduler/predicates.go index 8f30fe172b1..348345b8a23 100644 --- a/pkg/scheduler/randomfit.go +++ b/pkg/scheduler/predicates.go @@ -17,24 +17,11 @@ limitations under the License. package scheduler import ( - "math/rand" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// NewRandomFitScheduler creates a random fit scheduler with the default set of fit predicates -func NewRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { - return NewRandomFitSchedulerWithPredicates(podLister, random, []FitPredicate{podFitsPorts}) -} - -// NewRandomFitScheduler creates a random fit scheduler with the specified set of fit predicates. -// All predicates must be true for the pod to be considered a fit. -func NewRandomFitSchedulerWithPredicates(podLister PodLister, random *rand.Rand, predicates []FitPredicate) Scheduler { - return NewGenericScheduler(predicates, evenPriority, podLister, random) -} - -func podFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { +func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { for _, scheduledPod := range existingPods { for _, container := range pod.DesiredState.Manifest.Containers { for _, port := range container.Ports { diff --git a/pkg/scheduler/predicates_test.go b/pkg/scheduler/predicates_test.go new file mode 100644 index 00000000000..89539f7d791 --- /dev/null +++ b/pkg/scheduler/predicates_test.go @@ -0,0 +1,80 @@ +/* +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 ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" +) + +func TestPodFitsPorts(t *testing.T) { + tests := []struct { + pod api.Pod + existingPods []api.Pod + fits bool + test string + }{ + { + pod: api.Pod{}, + existingPods: []api.Pod{}, + fits: true, + test: "nothing running", + }, + { + pod: newPod("m1", 8080), + existingPods: []api.Pod{ + newPod("m1", 9090), + }, + fits: true, + test: "other port", + }, + { + pod: newPod("m1", 8080), + existingPods: []api.Pod{ + newPod("m1", 8080), + }, + fits: false, + test: "same port", + }, + { + pod: newPod("m1", 8000, 8080), + existingPods: []api.Pod{ + newPod("m1", 8080), + }, + fits: false, + test: "second port", + }, + { + pod: newPod("m1", 8000, 8080), + existingPods: []api.Pod{ + newPod("m1", 8001, 8080), + }, + fits: false, + test: "second port", + }, + } + for _, test := range tests { + fits, err := PodFitsPorts(test.pod, test.existingPods, "machine") + if err != nil { + t.Errorf("unexpected error: %v") + } + if test.fits != fits { + t.Errorf("%s: expected %v, saw %v", test.test, test.fits, fits) + } + } +} diff --git a/pkg/scheduler/randomfit_test.go b/pkg/scheduler/randomfit_test.go deleted file mode 100644 index f4c3b908f33..00000000000 --- a/pkg/scheduler/randomfit_test.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -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 ( - "math/rand" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" -) - -func TestRandomFitSchedulerNothingScheduled(t *testing.T) { - fakeRegistry := FakePodLister{} - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitScheduler(&fakeRegistry, r), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectSchedule(api.Pod{}, "m3") -} - -func TestRandomFitSchedulerFirstScheduled(t *testing.T) { - fakeRegistry := FakePodLister{ - newPod("m1", 8080), - } - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitScheduler(fakeRegistry, r), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectSchedule(newPod("", 8080), "m3") -} - -func TestRandomFitSchedulerFirstScheduledComplicated(t *testing.T) { - fakeRegistry := FakePodLister{ - newPod("m1", 80, 8080), - newPod("m2", 8081, 8082, 8083), - newPod("m3", 80, 443, 8085), - } - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitScheduler(fakeRegistry, r), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectSchedule(newPod("", 8080, 8081), "m3") -} - -func TestRandomFitSchedulerFirstScheduledImpossible(t *testing.T) { - fakeRegistry := FakePodLister{ - newPod("m1", 8080), - newPod("m2", 8081), - newPod("m3", 8080), - } - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitScheduler(fakeRegistry, r), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectFailure(newPod("", 8080, 8081)) -} - -func falsePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { - return false, nil -} - -func truePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { - return true, nil -} - -func TestRandomFitSchedulerFirstScheduledComplicatedWithMultiplePredicates(t *testing.T) { - fakeRegistry := FakePodLister{ - newPod("m1", 80, 8080), - newPod("m2", 8081, 8082, 8083), - newPod("m3", 80, 443, 8085), - } - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitSchedulerWithPredicates(fakeRegistry, r, []FitPredicate{podFitsPorts, truePredicate}), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectSchedule(newPod("", 8080, 8081), "m3") -} - -func TestRandomFitSchedulerFailureManyPredicates(t *testing.T) { - fakeRegistry := FakePodLister{ - newPod("m1", 8080), - newPod("m2", 8081), - newPod("m3", 8080), - } - r := rand.New(rand.NewSource(0)) - st := schedulerTester{ - t: t, - scheduler: NewRandomFitSchedulerWithPredicates(fakeRegistry, r, []FitPredicate{truePredicate, falsePredicate}), - minionLister: FakeMinionLister{"m1", "m2", "m3"}, - } - st.expectFailure(newPod("", 8080, 8081)) -} diff --git a/pkg/scheduler/spreading_scheduler.go b/pkg/scheduler/spreading.go similarity index 100% rename from pkg/scheduler/spreading_scheduler.go rename to pkg/scheduler/spreading.go diff --git a/pkg/scheduler/spreading_scheduler_test.go b/pkg/scheduler/spreading_test.go similarity index 100% rename from pkg/scheduler/spreading_scheduler_test.go rename to pkg/scheduler/spreading_test.go diff --git a/plugin/pkg/scheduler/factory/factory.go b/plugin/pkg/scheduler/factory/factory.go index c67aa863785..f3efb03d1a7 100644 --- a/plugin/pkg/scheduler/factory/factory.go +++ b/plugin/pkg/scheduler/factory/factory.go @@ -62,7 +62,11 @@ func (factory *ConfigFactory) Create() *scheduler.Config { } r := rand.New(rand.NewSource(time.Now().UnixNano())) - algo := algorithm.NewRandomFitScheduler( + algo := algorithm.NewGenericScheduler( + // Fit is defined based on the absence of port conflicts. + []algorithm.FitPredicate{algorithm.PodFitsPorts}, + // All nodes where things fit are equally likely (Random) + algorithm.EqualPriority, &storeToPodLister{podCache}, r) return &scheduler.Config{