mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
Move pkg/scheduler to plugin/pkg/scheduler
As the TODO in plugin/pkg/scheduler/scheduler.go described: move everything from pkg/scheduler into this package. Remove references from registry.
This commit is contained in:
377
plugin/pkg/scheduler/algorithm/predicates/predicates.go
Normal file
377
plugin/pkg/scheduler/algorithm/predicates/predicates.go
Normal file
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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 predicates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
type NodeInfo interface {
|
||||
GetNodeInfo(nodeID string) (*api.Node, error)
|
||||
}
|
||||
|
||||
type StaticNodeInfo struct {
|
||||
*api.NodeList
|
||||
}
|
||||
|
||||
func (nodes StaticNodeInfo) GetNodeInfo(nodeID string) (*api.Node, error) {
|
||||
for ix := range nodes.Items {
|
||||
if nodes.Items[ix].Name == nodeID {
|
||||
return &nodes.Items[ix], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find node: %s, %#v", nodeID, nodes)
|
||||
}
|
||||
|
||||
type ClientNodeInfo struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
func (nodes ClientNodeInfo) GetNodeInfo(nodeID string) (*api.Node, error) {
|
||||
return nodes.Nodes().Get(nodeID)
|
||||
}
|
||||
|
||||
func isVolumeConflict(volume api.Volume, pod *api.Pod) bool {
|
||||
if volume.GCEPersistentDisk != nil {
|
||||
pdName := volume.GCEPersistentDisk.PDName
|
||||
|
||||
manifest := &(pod.Spec)
|
||||
for ix := range manifest.Volumes {
|
||||
if manifest.Volumes[ix].GCEPersistentDisk != nil &&
|
||||
manifest.Volumes[ix].GCEPersistentDisk.PDName == pdName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if volume.AWSElasticBlockStore != nil {
|
||||
volumeID := volume.AWSElasticBlockStore.VolumeID
|
||||
|
||||
manifest := &(pod.Spec)
|
||||
for ix := range manifest.Volumes {
|
||||
if manifest.Volumes[ix].AWSElasticBlockStore != nil &&
|
||||
manifest.Volumes[ix].AWSElasticBlockStore.VolumeID == volumeID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NoDiskConflict evaluates if a pod can fit due to the volumes it requests, and those that
|
||||
// are already mounted. Some times of volumes are mounted onto node machines. For now, these mounts
|
||||
// are exclusive so if there is already a volume mounted on that node, another pod can't schedule
|
||||
// there. This is GCE specific for now.
|
||||
// TODO: migrate this into some per-volume specific code?
|
||||
func NoDiskConflict(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
manifest := &(pod.Spec)
|
||||
for ix := range manifest.Volumes {
|
||||
for podIx := range existingPods {
|
||||
if isVolumeConflict(manifest.Volumes[ix], existingPods[podIx]) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type ResourceFit struct {
|
||||
info NodeInfo
|
||||
}
|
||||
|
||||
type resourceRequest struct {
|
||||
milliCPU int64
|
||||
memory int64
|
||||
}
|
||||
|
||||
func getResourceRequest(pod *api.Pod) resourceRequest {
|
||||
result := resourceRequest{}
|
||||
for ix := range pod.Spec.Containers {
|
||||
limits := pod.Spec.Containers[ix].Resources.Limits
|
||||
result.memory += limits.Memory().Value()
|
||||
result.milliCPU += limits.Cpu().MilliValue()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func CheckPodsExceedingCapacity(pods []*api.Pod, capacity api.ResourceList) (fitting []*api.Pod, notFitting []*api.Pod) {
|
||||
totalMilliCPU := capacity.Cpu().MilliValue()
|
||||
totalMemory := capacity.Memory().Value()
|
||||
milliCPURequested := int64(0)
|
||||
memoryRequested := int64(0)
|
||||
for _, pod := range pods {
|
||||
podRequest := getResourceRequest(pod)
|
||||
fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU
|
||||
fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory
|
||||
if !fitsCPU || !fitsMemory {
|
||||
// the pod doesn't fit
|
||||
notFitting = append(notFitting, pod)
|
||||
continue
|
||||
}
|
||||
// the pod fits
|
||||
milliCPURequested += podRequest.milliCPU
|
||||
memoryRequested += podRequest.memory
|
||||
fitting = append(fitting, pod)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PodFitsResources calculates fit based on requested, rather than used resources
|
||||
func (r *ResourceFit) PodFitsResources(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
podRequest := getResourceRequest(pod)
|
||||
if podRequest.milliCPU == 0 && podRequest.memory == 0 {
|
||||
// no resources requested always fits.
|
||||
return true, nil
|
||||
}
|
||||
info, err := r.info.GetNodeInfo(node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pods := []*api.Pod{}
|
||||
copy(pods, existingPods)
|
||||
pods = append(existingPods, pod)
|
||||
_, exceeding := CheckPodsExceedingCapacity(pods, info.Status.Capacity)
|
||||
if len(exceeding) > 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func NewResourceFitPredicate(info NodeInfo) algorithm.FitPredicate {
|
||||
fit := &ResourceFit{
|
||||
info: info,
|
||||
}
|
||||
return fit.PodFitsResources
|
||||
}
|
||||
|
||||
func NewSelectorMatchPredicate(info NodeInfo) algorithm.FitPredicate {
|
||||
selector := &NodeSelector{
|
||||
info: info,
|
||||
}
|
||||
return selector.PodSelectorMatches
|
||||
}
|
||||
|
||||
func PodMatchesNodeLabels(pod *api.Pod, node *api.Node) bool {
|
||||
if len(pod.Spec.NodeSelector) == 0 {
|
||||
return true
|
||||
}
|
||||
selector := labels.SelectorFromSet(pod.Spec.NodeSelector)
|
||||
return selector.Matches(labels.Set(node.Labels))
|
||||
}
|
||||
|
||||
type NodeSelector struct {
|
||||
info NodeInfo
|
||||
}
|
||||
|
||||
func (n *NodeSelector) PodSelectorMatches(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
minion, err := n.info.GetNodeInfo(node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return PodMatchesNodeLabels(pod, minion), 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
|
||||
}
|
||||
|
||||
type NodeLabelChecker struct {
|
||||
info NodeInfo
|
||||
labels []string
|
||||
presence bool
|
||||
}
|
||||
|
||||
func NewNodeLabelPredicate(info NodeInfo, labels []string, presence bool) algorithm.FitPredicate {
|
||||
labelChecker := &NodeLabelChecker{
|
||||
info: info,
|
||||
labels: labels,
|
||||
presence: presence,
|
||||
}
|
||||
return labelChecker.CheckNodeLabelPresence
|
||||
}
|
||||
|
||||
// CheckNodeLabelPresence checks whether all of the specified labels exists on a minion or not, regardless of their value
|
||||
// If "presence" is false, then returns false if any of the requested labels matches any of the minion's labels,
|
||||
// otherwise returns true.
|
||||
// If "presence" is true, then returns false if any of the requested labels does not match any of the minion's labels,
|
||||
// otherwise returns true.
|
||||
//
|
||||
// Consider the cases where the minions are placed in regions/zones/racks and these are identified by labels
|
||||
// In some cases, it is required that only minions that are part of ANY of the defined regions/zones/racks be selected
|
||||
//
|
||||
// Alternately, eliminating minions that have a certain label, regardless of value, is also useful
|
||||
// A minion may have a label with "retiring" as key and the date as the value
|
||||
// and it may be desirable to avoid scheduling new pods on this minion
|
||||
func (n *NodeLabelChecker) CheckNodeLabelPresence(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
var exists bool
|
||||
minion, err := n.info.GetNodeInfo(node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
minionLabels := labels.Set(minion.Labels)
|
||||
for _, label := range n.labels {
|
||||
exists = minionLabels.Has(label)
|
||||
if (exists && !n.presence) || (!exists && n.presence) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type ServiceAffinity struct {
|
||||
podLister algorithm.PodLister
|
||||
serviceLister algorithm.ServiceLister
|
||||
nodeInfo NodeInfo
|
||||
labels []string
|
||||
}
|
||||
|
||||
func NewServiceAffinityPredicate(podLister algorithm.PodLister, serviceLister algorithm.ServiceLister, nodeInfo NodeInfo, labels []string) algorithm.FitPredicate {
|
||||
affinity := &ServiceAffinity{
|
||||
podLister: podLister,
|
||||
serviceLister: serviceLister,
|
||||
nodeInfo: nodeInfo,
|
||||
labels: labels,
|
||||
}
|
||||
return affinity.CheckServiceAffinity
|
||||
}
|
||||
|
||||
// CheckServiceAffinity ensures that only the minions that match the specified labels are considered for scheduling.
|
||||
// The set of labels to be considered are provided to the struct (ServiceAffinity).
|
||||
// The pod is checked for the labels and any missing labels are then checked in the minion
|
||||
// that hosts the service pods (peers) for the given pod.
|
||||
//
|
||||
// We add an implicit selector requiring some particular value V for label L to a pod, if:
|
||||
// - L is listed in the ServiceAffinity object that is passed into the function
|
||||
// - the pod does not have any NodeSelector for L
|
||||
// - some other pod from the same service is already scheduled onto a minion that has value V for label L
|
||||
func (s *ServiceAffinity) CheckServiceAffinity(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
var affinitySelector labels.Selector
|
||||
|
||||
// check if the pod being scheduled has the affinity labels specified in its NodeSelector
|
||||
affinityLabels := map[string]string{}
|
||||
nodeSelector := labels.Set(pod.Spec.NodeSelector)
|
||||
labelsExist := true
|
||||
for _, l := range s.labels {
|
||||
if nodeSelector.Has(l) {
|
||||
affinityLabels[l] = nodeSelector.Get(l)
|
||||
} else {
|
||||
// the current pod does not specify all the labels, look in the existing service pods
|
||||
labelsExist = false
|
||||
}
|
||||
}
|
||||
|
||||
// skip looking at other pods in the service if the current pod defines all the required affinity labels
|
||||
if !labelsExist {
|
||||
services, err := s.serviceLister.GetPodServices(pod)
|
||||
if err == nil {
|
||||
// just use the first service and get the other pods within the service
|
||||
// TODO: a separate predicate can be created that tries to handle all services for the pod
|
||||
selector := labels.SelectorFromSet(services[0].Spec.Selector)
|
||||
servicePods, err := s.podLister.List(selector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// consider only the pods that belong to the same namespace
|
||||
nsServicePods := []*api.Pod{}
|
||||
for _, nsPod := range servicePods {
|
||||
if nsPod.Namespace == pod.Namespace {
|
||||
nsServicePods = append(nsServicePods, nsPod)
|
||||
}
|
||||
}
|
||||
if len(nsServicePods) > 0 {
|
||||
// consider any service pod and fetch the minion its hosted on
|
||||
otherMinion, err := s.nodeInfo.GetNodeInfo(nsServicePods[0].Spec.Host)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, l := range s.labels {
|
||||
// If the pod being scheduled has the label value specified, do not override it
|
||||
if _, exists := affinityLabels[l]; exists {
|
||||
continue
|
||||
}
|
||||
if labels.Set(otherMinion.Labels).Has(l) {
|
||||
affinityLabels[l] = labels.Set(otherMinion.Labels).Get(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no existing pods in the service, consider all minions
|
||||
if len(affinityLabels) == 0 {
|
||||
affinitySelector = labels.Everything()
|
||||
} else {
|
||||
affinitySelector = labels.Set(affinityLabels).AsSelector()
|
||||
}
|
||||
|
||||
minion, err := s.nodeInfo.GetNodeInfo(node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// check if the minion matches the selector
|
||||
return affinitySelector.Matches(labels.Set(minion.Labels)), nil
|
||||
}
|
||||
|
||||
func PodFitsPorts(pod *api.Pod, existingPods []*api.Pod, node string) (bool, error) {
|
||||
existingPorts := getUsedPorts(existingPods...)
|
||||
wantPorts := getUsedPorts(pod)
|
||||
for wport := range wantPorts {
|
||||
if wport == 0 {
|
||||
continue
|
||||
}
|
||||
if existingPorts[wport] {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func getUsedPorts(pods ...*api.Pod) map[int]bool {
|
||||
ports := make(map[int]bool)
|
||||
for _, pod := range pods {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for _, podPort := range container.Ports {
|
||||
ports[podPort.HostPort] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
// MapPodsToMachines obtains a list of pods and pivots that list into a map where the keys are host names
|
||||
// and the values are the list of pods running on that host.
|
||||
func MapPodsToMachines(lister algorithm.PodLister) (map[string][]*api.Pod, error) {
|
||||
machineToPods := map[string][]*api.Pod{}
|
||||
// TODO: perform more targeted query...
|
||||
pods, err := lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return map[string][]*api.Pod{}, err
|
||||
}
|
||||
for _, scheduledPod := range pods {
|
||||
host := scheduledPod.Spec.Host
|
||||
machineToPods[host] = append(machineToPods[host], scheduledPod)
|
||||
}
|
||||
return machineToPods, nil
|
||||
}
|
||||
671
plugin/pkg/scheduler/algorithm/predicates/predicates_test.go
Normal file
671
plugin/pkg/scheduler/algorithm/predicates/predicates_test.go
Normal file
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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 predicates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
type FakeNodeInfo api.Node
|
||||
|
||||
func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
|
||||
node := api.Node(n)
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
type FakeNodeListInfo []api.Node
|
||||
|
||||
func (nodes FakeNodeListInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
|
||||
for _, node := range nodes {
|
||||
if node.Name == nodeName {
|
||||
return &node, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Unable to find node: %s", nodeName)
|
||||
}
|
||||
|
||||
func makeResources(milliCPU int64, memory int64) api.NodeResources {
|
||||
return api.NodeResources{
|
||||
Capacity: api.ResourceList{
|
||||
"cpu": *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||
"memory": *resource.NewQuantity(memory, resource.BinarySI),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newResourcePod(usage ...resourceRequest) *api.Pod {
|
||||
containers := []api.Container{}
|
||||
for _, req := range usage {
|
||||
containers = append(containers, api.Container{
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: api.ResourceList{
|
||||
"cpu": *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
|
||||
"memory": *resource.NewQuantity(req.memory, resource.BinarySI),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: containers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodFitsResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
existingPods []*api.Pod
|
||||
fits bool
|
||||
test string
|
||||
}{
|
||||
{
|
||||
pod: &api.Pod{},
|
||||
existingPods: []*api.Pod{
|
||||
newResourcePod(resourceRequest{milliCPU: 10, memory: 20}),
|
||||
},
|
||||
fits: true,
|
||||
test: "no resources requested always fits",
|
||||
},
|
||||
{
|
||||
pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}),
|
||||
existingPods: []*api.Pod{
|
||||
newResourcePod(resourceRequest{milliCPU: 10, memory: 20}),
|
||||
},
|
||||
fits: false,
|
||||
test: "too many resources fails",
|
||||
},
|
||||
{
|
||||
pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}),
|
||||
existingPods: []*api.Pod{
|
||||
newResourcePod(resourceRequest{milliCPU: 5, memory: 5}),
|
||||
},
|
||||
fits: true,
|
||||
test: "both resources fit",
|
||||
},
|
||||
{
|
||||
pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 2}),
|
||||
existingPods: []*api.Pod{
|
||||
newResourcePod(resourceRequest{milliCPU: 5, memory: 19}),
|
||||
},
|
||||
fits: false,
|
||||
test: "one resources fits",
|
||||
},
|
||||
{
|
||||
pod: newResourcePod(resourceRequest{milliCPU: 5, memory: 1}),
|
||||
existingPods: []*api.Pod{
|
||||
newResourcePod(resourceRequest{milliCPU: 5, memory: 19}),
|
||||
},
|
||||
fits: true,
|
||||
test: "equal edge case",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
node := api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20).Capacity}}
|
||||
|
||||
fit := ResourceFit{FakeNodeInfo(node)}
|
||||
fits, err := fit.PodFitsResources(test.pod, test.existingPods, "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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 newPod(host string, hostPorts ...int) *api.Pod {
|
||||
networkPorts := []api.ContainerPort{}
|
||||
for _, port := range hostPorts {
|
||||
networkPorts = append(networkPorts, api.ContainerPort{HostPort: port})
|
||||
}
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Host: host,
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Ports: networkPorts,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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", err)
|
||||
}
|
||||
if test.fits != fits {
|
||||
t.Errorf("%s: expected %v, saw %v", test.test, test.fits, fits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsedPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
pods []*api.Pod
|
||||
|
||||
ports map[int]bool
|
||||
}{
|
||||
{
|
||||
[]*api.Pod{
|
||||
newPod("m1", 9090),
|
||||
},
|
||||
map[int]bool{9090: true},
|
||||
},
|
||||
{
|
||||
[]*api.Pod{
|
||||
newPod("m1", 9090),
|
||||
newPod("m1", 9091),
|
||||
},
|
||||
map[int]bool{9090: true, 9091: true},
|
||||
},
|
||||
{
|
||||
[]*api.Pod{
|
||||
newPod("m1", 9090),
|
||||
newPod("m2", 9091),
|
||||
},
|
||||
map[int]bool{9090: true, 9091: true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ports := getUsedPorts(test.pods...)
|
||||
if !reflect.DeepEqual(test.ports, ports) {
|
||||
t.Errorf("expect %v, got %v", test.ports, ports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiskConflicts(t *testing.T) {
|
||||
volState := api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
VolumeSource: api.VolumeSource{
|
||||
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||
PDName: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
volState2 := api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
VolumeSource: api.VolumeSource{
|
||||
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||
PDName: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
existingPods []*api.Pod
|
||||
isOk bool
|
||||
test string
|
||||
}{
|
||||
{&api.Pod{}, []*api.Pod{}, true, "nothing"},
|
||||
{&api.Pod{}, []*api.Pod{{Spec: volState}}, true, "one state"},
|
||||
{&api.Pod{Spec: volState}, []*api.Pod{{Spec: volState}}, false, "same state"},
|
||||
{&api.Pod{Spec: volState2}, []*api.Pod{{Spec: volState}}, true, "different state"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ok, err := NoDiskConflict(test.pod, test.existingPods, "machine")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if test.isOk && !ok {
|
||||
t.Errorf("expected ok, got none. %v %v %s", test.pod, test.existingPods, test.test)
|
||||
}
|
||||
if !test.isOk && ok {
|
||||
t.Errorf("expected no ok, got one. %v %v %s", test.pod, test.existingPods, test.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSDiskConflicts(t *testing.T) {
|
||||
volState := api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
VolumeSource: api.VolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
volState2 := api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
VolumeSource: api.VolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
existingPods []*api.Pod
|
||||
isOk bool
|
||||
test string
|
||||
}{
|
||||
{&api.Pod{}, []*api.Pod{}, true, "nothing"},
|
||||
{&api.Pod{}, []*api.Pod{{Spec: volState}}, true, "one state"},
|
||||
{&api.Pod{Spec: volState}, []*api.Pod{{Spec: volState}}, false, "same state"},
|
||||
{&api.Pod{Spec: volState2}, []*api.Pod{{Spec: volState}}, true, "different state"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ok, err := NoDiskConflict(test.pod, test.existingPods, "machine")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if test.isOk && !ok {
|
||||
t.Errorf("expected ok, got none. %v %v %s", test.pod, test.existingPods, test.test)
|
||||
}
|
||||
if !test.isOk && ok {
|
||||
t.Errorf("expected no ok, got one. %v %v %s", test.pod, test.existingPods, test.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
Spec: api.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
fits: false,
|
||||
test: "missing labels",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
fits: true,
|
||||
test: "same labels",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "blah",
|
||||
},
|
||||
fits: true,
|
||||
test: "node labels are superset",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
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.Node{ObjectMeta: api.ObjectMeta{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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeLabelPresence(t *testing.T) {
|
||||
label := map[string]string{"foo": "bar", "bar": "foo"}
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
existingPods []*api.Pod
|
||||
labels []string
|
||||
presence bool
|
||||
fits bool
|
||||
test string
|
||||
}{
|
||||
{
|
||||
labels: []string{"baz"},
|
||||
presence: true,
|
||||
fits: false,
|
||||
test: "label does not match, presence true",
|
||||
},
|
||||
{
|
||||
labels: []string{"baz"},
|
||||
presence: false,
|
||||
fits: true,
|
||||
test: "label does not match, presence false",
|
||||
},
|
||||
{
|
||||
labels: []string{"foo", "baz"},
|
||||
presence: true,
|
||||
fits: false,
|
||||
test: "one label matches, presence true",
|
||||
},
|
||||
{
|
||||
labels: []string{"foo", "baz"},
|
||||
presence: false,
|
||||
fits: false,
|
||||
test: "one label matches, presence false",
|
||||
},
|
||||
{
|
||||
labels: []string{"foo", "bar"},
|
||||
presence: true,
|
||||
fits: true,
|
||||
test: "all labels match, presence true",
|
||||
},
|
||||
{
|
||||
labels: []string{"foo", "bar"},
|
||||
presence: false,
|
||||
fits: false,
|
||||
test: "all labels match, presence false",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
node := api.Node{ObjectMeta: api.ObjectMeta{Labels: label}}
|
||||
labelChecker := NodeLabelChecker{FakeNodeInfo(node), test.labels, test.presence}
|
||||
fits, err := labelChecker.CheckNodeLabelPresence(test.pod, test.existingPods, "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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceAffinity(t *testing.T) {
|
||||
selector := map[string]string{"foo": "bar"}
|
||||
labels1 := map[string]string{
|
||||
"region": "r1",
|
||||
"zone": "z11",
|
||||
}
|
||||
labels2 := map[string]string{
|
||||
"region": "r1",
|
||||
"zone": "z12",
|
||||
}
|
||||
labels3 := map[string]string{
|
||||
"region": "r2",
|
||||
"zone": "z21",
|
||||
}
|
||||
labels4 := map[string]string{
|
||||
"region": "r2",
|
||||
"zone": "z22",
|
||||
}
|
||||
node1 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: labels1}}
|
||||
node2 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: labels2}}
|
||||
node3 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: labels3}}
|
||||
node4 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine4", Labels: labels4}}
|
||||
node5 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine5", Labels: labels4}}
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
pods []*api.Pod
|
||||
services []api.Service
|
||||
node string
|
||||
labels []string
|
||||
fits bool
|
||||
test string
|
||||
}{
|
||||
{
|
||||
pod: new(api.Pod),
|
||||
node: "machine1",
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "nothing scheduled",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{Spec: api.PodSpec{NodeSelector: map[string]string{"region": "r1"}}},
|
||||
node: "machine1",
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "pod with region label match",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{Spec: api.PodSpec{NodeSelector: map[string]string{"region": "r2"}}},
|
||||
node: "machine1",
|
||||
fits: false,
|
||||
labels: []string{"region"},
|
||||
test: "pod with region label mismatch",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine1"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "service pod on same minion",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine2"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "service pod on different minion, region match",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine3"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||
fits: false,
|
||||
labels: []string{"region"},
|
||||
test: "service pod on different minion, region mismatch",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns1"}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine3"}, ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns1"}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}, ObjectMeta: api.ObjectMeta{Namespace: "ns2"}}},
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "service in different namespace, region mismatch",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns1"}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine3"}, ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns2"}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}, ObjectMeta: api.ObjectMeta{Namespace: "ns1"}}},
|
||||
fits: true,
|
||||
labels: []string{"region"},
|
||||
test: "pod in different namespace, region mismatch",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns1"}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine3"}, ObjectMeta: api.ObjectMeta{Labels: selector, Namespace: "ns1"}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}, ObjectMeta: api.ObjectMeta{Namespace: "ns1"}}},
|
||||
fits: false,
|
||||
labels: []string{"region"},
|
||||
test: "service and pod in same namespace, region mismatch",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine2"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||
node: "machine1",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||
fits: false,
|
||||
labels: []string{"region", "zone"},
|
||||
test: "service pod on different minion, multiple labels, not all match",
|
||||
},
|
||||
{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||
pods: []*api.Pod{{Spec: api.PodSpec{Host: "machine5"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||
node: "machine4",
|
||||
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||
fits: true,
|
||||
labels: []string{"region", "zone"},
|
||||
test: "service pod on different minion, multiple labels, all match",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
nodes := []api.Node{node1, node2, node3, node4, node5}
|
||||
serviceAffinity := ServiceAffinity{algorithm.FakePodLister(test.pods), algorithm.FakeServiceLister(test.services), FakeNodeListInfo(nodes), test.labels}
|
||||
fits, err := serviceAffinity.CheckServiceAffinity(test.pod, []*api.Pod{}, test.node)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user