From 5d4d60783db00a057d99bd3352e019bc297e8750 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 21 Oct 2014 17:13:52 -0700 Subject: [PATCH] Add requirements based scheduling. --- pkg/api/serialization_test.go | 4 +- pkg/api/types.go | 4 ++ pkg/api/v1beta1/conversion.go | 14 ++++++ pkg/api/v1beta1/types.go | 4 ++ pkg/api/v1beta2/conversion.go | 14 ++++++ pkg/api/v1beta2/types.go | 4 ++ pkg/api/v1beta3/types.go | 2 + pkg/kubectl/resource_printer_test.go | 2 +- pkg/scheduler/predicates.go | 23 +++++++++ pkg/scheduler/predicates_test.go | 74 ++++++++++++++++++++++++++++ 10 files changed, 142 insertions(+), 3 deletions(-) diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 44765979be6..16a0e72e1e7 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -141,7 +141,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { return } else { if !reflect.DeepEqual(source, obj2) { - t.Errorf("1: %v: diff: %v", name, util.ObjectDiff(source, obj2)) + t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v", name, util.ObjectDiff(source, obj2), codec, string(data), source) return } } @@ -152,7 +152,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { return } else { if !reflect.DeepEqual(source, obj3) { - t.Errorf("3: %v: diff: %v", name, util.ObjectDiff(source, obj3)) + t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(source, obj3), codec) return } } diff --git a/pkg/api/types.go b/pkg/api/types.go index fe152b7a4c9..baf7eefab6d 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -424,6 +424,8 @@ type Pod struct { DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` + // NodeSelector is a selector which must be true for the pod to fit on a node + NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"` } // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). @@ -531,6 +533,8 @@ type Minion struct { HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` // Resources available on the node NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"` + // Labels for the node + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` } // MinionList is a list of minions. diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 9f393d91a2f..cb27b823fca 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -177,6 +177,10 @@ func init() { if err := s.Convert(&in.CurrentState, &out.CurrentState, 0); err != nil { return err } + + if err := s.Convert(&in.NodeSelector, &out.NodeSelector, 0); err != nil { + return err + } return nil }, func(in *Pod, out *newer.Pod, s conversion.Scope) error { @@ -196,6 +200,10 @@ func init() { if err := s.Convert(&in.CurrentState, &out.CurrentState, 0); err != nil { return err } + + if err := s.Convert(&in.NodeSelector, &out.NodeSelector, 0); err != nil { + return err + } return nil }, @@ -348,6 +356,9 @@ func init() { if err := s.Convert(&in.ObjectMeta, &out.TypeMeta, 0); err != nil { return err } + if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil { + return err + } out.HostIP = in.HostIP return s.Convert(&in.NodeResources, &out.NodeResources, 0) @@ -359,6 +370,9 @@ func init() { if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil { return err } + if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil { + return err + } out.HostIP = in.HostIP return s.Convert(&in.NodeResources, &out.NodeResources, 0) diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 12b576a2e84..b3420f21fcc 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -395,6 +395,8 @@ type Pod struct { Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` + // NodeSelector is a selector which must be true for the pod to fit on a node + NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"` } // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). @@ -491,6 +493,8 @@ type Minion struct { HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` // Resources available on the node NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"` + // Labels for the node + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` } // MinionList is a list of minions. diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index f89def719d3..b16998ae2fd 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -108,6 +108,10 @@ func init() { if err := s.Convert(&in.CurrentState, &out.CurrentState, 0); err != nil { return err } + + if err := s.Convert(&in.NodeSelector, &out.NodeSelector, 0); err != nil { + return err + } return nil }, func(in *Pod, out *newer.Pod, s conversion.Scope) error { @@ -127,6 +131,10 @@ func init() { if err := s.Convert(&in.CurrentState, &out.CurrentState, 0); err != nil { return err } + + if err := s.Convert(&in.NodeSelector, &out.NodeSelector, 0); err != nil { + return err + } return nil }, @@ -279,6 +287,9 @@ func init() { if err := s.Convert(&in.ObjectMeta, &out.TypeMeta, 0); err != nil { return err } + if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil { + return err + } out.HostIP = in.HostIP return s.Convert(&in.NodeResources, &out.NodeResources, 0) @@ -290,6 +301,9 @@ func init() { if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil { return err } + if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil { + return err + } out.HostIP = in.HostIP return s.Convert(&in.NodeResources, &out.NodeResources, 0) diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 858cabc1c25..43ea1383230 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -360,6 +360,8 @@ type Pod struct { Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` + // NodeSelector is a selector which must be true for the pod to fit on a node + NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"` } // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). @@ -456,6 +458,8 @@ type Minion struct { HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` // Resources available on the node NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"` + // Labels for the node + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` } // MinionList is a list of minions. diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index c41c54f3c33..122810e1b18 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -432,6 +432,8 @@ type PodSpec struct { Volumes []Volume `json:"volumes" yaml:"volumes"` Containers []Container `json:"containers" yaml:"containers"` RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" yaml:"restartPolicy,omitempty"` + // NodeSelector is a selector which must be true for the pod to fit on a node + NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"` } // PodStatus represents information about the status of a pod. Status may trail the actual diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index 75850090021..4110233f982 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -79,7 +79,7 @@ func testPrinter(t *testing.T, printer ResourcePrinter, unmarshalFunc func(data t.Errorf("Unexpeted error: %#v", err) } if !reflect.DeepEqual(obj, &objOut) { - t.Errorf("Unexpected inequality: %#v vs %#v", obj, &objOut) + t.Errorf("Unexpected inequality:\n%#v \nvs\n%#v", obj, &objOut) } } diff --git a/pkg/scheduler/predicates.go b/pkg/scheduler/predicates.go index 7c51407e7c1..b6f78557666 100644 --- a/pkg/scheduler/predicates.go +++ b/pkg/scheduler/predicates.go @@ -139,6 +139,29 @@ func NewResourceFitPredicate(info NodeInfo) FitPredicate { return fit.PodFitsResources } +func NewSelectorMatchPredicate(info NodeInfo) FitPredicate { + selector := &NodeSelector{ + info: info, + } + return selector.PodSelectorMatches +} + +type NodeSelector struct { + info NodeInfo +} + +func (n *NodeSelector) PodSelectorMatches(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { + if len(pod.NodeSelector) == 0 { + return true, nil + } + selector := labels.SelectorFromSet(pod.NodeSelector) + minion, err := n.info.GetNodeInfo(node) + if err != nil { + return false, err + } + return selector.Matches(labels.Set(minion.Labels)), nil +} + func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { for _, scheduledPod := range existingPods { for _, container := range pod.DesiredState.Manifest.Containers { diff --git a/pkg/scheduler/predicates_test.go b/pkg/scheduler/predicates_test.go index 91a55371f70..a8a5c69e18d 100644 --- a/pkg/scheduler/predicates_test.go +++ b/pkg/scheduler/predicates_test.go @@ -234,3 +234,77 @@ func TestDiskConflicts(t *testing.T) { } } } + +func TestPodFitsSelector(t *testing.T) { + tests := []struct { + pod api.Pod + labels map[string]string + fits bool + test string + }{ + { + pod: api.Pod{}, + fits: true, + test: "no selector", + }, + { + pod: api.Pod{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + }, + fits: false, + test: "missing labels", + }, + { + pod: api.Pod{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + }, + labels: map[string]string{ + "foo": "bar", + }, + fits: true, + test: "same labels", + }, + { + pod: api.Pod{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + }, + labels: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + fits: true, + test: "node labels are superset", + }, + { + pod: api.Pod{ + NodeSelector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + }, + labels: map[string]string{ + "foo": "bar", + }, + fits: false, + test: "node labels are subset", + }, + } + for _, test := range tests { + node := api.Minion{Labels: test.labels} + + fit := NodeSelector{FakeNodeInfo(node)} + fits, err := fit.PodSelectorMatches(test.pod, []api.Pod{}, "machine") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if fits != test.fits { + t.Errorf("%s: expected: %v got %v", test.test, test.fits, fits) + } + } +}