2014-08-20 21:34:55 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2014-08-20 21:34:55 +00:00
|
|
|
|
|
|
|
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 factory
|
|
|
|
|
|
|
|
import (
|
2014-08-29 03:18:27 +00:00
|
|
|
"net/http"
|
2014-08-20 21:34:55 +00:00
|
|
|
"net/http/httptest"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
2014-08-20 22:03:32 +00:00
|
|
|
"time"
|
2014-08-20 21:34:55 +00:00
|
|
|
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/testapi"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/cache"
|
2015-09-03 21:43:19 +00:00
|
|
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/runtime"
|
2015-09-23 22:24:49 +00:00
|
|
|
"k8s.io/kubernetes/pkg/types"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util"
|
|
|
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
|
|
|
schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api"
|
|
|
|
latestschedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest"
|
2014-08-20 21:34:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestCreate(t *testing.T) {
|
|
|
|
handler := util.FakeHandler{
|
|
|
|
StatusCode: 500,
|
|
|
|
ResponseBody: "",
|
|
|
|
T: t,
|
|
|
|
}
|
|
|
|
server := httptest.NewServer(&handler)
|
2014-10-31 01:15:44 +00:00
|
|
|
defer server.Close()
|
2015-09-04 07:06:01 +00:00
|
|
|
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Default.Version()})
|
2015-07-27 15:22:38 +00:00
|
|
|
factory := NewConfigFactory(client, nil)
|
2014-12-10 04:25:45 +00:00
|
|
|
factory.Create()
|
2014-08-20 21:34:55 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 17:59:29 +00:00
|
|
|
// Test configures a scheduler from a policies defined in a file
|
|
|
|
// It combines some configurable predicate/priorities with some pre-defined ones
|
2015-02-27 20:53:04 +00:00
|
|
|
func TestCreateFromConfig(t *testing.T) {
|
|
|
|
var configData []byte
|
|
|
|
var policy schedulerapi.Policy
|
|
|
|
|
|
|
|
handler := util.FakeHandler{
|
|
|
|
StatusCode: 500,
|
|
|
|
ResponseBody: "",
|
|
|
|
T: t,
|
|
|
|
}
|
|
|
|
server := httptest.NewServer(&handler)
|
|
|
|
defer server.Close()
|
2015-09-04 07:06:01 +00:00
|
|
|
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Default.Version()})
|
2015-07-27 15:22:38 +00:00
|
|
|
factory := NewConfigFactory(client, nil)
|
2015-02-27 20:53:04 +00:00
|
|
|
|
2015-03-02 17:59:29 +00:00
|
|
|
// Pre-register some predicate and priority functions
|
|
|
|
RegisterFitPredicate("PredicateOne", PredicateOne)
|
|
|
|
RegisterFitPredicate("PredicateTwo", PredicateTwo)
|
|
|
|
RegisterPriorityFunction("PriorityOne", PriorityOne, 1)
|
|
|
|
RegisterPriorityFunction("PriorityTwo", PriorityTwo, 1)
|
2015-02-27 20:53:04 +00:00
|
|
|
|
|
|
|
configData = []byte(`{
|
|
|
|
"kind" : "Policy",
|
|
|
|
"apiVersion" : "v1",
|
|
|
|
"predicates" : [
|
|
|
|
{"name" : "TestZoneAffinity", "argument" : {"serviceAffinity" : {"labels" : ["zone"]}}},
|
|
|
|
{"name" : "TestRequireZone", "argument" : {"labelsPresence" : {"labels" : ["zone"], "presence" : true}}},
|
2015-03-02 17:59:29 +00:00
|
|
|
{"name" : "PredicateOne"},
|
|
|
|
{"name" : "PredicateTwo"}
|
2015-02-27 20:53:04 +00:00
|
|
|
],
|
|
|
|
"priorities" : [
|
2015-03-02 17:59:29 +00:00
|
|
|
{"name" : "RackSpread", "weight" : 3, "argument" : {"serviceAntiAffinity" : {"label" : "rack"}}},
|
|
|
|
{"name" : "PriorityOne", "weight" : 2},
|
|
|
|
{"name" : "PriorityTwo", "weight" : 1} ]
|
2015-02-27 20:53:04 +00:00
|
|
|
}`)
|
|
|
|
err := latestschedulerapi.Codec.DecodeInto(configData, &policy)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Invalid configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
factory.CreateFromConfig(policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateFromEmptyConfig(t *testing.T) {
|
|
|
|
var configData []byte
|
|
|
|
var policy schedulerapi.Policy
|
|
|
|
|
|
|
|
handler := util.FakeHandler{
|
|
|
|
StatusCode: 500,
|
|
|
|
ResponseBody: "",
|
|
|
|
T: t,
|
|
|
|
}
|
|
|
|
server := httptest.NewServer(&handler)
|
|
|
|
defer server.Close()
|
2015-09-04 07:06:01 +00:00
|
|
|
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Default.Version()})
|
2015-07-27 15:22:38 +00:00
|
|
|
factory := NewConfigFactory(client, nil)
|
2015-02-27 20:53:04 +00:00
|
|
|
|
|
|
|
configData = []byte(`{}`)
|
|
|
|
err := latestschedulerapi.Codec.DecodeInto(configData, &policy)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Invalid configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
factory.CreateFromConfig(policy)
|
|
|
|
}
|
|
|
|
|
2015-04-03 22:51:50 +00:00
|
|
|
func PredicateOne(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
2015-03-02 17:59:29 +00:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2015-04-03 22:51:50 +00:00
|
|
|
func PredicateTwo(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
2015-03-02 17:59:29 +00:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2015-09-10 08:40:22 +00:00
|
|
|
func PriorityOne(pod *api.Pod, podLister algorithm.PodLister, nodeLister algorithm.NodeLister) (algorithm.HostPriorityList, error) {
|
2015-03-02 17:59:29 +00:00
|
|
|
return []algorithm.HostPriority{}, nil
|
|
|
|
}
|
|
|
|
|
2015-09-10 08:40:22 +00:00
|
|
|
func PriorityTwo(pod *api.Pod, podLister algorithm.PodLister, nodeLister algorithm.NodeLister) (algorithm.HostPriorityList, error) {
|
2015-03-02 17:59:29 +00:00
|
|
|
return []algorithm.HostPriority{}, nil
|
|
|
|
}
|
|
|
|
|
2014-08-20 22:03:32 +00:00
|
|
|
func TestDefaultErrorFunc(t *testing.T) {
|
2015-08-19 23:59:43 +00:00
|
|
|
grace := int64(30)
|
2015-01-26 17:52:50 +00:00
|
|
|
testPod := &api.Pod{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
|
|
|
|
Spec: api.PodSpec{
|
2015-08-19 23:59:43 +00:00
|
|
|
RestartPolicy: api.RestartPolicyAlways,
|
|
|
|
DNSPolicy: api.DNSClusterFirst,
|
|
|
|
TerminationGracePeriodSeconds: &grace,
|
2015-01-26 17:52:50 +00:00
|
|
|
},
|
|
|
|
}
|
2014-08-20 22:03:32 +00:00
|
|
|
handler := util.FakeHandler{
|
|
|
|
StatusCode: 200,
|
2015-09-12 22:43:33 +00:00
|
|
|
ResponseBody: runtime.EncodeOrDie(testapi.Default.Codec(), testPod),
|
2014-08-20 22:03:32 +00:00
|
|
|
T: t,
|
|
|
|
}
|
2014-08-29 03:18:27 +00:00
|
|
|
mux := http.NewServeMux()
|
2014-12-19 21:32:42 +00:00
|
|
|
|
2014-08-29 03:18:27 +00:00
|
|
|
// FakeHandler musn't be sent requests other than the one you want to test.
|
2015-09-04 07:06:01 +00:00
|
|
|
mux.Handle(testapi.Default.ResourcePath("pods", "bar", "foo"), &handler)
|
2014-08-29 03:18:27 +00:00
|
|
|
server := httptest.NewServer(mux)
|
2014-10-31 01:15:44 +00:00
|
|
|
defer server.Close()
|
2015-09-04 07:06:01 +00:00
|
|
|
factory := NewConfigFactory(client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Default.Version()}), nil)
|
2015-01-26 21:44:53 +00:00
|
|
|
queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc)
|
2014-10-03 16:59:39 +00:00
|
|
|
podBackoff := podBackoff{
|
2015-09-23 22:24:49 +00:00
|
|
|
perPodBackoff: map[types.NamespacedName]*backoffEntry{},
|
2015-01-09 18:03:49 +00:00
|
|
|
clock: &fakeClock{},
|
|
|
|
defaultDuration: 1 * time.Millisecond,
|
|
|
|
maxDuration: 1 * time.Second,
|
2014-10-03 16:59:39 +00:00
|
|
|
}
|
|
|
|
errFunc := factory.makeDefaultErrorFunc(&podBackoff, queue)
|
2014-08-20 22:03:32 +00:00
|
|
|
|
|
|
|
errFunc(testPod, nil)
|
|
|
|
for {
|
|
|
|
// This is a terrible way to do this but I plan on replacing this
|
|
|
|
// whole error handling system in the future. The test will time
|
|
|
|
// out if something doesn't work.
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
2015-01-26 21:44:53 +00:00
|
|
|
got, exists, _ := queue.Get(testPod)
|
2014-08-20 22:03:32 +00:00
|
|
|
if !exists {
|
|
|
|
continue
|
|
|
|
}
|
2015-09-04 07:06:01 +00:00
|
|
|
handler.ValidateRequest(t, testapi.Default.ResourcePath("pods", "bar", "foo"), "GET", nil)
|
2014-08-20 22:03:32 +00:00
|
|
|
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("Expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-10 08:40:22 +00:00
|
|
|
func TestNodeEnumerator(t *testing.T) {
|
2014-12-08 03:44:27 +00:00
|
|
|
testList := &api.NodeList{
|
|
|
|
Items: []api.Node{
|
2014-10-23 20:51:34 +00:00
|
|
|
{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
|
|
|
{ObjectMeta: api.ObjectMeta{Name: "bar"}},
|
|
|
|
{ObjectMeta: api.ObjectMeta{Name: "baz"}},
|
2014-08-20 21:34:55 +00:00
|
|
|
},
|
|
|
|
}
|
2014-12-08 03:44:27 +00:00
|
|
|
me := nodeEnumerator{testList}
|
2014-08-20 21:34:55 +00:00
|
|
|
|
|
|
|
if e, a := 3, me.Len(); e != a {
|
|
|
|
t.Fatalf("expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
for i := range testList.Items {
|
2015-01-26 21:44:53 +00:00
|
|
|
gotObj := me.Get(i)
|
|
|
|
if e, a := testList.Items[i].Name, gotObj.(*api.Node).Name; e != a {
|
2014-08-20 21:34:55 +00:00
|
|
|
t.Errorf("Expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
if e, a := &testList.Items[i], gotObj; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("Expected %#v, got %v#", e, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-03 16:59:39 +00:00
|
|
|
type fakeClock struct {
|
|
|
|
t time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeClock) Now() time.Time {
|
|
|
|
return f.t
|
|
|
|
}
|
|
|
|
|
2014-08-20 21:34:55 +00:00
|
|
|
func TestBind(t *testing.T) {
|
|
|
|
table := []struct {
|
|
|
|
binding *api.Binding
|
|
|
|
}{
|
2015-02-16 04:43:45 +00:00
|
|
|
{binding: &api.Binding{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Namespace: api.NamespaceDefault,
|
2015-03-04 20:55:41 +00:00
|
|
|
Name: "foo",
|
|
|
|
},
|
|
|
|
Target: api.ObjectReference{
|
|
|
|
Name: "foohost.kubernetes.mydomain.com",
|
2015-02-16 04:43:45 +00:00
|
|
|
},
|
|
|
|
}},
|
2014-08-20 21:34:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range table {
|
|
|
|
handler := util.FakeHandler{
|
|
|
|
StatusCode: 200,
|
|
|
|
ResponseBody: "",
|
|
|
|
T: t,
|
|
|
|
}
|
|
|
|
server := httptest.NewServer(&handler)
|
2014-10-31 01:15:44 +00:00
|
|
|
defer server.Close()
|
2015-09-04 07:06:01 +00:00
|
|
|
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Default.Version()})
|
2014-08-28 13:56:38 +00:00
|
|
|
b := binder{client}
|
2014-08-20 21:34:55 +00:00
|
|
|
|
2014-08-28 13:56:38 +00:00
|
|
|
if err := b.Bind(item.binding); err != nil {
|
2014-08-20 21:34:55 +00:00
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
2015-09-04 07:06:01 +00:00
|
|
|
expectedBody := runtime.EncodeOrDie(testapi.Default.Codec(), item.binding)
|
|
|
|
handler.ValidateRequest(t, testapi.Default.ResourcePath("bindings", api.NamespaceDefault, ""), "POST", &expectedBody)
|
2014-08-20 21:34:55 +00:00
|
|
|
}
|
|
|
|
}
|
2014-10-03 16:59:39 +00:00
|
|
|
|
|
|
|
func TestBackoff(t *testing.T) {
|
|
|
|
clock := fakeClock{}
|
|
|
|
backoff := podBackoff{
|
2015-09-23 22:24:49 +00:00
|
|
|
perPodBackoff: map[types.NamespacedName]*backoffEntry{},
|
2015-01-09 18:03:49 +00:00
|
|
|
clock: &clock,
|
|
|
|
defaultDuration: 1 * time.Second,
|
|
|
|
maxDuration: 60 * time.Second,
|
2014-10-03 16:59:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
2015-09-23 22:24:49 +00:00
|
|
|
podID types.NamespacedName
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration time.Duration
|
|
|
|
advanceClock time.Duration
|
|
|
|
}{
|
|
|
|
{
|
2015-09-23 22:24:49 +00:00
|
|
|
podID: types.NamespacedName{Namespace: "default", Name: "foo"},
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration: 1 * time.Second,
|
|
|
|
},
|
|
|
|
{
|
2015-09-23 22:24:49 +00:00
|
|
|
podID: types.NamespacedName{Namespace: "default", Name: "foo"},
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration: 2 * time.Second,
|
|
|
|
},
|
|
|
|
{
|
2015-09-23 22:24:49 +00:00
|
|
|
podID: types.NamespacedName{Namespace: "default", Name: "foo"},
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration: 4 * time.Second,
|
|
|
|
},
|
|
|
|
{
|
2015-09-23 22:24:49 +00:00
|
|
|
podID: types.NamespacedName{Namespace: "default", Name: "bar"},
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration: 1 * time.Second,
|
|
|
|
advanceClock: 120 * time.Second,
|
|
|
|
},
|
|
|
|
// 'foo' should have been gc'd here.
|
|
|
|
{
|
2015-09-23 22:24:49 +00:00
|
|
|
podID: types.NamespacedName{Namespace: "default", Name: "foo"},
|
2014-10-03 16:59:39 +00:00
|
|
|
expectedDuration: 1 * time.Second,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
2015-09-23 22:24:49 +00:00
|
|
|
duration := backoff.getEntry(test.podID).getBackoff(backoff.maxDuration)
|
2014-10-03 16:59:39 +00:00
|
|
|
if duration != test.expectedDuration {
|
|
|
|
t.Errorf("expected: %s, got %s for %s", test.expectedDuration.String(), duration.String(), test.podID)
|
|
|
|
}
|
|
|
|
clock.t = clock.t.Add(test.advanceClock)
|
|
|
|
backoff.gc()
|
|
|
|
}
|
2015-09-23 22:24:49 +00:00
|
|
|
fooID := types.NamespacedName{Namespace: "default", Name: "foo"}
|
|
|
|
backoff.perPodBackoff[fooID].backoff = 60 * time.Second
|
|
|
|
duration := backoff.getEntry(fooID).getBackoff(backoff.maxDuration)
|
2014-10-03 16:59:39 +00:00
|
|
|
if duration != 60*time.Second {
|
|
|
|
t.Errorf("expected: 60, got %s", duration.String())
|
|
|
|
}
|
2015-09-23 22:24:49 +00:00
|
|
|
// Verify that we split on namespaces correctly, same name, different namespace
|
|
|
|
fooID.Namespace = "other"
|
|
|
|
duration = backoff.getEntry(fooID).getBackoff(backoff.maxDuration)
|
|
|
|
if duration != 1*time.Second {
|
|
|
|
t.Errorf("expected: 1, got %s", duration.String())
|
|
|
|
}
|
2014-10-03 16:59:39 +00:00
|
|
|
}
|