diff --git a/pkg/api/types.go b/pkg/api/types.go index cd92fc6f08..1d4eb92738 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -452,6 +452,11 @@ type PodSpec struct { RestartPolicy RestartPolicy `json:"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"` + + // Host is a request to schedule this pod onto a specific host. If it is non-empty, + // the the scheduler simply schedules this pod onto that host, assuming that it fits + // resource requirements. + Host string `json:"host,omitempty"` } // PodStatus represents information about the status of a pod. Status may trail the actual diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 28b8087194..ae3621040b 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -204,12 +204,14 @@ func init() { if err := s.Convert(&in, &out.Manifest, 0); err != nil { return err } + out.Host = in.Host return nil }, func(in *PodState, out *newer.PodSpec, s conversion.Scope) error { if err := s.Convert(&in.Manifest, &out, 0); err != nil { return err } + out.Host = in.Host return nil }, @@ -265,6 +267,7 @@ func init() { if err := s.Convert(&in.Spec, &out.DesiredState.Manifest, 0); err != nil { return err } + out.DesiredState.Host = in.Spec.Host if err := s.Convert(&in.Status, &out.CurrentState, 0); err != nil { return err } @@ -286,6 +289,7 @@ func init() { if err := s.Convert(&in.DesiredState.Manifest, &out.Spec, 0); err != nil { return err } + out.Spec.Host = in.DesiredState.Host if err := s.Convert(&in.CurrentState, &out.Status, 0); err != nil { return err } @@ -361,6 +365,7 @@ func init() { if err := s.Convert(&in.Spec, &out.DesiredState.Manifest, 0); err != nil { return err } + out.DesiredState.Host = in.Spec.Host if err := s.Convert(&in.ObjectMeta.Labels, &out.Labels, 0); err != nil { return err } @@ -370,6 +375,7 @@ func init() { if err := s.Convert(&in.DesiredState.Manifest, &out.Spec, 0); err != nil { return err } + out.Spec.Host = in.DesiredState.Host if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil { return err } diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 5b05a3e74e..30847e1901 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -830,6 +830,11 @@ type PodSpec struct { RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" description:"restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"` // NodeSelector is a selector which must be true for the pod to fit on a node NodeSelector map[string]string `json:"nodeSelector,omitempty" description:"selector which must match a node's labels for the pod to be scheduled on that node"` + + // Host is a request to schedule this pod onto a specific host. If it is non-empty, + // the the scheduler simply schedules this pod onto that host, assuming that it fits + // resource requirements. + Host string `json:"host,omitempty" description:"host requested for this pod"` } // BoundPod is a collection of containers that should be run on a host. A BoundPod diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index 96e5ff6a5c..955266bbcf 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -155,6 +155,7 @@ func init() { if err := s.Convert(&in.Spec, &out.DesiredState.Manifest, 0); err != nil { return err } + out.DesiredState.Host = in.Spec.Host if err := s.Convert(&in.Status, &out.CurrentState, 0); err != nil { return err } @@ -176,6 +177,7 @@ func init() { if err := s.Convert(&in.DesiredState.Manifest, &out.Spec, 0); err != nil { return err } + out.Spec.Host = in.DesiredState.Host if err := s.Convert(&in.CurrentState, &out.Status, 0); err != nil { return err } @@ -251,6 +253,7 @@ func init() { if err := s.Convert(&in.Spec, &out.DesiredState.Manifest, 0); err != nil { return err } + out.DesiredState.Host = in.Spec.Host if err := s.Convert(&in.ObjectMeta.Labels, &out.Labels, 0); err != nil { return err } @@ -260,6 +263,7 @@ func init() { if err := s.Convert(&in.DesiredState.Manifest, &out.Spec, 0); err != nil { return err } + out.Spec.Host = in.DesiredState.Host if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil { return err } @@ -323,12 +327,14 @@ func init() { if err := s.Convert(&in, &out.Manifest, 0); err != nil { return err } + out.Host = in.Host return nil }, func(in *PodState, out *newer.PodSpec, s conversion.Scope) error { if err := s.Convert(&in.Manifest, &out, 0); err != nil { return err } + out.Host = in.Host return nil }, func(in *newer.Service, out *Service, s conversion.Scope) error { diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index cfb6303e35..090b11e336 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -831,6 +831,11 @@ type PodSpec struct { RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" description:"restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"` // NodeSelector is a selector which must be true for the pod to fit on a node NodeSelector map[string]string `json:"nodeSelector,omitempty" description:"selector which must match a node's labels for the pod to be scheduled on that node"` + + // Host is a request to schedule this pod onto a specific host. If it is non-empty, + // the the scheduler simply schedules this pod onto that host, assuming that it fits + // resource requirements. + Host string `json:"host,omitempty" description:"host requested for this pod"` } // BoundPod is a collection of containers that should be run on a host. A BoundPod diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index b6760df0f2..504071f5a6 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -461,6 +461,11 @@ type PodSpec struct { RestartPolicy RestartPolicy `json:"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"` + + // Host is a request to schedule this pod onto a specific host. If it is non-empty, + // the the scheduler simply schedules this pod onto that host, assuming that it fits + // other requirements. + Host string `json:"host,omitempty" description:"host requested for this pod"` } // PodStatus represents information about the status of a pod. Status may trail the actual diff --git a/pkg/scheduler/predicates.go b/pkg/scheduler/predicates.go index f6c7c2ebdb..6768016b83 100644 --- a/pkg/scheduler/predicates.go +++ b/pkg/scheduler/predicates.go @@ -162,6 +162,13 @@ func (n *NodeSelector) PodSelectorMatches(pod api.Pod, existingPods []api.Pod, n return selector.Matches(labels.Set(minion.Labels)), nil } +func PodFitsHost(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { + if len(pod.Spec.Host) == 0 { + return true, nil + } + return pod.Spec.Host == node, nil +} + func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { existingPorts := getUsedPorts(existingPods...) wantPorts := getUsedPorts(pod) diff --git a/pkg/scheduler/predicates_test.go b/pkg/scheduler/predicates_test.go index 1dd7498ea7..106efbcb42 100644 --- a/pkg/scheduler/predicates_test.go +++ b/pkg/scheduler/predicates_test.go @@ -124,6 +124,52 @@ func TestPodFitsResources(t *testing.T) { } } +func TestPodFitsHost(t *testing.T) { + tests := []struct { + pod api.Pod + node string + fits bool + test string + }{ + { + pod: api.Pod{}, + node: "foo", + fits: true, + test: "no host specified", + }, + { + pod: api.Pod{ + Spec: api.PodSpec{ + Host: "foo", + }, + }, + node: "foo", + fits: true, + test: "host matches", + }, + { + pod: api.Pod{ + Spec: api.PodSpec{ + Host: "bar", + }, + }, + node: "foo", + fits: false, + test: "host doesn't match", + }, + } + + for _, test := range tests { + result, err := PodFitsHost(test.pod, []api.Pod{}, test.node) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if result != test.fits { + t.Errorf("unexpected difference for %s: got: %v expected %v", test.test, test.fits, result) + } + } +} + func TestPodFitsPorts(t *testing.T) { tests := []struct { pod api.Pod diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go index d91f2d132c..e93af9a4e3 100644 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go +++ b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go @@ -37,6 +37,8 @@ func defaultPredicates() util.StringSet { factory.RegisterFitPredicate("NoDiskConflict", algorithm.NoDiskConflict), // Fit is determined by node selector query factory.RegisterFitPredicate("MatchNodeSelector", algorithm.NewSelectorMatchPredicate(factory.MinionLister)), + // Fit is determined by the presence of the Host parameter and a string match + factory.RegisterFitPredicate("HostName", algorithm.PodFitsHost), ) }