From 21e63cf75a26e41758742e4d15b6353b3bae12e2 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Sat, 28 Jun 2014 14:21:08 -0700 Subject: [PATCH 1/3] Move scheduler to own package. --- pkg/{registry => scheduler}/scheduler.go | 42 +++++++++++-------- pkg/{registry => scheduler}/scheduler_test.go | 0 2 files changed, 24 insertions(+), 18 deletions(-) rename pkg/{registry => scheduler}/scheduler.go (74%) rename pkg/{registry => scheduler}/scheduler_test.go (100%) diff --git a/pkg/registry/scheduler.go b/pkg/scheduler/scheduler.go similarity index 74% rename from pkg/registry/scheduler.go rename to pkg/scheduler/scheduler.go index e0518259cb..23e358fa3f 100644 --- a/pkg/registry/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -24,26 +24,36 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) +// Anything that can list minions for a scheduler. +type MinionLister interface { + List() (machines []string, err error) +} + +// Make a MinionLister from a []string +type StringMinionLister []string + +func (s StringMinionLister) List() ([]string, error) { + return []string(s), nil +} + // Scheduler is an interface implemented by things that know how to schedule pods onto machines. type Scheduler interface { - Schedule(api.Pod) (string, error) + Schedule(api.Pod, MinionLister) (string, error) } // RandomScheduler choses machines uniformly at random. type RandomScheduler struct { - machines MinionRegistry - random rand.Rand + random rand.Rand } -func MakeRandomScheduler(machines MinionRegistry, random rand.Rand) Scheduler { +func MakeRandomScheduler(random rand.Rand) Scheduler { return &RandomScheduler{ - machines: machines, - random: random, + random: random, } } -func (s *RandomScheduler) Schedule(pod api.Pod) (string, error) { - machines, err := s.machines.List() +func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { + machines, err := minionLister.List() if err != nil { return "", err } @@ -52,19 +62,17 @@ func (s *RandomScheduler) Schedule(pod api.Pod) (string, error) { // RoundRobinScheduler chooses machines in order. type RoundRobinScheduler struct { - machines MinionRegistry currentIndex int } -func MakeRoundRobinScheduler(machines MinionRegistry) Scheduler { +func MakeRoundRobinScheduler() Scheduler { return &RoundRobinScheduler{ - machines: machines, currentIndex: -1, } } -func (s *RoundRobinScheduler) Schedule(pod api.Pod) (string, error) { - machines, err := s.machines.List() +func (s *RoundRobinScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { + machines, err := minionLister.List() if err != nil { return "", err } @@ -74,14 +82,12 @@ func (s *RoundRobinScheduler) Schedule(pod api.Pod) (string, error) { } type FirstFitScheduler struct { - machines MinionRegistry registry PodRegistry random *rand.Rand } -func MakeFirstFitScheduler(machines MinionRegistry, registry PodRegistry, random *rand.Rand) Scheduler { +func MakeFirstFitScheduler(registry PodRegistry, random *rand.Rand) Scheduler { return &FirstFitScheduler{ - machines: machines, registry: registry, random: random, } @@ -98,8 +104,8 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { return false } -func (s *FirstFitScheduler) Schedule(pod api.Pod) (string, error) { - machines, err := s.machines.List() +func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { + machines, err := minionLister.List() if err != nil { return "", err } diff --git a/pkg/registry/scheduler_test.go b/pkg/scheduler/scheduler_test.go similarity index 100% rename from pkg/registry/scheduler_test.go rename to pkg/scheduler/scheduler_test.go From 0760e9bc2cfbe5f70bc6b168b58baf100f513e56 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Sat, 28 Jun 2014 15:35:51 -0700 Subject: [PATCH 2/3] Fix up usage and tests, split into multiple files. Doing this in multiple commits in an attempt to preserve the file movement history. --- pkg/master/master.go | 5 +- pkg/registry/pod_registry.go | 25 +++++-- pkg/scheduler/doc.go | 19 +++++ pkg/scheduler/firstfit.go | 86 +++++++++++++++++++++ pkg/scheduler/firstfit_test.go | 78 ++++++++++++++++++++ pkg/scheduler/listers.go | 52 +++++++++++++ pkg/scheduler/random.go | 43 +++++++++++ pkg/scheduler/random_test.go | 34 +++++++++ pkg/scheduler/roundrobin.go | 42 +++++++++++ pkg/scheduler/roundrobin_test.go | 35 +++++++++ pkg/scheduler/scheduler.go | 123 +------------------------------ pkg/scheduler/scheduler_test.go | 94 ++++++++--------------- 12 files changed, 444 insertions(+), 192 deletions(-) create mode 100644 pkg/scheduler/doc.go create mode 100644 pkg/scheduler/firstfit.go create mode 100644 pkg/scheduler/firstfit_test.go create mode 100644 pkg/scheduler/listers.go create mode 100644 pkg/scheduler/random.go create mode 100644 pkg/scheduler/random_test.go create mode 100644 pkg/scheduler/roundrobin.go create mode 100644 pkg/scheduler/roundrobin_test.go diff --git a/pkg/master/master.go b/pkg/master/master.go index c57d790b64..1c60eaba5a 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry" + "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" "github.com/golang/glog" @@ -37,6 +38,7 @@ type Master struct { serviceRegistry registry.ServiceRegistry minionRegistry registry.MinionRegistry + // TODO: don't reuse non-threadsafe objects. random *rand.Rand storage map[string]apiserver.RESTStorage } @@ -86,8 +88,9 @@ func (m *Master) init(cloud cloudprovider.Interface) { m.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) podCache := NewPodCache(containerInfo, m.podRegistry, time.Second*30) go podCache.Loop() + s := scheduler.MakeFirstFitScheduler(m.podRegistry, m.random) m.storage = map[string]apiserver.RESTStorage{ - "pods": registry.MakePodRegistryStorage(m.podRegistry, containerInfo, registry.MakeFirstFitScheduler(m.minionRegistry, m.podRegistry, m.random), cloud, podCache), + "pods": registry.MakePodRegistryStorage(m.podRegistry, containerInfo, s, m.minionRegistry, cloud, podCache), "replicationControllers": registry.MakeControllerRegistryStorage(m.controllerRegistry, m.podRegistry), "services": registry.MakeServiceRegistryStorage(m.serviceRegistry, cloud, m.minionRegistry), "minions": registry.MakeMinionRegistryStorage(m.minionRegistry), diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index 708dfb24ac..c8a1856c13 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" "github.com/golang/glog" ) @@ -33,22 +34,30 @@ type PodRegistryStorage struct { registry PodRegistry containerInfo client.ContainerInfo podCache client.ContainerInfo - scheduler Scheduler + scheduler scheduler.Scheduler + minionLister scheduler.MinionLister cloud cloudprovider.Interface } // MakePodRegistryStorage makes a RESTStorage object for a pod registry. // Parameters: -// registry The pod registry -// containerInfo Source of fresh container info -// scheduler The scheduler for assigning pods to machines -// cloud Interface to a cloud provider (may be null) -// podCache Source of cached container info -func MakePodRegistryStorage(registry PodRegistry, containerInfo client.ContainerInfo, scheduler Scheduler, cloud cloudprovider.Interface, podCache client.ContainerInfo) apiserver.RESTStorage { +// registry: The pod registry +// containerInfo: Source of fresh container info +// scheduler: The scheduler for assigning pods to machines +// minionLister: Object which can list available minions for the scheduler +// cloud: Interface to a cloud provider (may be null) +// podCache: Source of cached container info +func MakePodRegistryStorage(registry PodRegistry, + containerInfo client.ContainerInfo, + scheduler scheduler.Scheduler, + minionLister scheduler.MinionLister, + cloud cloudprovider.Interface, + podCache client.ContainerInfo) apiserver.RESTStorage { return &PodRegistryStorage{ registry: registry, containerInfo: containerInfo, scheduler: scheduler, + minionLister: minionLister, cloud: cloud, podCache: podCache, } @@ -150,7 +159,7 @@ func (storage *PodRegistryStorage) Create(obj interface{}) (<-chan interface{}, return apiserver.MakeAsync(func() (interface{}, error) { // TODO(lavalamp): Separate scheduler more cleanly. - machine, err := storage.scheduler.Schedule(pod) + machine, err := storage.scheduler.Schedule(pod, storage.minionLister) if err != nil { return nil, err } diff --git a/pkg/scheduler/doc.go b/pkg/scheduler/doc.go new file mode 100644 index 0000000000..1e194acd85 --- /dev/null +++ b/pkg/scheduler/doc.go @@ -0,0 +1,19 @@ +/* +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 contains a generic Scheduler interface and several +// implementations. +package scheduler diff --git a/pkg/scheduler/firstfit.go b/pkg/scheduler/firstfit.go new file mode 100644 index 0000000000..e98c9a9693 --- /dev/null +++ b/pkg/scheduler/firstfit.go @@ -0,0 +1,86 @@ +/* +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 ( + "fmt" + "math/rand" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" +) + +type FirstFitScheduler struct { + podLister PodLister + // TODO: *rand.Rand is *not* threadsafe + random *rand.Rand +} + +func MakeFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { + return &FirstFitScheduler{ + podLister: podLister, + random: random, + } +} + +func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { + for _, container := range pod.DesiredState.Manifest.Containers { + for _, podPort := range container.Ports { + if podPort.HostPort == port.HostPort { + return true + } + } + } + return false +} + +func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { + machines, err := minionLister.List() + if err != nil { + return "", err + } + machineToPods := map[string][]api.Pod{} + pods, err := s.podLister.ListPods(labels.Everything()) + if err != nil { + return "", err + } + for _, scheduledPod := range pods { + host := scheduledPod.CurrentState.Host + machineToPods[host] = append(machineToPods[host], scheduledPod) + } + var machineOptions []string + for _, machine := range machines { + podFits := true + for _, scheduledPod := range machineToPods[machine] { + for _, container := range pod.DesiredState.Manifest.Containers { + for _, port := range container.Ports { + if s.containsPort(scheduledPod, port) { + podFits = false + } + } + } + } + if podFits { + machineOptions = append(machineOptions, machine) + } + } + if len(machineOptions) == 0 { + return "", fmt.Errorf("failed to find fit for %#v", pod) + } else { + return machineOptions[s.random.Int()%len(machineOptions)], nil + } +} diff --git a/pkg/scheduler/firstfit_test.go b/pkg/scheduler/firstfit_test.go new file mode 100644 index 0000000000..57e8444b0e --- /dev/null +++ b/pkg/scheduler/firstfit_test.go @@ -0,0 +1,78 @@ +/* +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 TestFirstFitSchedulerNothingScheduled(t *testing.T) { + fakeRegistry := FakePodLister{} + r := rand.New(rand.NewSource(0)) + st := schedulerTester{ + t: t, + scheduler: MakeFirstFitScheduler(&fakeRegistry, r), + minionLister: FakeMinionLister{"m1", "m2", "m3"}, + } + st.expectSchedule(api.Pod{}, "m3") +} + +func TestFirstFitSchedulerFirstScheduled(t *testing.T) { + fakeRegistry := FakePodLister{ + makePod("m1", 8080), + } + r := rand.New(rand.NewSource(0)) + st := schedulerTester{ + t: t, + scheduler: MakeFirstFitScheduler(fakeRegistry, r), + minionLister: FakeMinionLister{"m1", "m2", "m3"}, + } + st.expectSchedule(makePod("", 8080), "m3") +} + +func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) { + fakeRegistry := FakePodLister{ + makePod("m1", 80, 8080), + makePod("m2", 8081, 8082, 8083), + makePod("m3", 80, 443, 8085), + } + r := rand.New(rand.NewSource(0)) + st := schedulerTester{ + t: t, + scheduler: MakeFirstFitScheduler(fakeRegistry, r), + minionLister: FakeMinionLister{"m1", "m2", "m3"}, + } + st.expectSchedule(makePod("", 8080, 8081), "m3") +} + +func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) { + fakeRegistry := FakePodLister{ + makePod("m1", 8080), + makePod("m2", 8081), + makePod("m3", 8080), + } + r := rand.New(rand.NewSource(0)) + st := schedulerTester{ + t: t, + scheduler: MakeFirstFitScheduler(fakeRegistry, r), + minionLister: FakeMinionLister{"m1", "m2", "m3"}, + } + st.expectFailure(makePod("", 8080, 8081)) +} diff --git a/pkg/scheduler/listers.go b/pkg/scheduler/listers.go new file mode 100644 index 0000000000..ad7a41562f --- /dev/null +++ b/pkg/scheduler/listers.go @@ -0,0 +1,52 @@ +/* +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/labels" +) + +// Anything that can list minions for a scheduler. +type MinionLister interface { + List() (machines []string, err error) +} + +// Make a MinionLister from a []string +type FakeMinionLister []string + +// Returns minions as a []string +func (f FakeMinionLister) List() ([]string, error) { + return []string(f), nil +} + +// Anything that can list pods for a scheduler +type PodLister interface { + ListPods(labels.Selector) ([]api.Pod, error) +} + +// Make a MinionLister from an []api.Pods +type FakePodLister []api.Pod + +func (f FakePodLister) ListPods(s labels.Selector) (selected []api.Pod, err error) { + for _, pod := range f { + if s.Matches(labels.Set(pod.Labels)) { + selected = append(selected, pod) + } + } + return selected, nil +} diff --git a/pkg/scheduler/random.go b/pkg/scheduler/random.go new file mode 100644 index 0000000000..6d268a33f0 --- /dev/null +++ b/pkg/scheduler/random.go @@ -0,0 +1,43 @@ +/* +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" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" +) + +// RandomScheduler choses machines uniformly at random. +type RandomScheduler struct { + // TODO: rand.Rand is *NOT* thread safe. + random *rand.Rand +} + +func MakeRandomScheduler(random *rand.Rand) Scheduler { + return &RandomScheduler{ + random: random, + } +} + +func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { + machines, err := minionLister.List() + if err != nil { + return "", err + } + return machines[s.random.Int()%len(machines)], nil +} diff --git a/pkg/scheduler/random_test.go b/pkg/scheduler/random_test.go new file mode 100644 index 0000000000..142b5f6436 --- /dev/null +++ b/pkg/scheduler/random_test.go @@ -0,0 +1,34 @@ +/* +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: MakeRandomScheduler(random), + minionLister: FakeMinionLister{"m1", "m2", "m3", "m4"}, + } + st.expectSuccess(api.Pod{}) +} diff --git a/pkg/scheduler/roundrobin.go b/pkg/scheduler/roundrobin.go new file mode 100644 index 0000000000..6d1df35fee --- /dev/null +++ b/pkg/scheduler/roundrobin.go @@ -0,0 +1,42 @@ +/* +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 MakeRoundRobinScheduler() Scheduler { + return &RoundRobinScheduler{ + currentIndex: -1, + } +} + +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 new file mode 100644 index 0000000000..3ce410a1df --- /dev/null +++ b/pkg/scheduler/roundrobin_test.go @@ -0,0 +1,35 @@ +/* +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: MakeRoundRobinScheduler(), + 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") +} diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go index 23e358fa3f..34e645fbe8 100644 --- a/pkg/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -14,129 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry +package scheduler import ( - "fmt" - "math/rand" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// Anything that can list minions for a scheduler. -type MinionLister interface { - List() (machines []string, err error) -} - -// Make a MinionLister from a []string -type StringMinionLister []string - -func (s StringMinionLister) List() ([]string, error) { - return []string(s), nil -} - -// Scheduler is an interface implemented by things that know how to schedule pods onto machines. +// Scheduler is an interface implemented by things that know how to schedule pods +// onto machines. type Scheduler interface { - Schedule(api.Pod, MinionLister) (string, error) -} - -// RandomScheduler choses machines uniformly at random. -type RandomScheduler struct { - random rand.Rand -} - -func MakeRandomScheduler(random rand.Rand) Scheduler { - return &RandomScheduler{ - random: random, - } -} - -func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { - machines, err := minionLister.List() - if err != nil { - return "", err - } - return machines[s.random.Int()%len(machines)], nil -} - -// RoundRobinScheduler chooses machines in order. -type RoundRobinScheduler struct { - currentIndex int -} - -func MakeRoundRobinScheduler() Scheduler { - return &RoundRobinScheduler{ - currentIndex: -1, - } -} - -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 -} - -type FirstFitScheduler struct { - registry PodRegistry - random *rand.Rand -} - -func MakeFirstFitScheduler(registry PodRegistry, random *rand.Rand) Scheduler { - return &FirstFitScheduler{ - registry: registry, - random: random, - } -} - -func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { - for _, container := range pod.DesiredState.Manifest.Containers { - for _, podPort := range container.Ports { - if podPort.HostPort == port.HostPort { - return true - } - } - } - return false -} - -func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { - machines, err := minionLister.List() - if err != nil { - return "", err - } - machineToPods := map[string][]api.Pod{} - pods, err := s.registry.ListPods(labels.Everything()) - if err != nil { - return "", err - } - for _, scheduledPod := range pods { - host := scheduledPod.CurrentState.Host - machineToPods[host] = append(machineToPods[host], scheduledPod) - } - var machineOptions []string - for _, machine := range machines { - podFits := true - for _, scheduledPod := range machineToPods[machine] { - for _, container := range pod.DesiredState.Manifest.Containers { - for _, port := range container.Ports { - if s.containsPort(scheduledPod, port) { - podFits = false - } - } - } - } - if podFits { - machineOptions = append(machineOptions, machine) - } - } - if len(machineOptions) == 0 { - return "", fmt.Errorf("failed to find fit for %#v", pod) - } else { - return machineOptions[s.random.Int()%len(machineOptions)], nil - } + Schedule(api.Pod, MinionLister) (selectedMachine string, err error) } diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go index e32304a83b..bfec2dfc41 100644 --- a/pkg/scheduler/scheduler_test.go +++ b/pkg/scheduler/scheduler_test.go @@ -14,43 +14,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry +package scheduler import ( - "math/rand" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -func expectSchedule(scheduler Scheduler, pod api.Pod, expected string, t *testing.T) { - actual, err := scheduler.Schedule(pod) - expectNoError(t, err) +// Some functions used by multiple scheduler tests. + +type schedulerTester struct { + t *testing.T + scheduler Scheduler + minionLister MinionLister +} + +// Call if you know exactly where pod should get scheduled. +func (st *schedulerTester) expectSchedule(pod api.Pod, expected string) { + actual, err := st.scheduler.Schedule(pod, st.minionLister) + if err != nil { + st.t.Errorf("Unexpected error %v\nTried to scheduler: %#v", err, pod) + return + } if actual != expected { - t.Errorf("Unexpected scheduling value: %v, expected %v", actual, expected) + st.t.Errorf("Unexpected scheduling value: %v, expected %v", actual, expected) } } -func TestRoundRobinScheduler(t *testing.T) { - scheduler := MakeRoundRobinScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3", "m4"})) - expectSchedule(scheduler, api.Pod{}, "m1", t) - expectSchedule(scheduler, api.Pod{}, "m2", t) - expectSchedule(scheduler, api.Pod{}, "m3", t) - expectSchedule(scheduler, api.Pod{}, "m4", t) +// Call if you can't predict where pod will be scheduled. +func (st *schedulerTester) expectSuccess(pod api.Pod) { + _, err := st.scheduler.Schedule(pod, st.minionLister) + if err != nil { + st.t.Errorf("Unexpected error %v\nTried to scheduler: %#v", err, pod) + return + } } -func TestRandomScheduler(t *testing.T) { - random := rand.New(rand.NewSource(0)) - scheduler := MakeRandomScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3", "m4"}), *random) - _, err := scheduler.Schedule(api.Pod{}) - expectNoError(t, err) -} - -func TestFirstFitSchedulerNothingScheduled(t *testing.T) { - mockRegistry := MockPodRegistry{} - r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) - expectSchedule(scheduler, api.Pod{}, "m3", t) +// Call if pod should *not* schedule. +func (st *schedulerTester) expectFailure(pod api.Pod) { + _, err := st.scheduler.Schedule(pod, st.minionLister) + if err == nil { + st.t.Error("Unexpected non-error") + } } func makePod(host string, hostPorts ...int) api.Pod { @@ -73,43 +79,3 @@ func makePod(host string, hostPorts ...int) api.Pod { }, } } - -func TestFirstFitSchedulerFirstScheduled(t *testing.T) { - mockRegistry := MockPodRegistry{ - pods: []api.Pod{ - makePod("m1", 8080), - }, - } - r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) - expectSchedule(scheduler, makePod("", 8080), "m3", t) -} - -func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) { - mockRegistry := MockPodRegistry{ - pods: []api.Pod{ - makePod("m1", 80, 8080), - makePod("m2", 8081, 8082, 8083), - makePod("m3", 80, 443, 8085), - }, - } - r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) - expectSchedule(scheduler, makePod("", 8080, 8081), "m3", t) -} - -func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) { - mockRegistry := MockPodRegistry{ - pods: []api.Pod{ - makePod("m1", 8080), - makePod("m2", 8081), - makePod("m3", 8080), - }, - } - r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) - _, err := scheduler.Schedule(makePod("", 8080, 8081)) - if err == nil { - t.Error("Unexpected non-error.") - } -} From 011ce9d871d1a6355f58abf1abcecadf0d0fb1c0 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Sat, 28 Jun 2014 17:52:08 -0700 Subject: [PATCH 3/3] Add crash handler bypass for testing. --- cmd/integration/integration.go | 1 + pkg/util/util.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 40fee2c686..2b4598a17a 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -38,6 +38,7 @@ import ( func main() { runtime.GOMAXPROCS(4) + util.ReallyCrash = true util.InitLogs() defer util.FlushLogs() diff --git a/pkg/util/util.go b/pkg/util/util.go index 65a645af75..ba8eb2753e 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -25,8 +25,15 @@ import ( "github.com/golang/glog" ) +// For testing, bypass HandleCrash. +var ReallyCrash bool + // Simply catches a crash and logs an error. Meant to be called via defer. func HandleCrash() { + if ReallyCrash { + return + } + r := recover() if r != nil { callers := ""