Merge pull request #125398 from AxeZhan/pvAffinity

[scheduler] When the hostname and nodename of a node do not match, ensure that pods carrying PVs with nodeAffinity are scheduled correctly.
This commit is contained in:
Kubernetes Prow Robot 2024-09-23 21:22:02 +01:00 committed by GitHub
commit 211d67a511
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 196 additions and 649 deletions

View File

@ -48,7 +48,6 @@ import (
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding/metrics" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding/metrics"
"k8s.io/kubernetes/pkg/scheduler/util/assumecache" "k8s.io/kubernetes/pkg/scheduler/util/assumecache"
"k8s.io/kubernetes/pkg/volume/util"
) )
// ConflictReason is used for the special strings which explain why // ConflictReason is used for the special strings which explain why
@ -130,8 +129,6 @@ type InTreeToCSITranslator interface {
// 1. The scheduler takes a Pod off the scheduler queue and processes it serially: // 1. The scheduler takes a Pod off the scheduler queue and processes it serially:
// a. Invokes all pre-filter plugins for the pod. GetPodVolumeClaims() is invoked // a. Invokes all pre-filter plugins for the pod. GetPodVolumeClaims() is invoked
// here, pod volume information will be saved in current scheduling cycle state for later use. // here, pod volume information will be saved in current scheduling cycle state for later use.
// If pod has bound immediate PVCs, GetEligibleNodes() is invoked to potentially reduce
// down the list of eligible nodes based on the bound PV's NodeAffinity (if any).
// b. Invokes all filter plugins, parallelized across nodes. FindPodVolumes() is invoked here. // b. Invokes all filter plugins, parallelized across nodes. FindPodVolumes() is invoked here.
// c. Invokes all score plugins. Future/TBD // c. Invokes all score plugins. Future/TBD
// d. Selects the best node for the Pod. // d. Selects the best node for the Pod.
@ -154,14 +151,6 @@ type SchedulerVolumeBinder interface {
// unbound with immediate binding (including prebound) and PVs that belong to storage classes of unbound PVCs with delayed binding. // unbound with immediate binding (including prebound) and PVs that belong to storage classes of unbound PVCs with delayed binding.
GetPodVolumeClaims(logger klog.Logger, pod *v1.Pod) (podVolumeClaims *PodVolumeClaims, err error) GetPodVolumeClaims(logger klog.Logger, pod *v1.Pod) (podVolumeClaims *PodVolumeClaims, err error)
// GetEligibleNodes checks the existing bound claims of the pod to determine if the list of nodes can be
// potentially reduced down to a subset of eligible nodes based on the bound claims which then can be used
// in subsequent scheduling stages.
//
// If eligibleNodes is 'nil', then it indicates that such eligible node reduction cannot be made
// and all nodes should be considered.
GetEligibleNodes(logger klog.Logger, boundClaims []*v1.PersistentVolumeClaim) (eligibleNodes sets.Set[string])
// FindPodVolumes checks if all of a Pod's PVCs can be satisfied by the // FindPodVolumes checks if all of a Pod's PVCs can be satisfied by the
// node and returns pod's volumes information. // node and returns pod's volumes information.
// //
@ -384,55 +373,6 @@ func (b *volumeBinder) FindPodVolumes(logger klog.Logger, pod *v1.Pod, podVolume
return return
} }
// GetEligibleNodes checks the existing bound claims of the pod to determine if the list of nodes can be
// potentially reduced down to a subset of eligible nodes based on the bound claims which then can be used
// in subsequent scheduling stages.
//
// Returning 'nil' for eligibleNodes indicates that such eligible node reduction cannot be made and all nodes
// should be considered.
func (b *volumeBinder) GetEligibleNodes(logger klog.Logger, boundClaims []*v1.PersistentVolumeClaim) (eligibleNodes sets.Set[string]) {
if len(boundClaims) == 0 {
return
}
var errs []error
for _, pvc := range boundClaims {
pvName := pvc.Spec.VolumeName
pv, err := b.pvCache.GetPV(pvName)
if err != nil {
errs = append(errs, err)
continue
}
// if the PersistentVolume is local and has node affinity matching specific node(s),
// add them to the eligible nodes
nodeNames := util.GetLocalPersistentVolumeNodeNames(pv)
if len(nodeNames) != 0 {
// on the first found list of eligible nodes for the local PersistentVolume,
// insert to the eligible node set.
if eligibleNodes == nil {
eligibleNodes = sets.New(nodeNames...)
} else {
// for subsequent finding of eligible nodes for the local PersistentVolume,
// take the intersection of the nodes with the existing eligible nodes
// for cases if PV1 has node affinity to node1 and PV2 has node affinity to node2,
// then the eligible node list should be empty.
eligibleNodes = eligibleNodes.Intersection(sets.New(nodeNames...))
}
}
}
if len(errs) > 0 {
logger.V(4).Info("GetEligibleNodes: one or more error occurred finding eligible nodes", "error", errs)
return nil
}
if eligibleNodes != nil {
logger.V(4).Info("GetEligibleNodes: reduced down eligible nodes", "nodes", eligibleNodes)
}
return
}
// AssumePodVolumes will take the matching PVs and PVCs to provision in pod's // AssumePodVolumes will take the matching PVs and PVCs to provision in pod's
// volume information for the chosen node, and: // volume information for the chosen node, and:
// 1. Update the pvCache with the new prebound PV. // 1. Update the pvCache with the new prebound PV.

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"reflect"
"sort" "sort"
"testing" "testing"
"time" "time"
@ -32,7 +31,6 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
@ -63,9 +61,6 @@ var (
boundPVCNode1a = makeTestPVC("unbound-pvc", "1G", "", pvcBound, "pv-node1a", "1", &waitClass) boundPVCNode1a = makeTestPVC("unbound-pvc", "1G", "", pvcBound, "pv-node1a", "1", &waitClass)
immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", "", pvcUnbound, "", "1", &immediateClass) immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", "", pvcUnbound, "", "1", &immediateClass)
immediateBoundPVC = makeTestPVC("immediate-bound-pvc", "1G", "", pvcBound, "pv-bound-immediate", "1", &immediateClass) immediateBoundPVC = makeTestPVC("immediate-bound-pvc", "1G", "", pvcBound, "pv-bound-immediate", "1", &immediateClass)
localPreboundPVC1a = makeTestPVC("local-prebound-pvc-1a", "1G", "", pvcPrebound, "local-pv-node1a", "1", &waitClass)
localPreboundPVC1b = makeTestPVC("local-prebound-pvc-1b", "1G", "", pvcPrebound, "local-pv-node1b", "1", &waitClass)
localPreboundPVC2a = makeTestPVC("local-prebound-pvc-2a", "1G", "", pvcPrebound, "local-pv-node2a", "1", &waitClass)
// PVCs for dynamic provisioning // PVCs for dynamic provisioning
provisionedPVC = makeTestPVC("provisioned-pvc", "1Gi", "", pvcUnbound, "", "1", &waitClassWithProvisioner) provisionedPVC = makeTestPVC("provisioned-pvc", "1Gi", "", pvcUnbound, "", "1", &waitClassWithProvisioner)
@ -97,9 +92,6 @@ var (
pvNode1bBoundHigherVersion = makeTestPV("pv-node1b", "node1", "10G", "2", unboundPVC2, waitClass) pvNode1bBoundHigherVersion = makeTestPV("pv-node1b", "node1", "10G", "2", unboundPVC2, waitClass)
pvBoundImmediate = makeTestPV("pv-bound-immediate", "node1", "1G", "1", immediateBoundPVC, immediateClass) pvBoundImmediate = makeTestPV("pv-bound-immediate", "node1", "1G", "1", immediateBoundPVC, immediateClass)
pvBoundImmediateNode2 = makeTestPV("pv-bound-immediate", "node2", "1G", "1", immediateBoundPVC, immediateClass) pvBoundImmediateNode2 = makeTestPV("pv-bound-immediate", "node2", "1G", "1", immediateBoundPVC, immediateClass)
localPVNode1a = makeLocalPV("local-pv-node1a", "node1", "5G", "1", nil, waitClass)
localPVNode1b = makeLocalPV("local-pv-node1b", "node1", "10G", "1", nil, waitClass)
localPVNode2a = makeLocalPV("local-pv-node2a", "node2", "5G", "1", nil, waitClass)
// PVs for CSI migration // PVs for CSI migration
migrationPVBound = makeTestPVForCSIMigration(zone1Labels, boundMigrationPVC, true) migrationPVBound = makeTestPVForCSIMigration(zone1Labels, boundMigrationPVC, true)
@ -709,12 +701,6 @@ func makeTestPVForCSIMigration(labels map[string]string, pvc *v1.PersistentVolum
return pv return pv
} }
func makeLocalPV(name, node, capacity, version string, boundToPVC *v1.PersistentVolumeClaim, className string) *v1.PersistentVolume {
pv := makeTestPV(name, node, capacity, version, boundToPVC, className)
pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions[0].Key = v1.LabelHostname
return pv
}
func pvcSetSelectedNode(pvc *v1.PersistentVolumeClaim, node string) *v1.PersistentVolumeClaim { func pvcSetSelectedNode(pvc *v1.PersistentVolumeClaim, node string) *v1.PersistentVolumeClaim {
newPVC := pvc.DeepCopy() newPVC := pvc.DeepCopy()
metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnSelectedNode, node) metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnSelectedNode, node)
@ -2326,130 +2312,3 @@ func TestCapacity(t *testing.T) {
}) })
} }
} }
func TestGetEligibleNodes(t *testing.T) {
type scenarioType struct {
// Inputs
pvcs []*v1.PersistentVolumeClaim
pvs []*v1.PersistentVolume
nodes []*v1.Node
// Expected return values
eligibleNodes sets.Set[string]
}
scenarios := map[string]scenarioType{
"no-bound-claims": {},
"no-nodes-found": {
pvcs: []*v1.PersistentVolumeClaim{
preboundPVC,
preboundPVCNode1a,
},
},
"pv-not-found": {
pvcs: []*v1.PersistentVolumeClaim{
preboundPVC,
preboundPVCNode1a,
},
nodes: []*v1.Node{
node1,
},
},
"node-affinity-mismatch": {
pvcs: []*v1.PersistentVolumeClaim{
preboundPVC,
preboundPVCNode1a,
},
pvs: []*v1.PersistentVolume{
pvNode1a,
},
nodes: []*v1.Node{
node1,
node2,
},
},
"local-pv-with-node-affinity": {
pvcs: []*v1.PersistentVolumeClaim{
localPreboundPVC1a,
localPreboundPVC1b,
},
pvs: []*v1.PersistentVolume{
localPVNode1a,
localPVNode1b,
},
nodes: []*v1.Node{
node1,
node2,
},
eligibleNodes: sets.New("node1"),
},
"multi-local-pv-with-different-nodes": {
pvcs: []*v1.PersistentVolumeClaim{
localPreboundPVC1a,
localPreboundPVC1b,
localPreboundPVC2a,
},
pvs: []*v1.PersistentVolume{
localPVNode1a,
localPVNode1b,
localPVNode2a,
},
nodes: []*v1.Node{
node1,
node2,
},
eligibleNodes: sets.New[string](),
},
"local-and-non-local-pv": {
pvcs: []*v1.PersistentVolumeClaim{
localPreboundPVC1a,
localPreboundPVC1b,
preboundPVC,
immediateBoundPVC,
},
pvs: []*v1.PersistentVolume{
localPVNode1a,
localPVNode1b,
pvNode1a,
pvBoundImmediate,
pvBoundImmediateNode2,
},
nodes: []*v1.Node{
node1,
node2,
},
eligibleNodes: sets.New("node1"),
},
}
run := func(t *testing.T, scenario scenarioType) {
logger, ctx := ktesting.NewTestContext(t)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Setup
testEnv := newTestBinder(t, ctx)
testEnv.initVolumes(scenario.pvs, scenario.pvs)
testEnv.initNodes(scenario.nodes)
testEnv.initClaims(scenario.pvcs, scenario.pvcs)
// Execute
eligibleNodes := testEnv.binder.GetEligibleNodes(logger, scenario.pvcs)
// Validate
if reflect.DeepEqual(scenario.eligibleNodes, eligibleNodes) {
fmt.Println("foo")
}
if compDiff := cmp.Diff(scenario.eligibleNodes, eligibleNodes, cmp.Comparer(func(a, b sets.Set[string]) bool {
return reflect.DeepEqual(a, b)
})); compDiff != "" {
t.Errorf("Unexpected eligible nodes (-want +got):\n%s", compDiff)
}
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) { run(t, scenario) })
}
}

View File

@ -20,7 +20,6 @@ import (
"context" "context"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
@ -55,11 +54,6 @@ func (b *FakeVolumeBinder) GetPodVolumeClaims(_ klog.Logger, pod *v1.Pod) (podVo
return &PodVolumeClaims{}, nil return &PodVolumeClaims{}, nil
} }
// GetEligibleNodes implements SchedulerVolumeBinder.GetEligibleNodes.
func (b *FakeVolumeBinder) GetEligibleNodes(_ klog.Logger, boundClaims []*v1.PersistentVolumeClaim) (eligibleNodes sets.Set[string]) {
return nil
}
// FindPodVolumes implements SchedulerVolumeBinder.FindPodVolumes. // FindPodVolumes implements SchedulerVolumeBinder.FindPodVolumes.
func (b *FakeVolumeBinder) FindPodVolumes(_ klog.Logger, pod *v1.Pod, _ *PodVolumeClaims, node *v1.Node) (podVolumes *PodVolumes, reasons ConflictReasons, err error) { func (b *FakeVolumeBinder) FindPodVolumes(_ klog.Logger, pod *v1.Pod, _ *PodVolumeClaims, node *v1.Node) (podVolumes *PodVolumes, reasons ConflictReasons, err error) {
return nil, b.config.FindReasons, b.config.FindErr return nil, b.config.FindReasons, b.config.FindErr

View File

@ -341,14 +341,6 @@ func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleSt
status.AppendReason("pod has unbound immediate PersistentVolumeClaims") status.AppendReason("pod has unbound immediate PersistentVolumeClaims")
return nil, status return nil, status
} }
// Attempt to reduce down the number of nodes to consider in subsequent scheduling stages if pod has bound claims.
var result *framework.PreFilterResult
if eligibleNodes := pl.Binder.GetEligibleNodes(logger, podVolumeClaims.boundClaims); eligibleNodes != nil {
result = &framework.PreFilterResult{
NodeNames: eligibleNodes,
}
}
state.Write(stateKey, &stateData{ state.Write(stateKey, &stateData{
podVolumesByNode: make(map[string]*PodVolumes), podVolumesByNode: make(map[string]*PodVolumes),
podVolumeClaims: &PodVolumeClaims{ podVolumeClaims: &PodVolumeClaims{
@ -357,7 +349,7 @@ func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleSt
unboundVolumesDelayBinding: podVolumeClaims.unboundVolumesDelayBinding, unboundVolumesDelayBinding: podVolumeClaims.unboundVolumesDelayBinding,
}, },
}) })
return result, nil return nil, nil
} }
// PreFilterExtensions returns prefilter extensions, pod add and remove. // PreFilterExtensions returns prefilter extensions, pod add and remove.

View File

@ -27,7 +27,6 @@ import (
storagev1 "k8s.io/api/storage/v1" storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/klog/v2/ktesting" "k8s.io/klog/v2/ktesting"
@ -127,43 +126,6 @@ func TestVolumeBinding(t *testing.T) {
}, },
wantPreScoreStatus: framework.NewStatus(framework.Skip), wantPreScoreStatus: framework.NewStatus(framework.Skip),
}, },
{
name: "all bound with local volumes",
pod: makePod("pod-a").withPVCVolume("pvc-a", "volume-a").withPVCVolume("pvc-b", "volume-b").Pod,
nodes: []*v1.Node{
makeNode("node-a").Node,
},
pvcs: []*v1.PersistentVolumeClaim{
makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
makePVC("pvc-b", waitSC.Name).withBoundPV("pv-b").PersistentVolumeClaim,
},
pvs: []*v1.PersistentVolume{
makePV("pv-a", waitSC.Name).withPhase(v1.VolumeBound).withNodeAffinity(map[string][]string{
v1.LabelHostname: {"node-a"},
}).PersistentVolume,
makePV("pv-b", waitSC.Name).withPhase(v1.VolumeBound).withNodeAffinity(map[string][]string{
v1.LabelHostname: {"node-a"},
}).PersistentVolume,
},
wantPreFilterResult: &framework.PreFilterResult{
NodeNames: sets.New("node-a"),
},
wantStateAfterPreFilter: &stateData{
podVolumeClaims: &PodVolumeClaims{
boundClaims: []*v1.PersistentVolumeClaim{
makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
makePVC("pvc-b", waitSC.Name).withBoundPV("pv-b").PersistentVolumeClaim,
},
unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{},
unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{},
},
podVolumesByNode: map[string]*PodVolumes{},
},
wantFilterStatus: []*framework.Status{
nil,
},
wantPreScoreStatus: framework.NewStatus(framework.Skip),
},
{ {
name: "PVC does not exist", name: "PVC does not exist",
pod: makePod("pod-a").withPVCVolume("pvc-a", "").Pod, pod: makePod("pod-a").withPVCVolume("pvc-a", "").Pod,

View File

@ -1825,8 +1825,9 @@ func TestSchedulerSchedulePod(t *testing.T) {
name string name string
registerPlugins []tf.RegisterPluginFunc registerPlugins []tf.RegisterPluginFunc
extenders []tf.FakeExtender extenders []tf.FakeExtender
nodes []string nodes []*v1.Node
pvcs []v1.PersistentVolumeClaim pvcs []v1.PersistentVolumeClaim
pvs []v1.PersistentVolume
pod *v1.Pod pod *v1.Pod
pods []*v1.Pod pods []*v1.Pod
wantNodes sets.Set[string] wantNodes sets.Set[string]
@ -1839,9 +1840,12 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("FalseFilter", tf.NewFalseFilterPlugin), tf.RegisterFilterPlugin("FalseFilter", tf.NewFalseFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
pod: st.MakePod().Name("2").UID("2").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
name: "test 1", {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("2").UID("2").Obj(),
name: "test 1",
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("2").UID("2").Obj(), Pod: st.MakePod().Name("2").UID("2").Obj(),
NumAllNodes: 2, NumAllNodes: 2,
@ -1861,7 +1865,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("ignore").UID("ignore").Obj(), pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
wantNodes: sets.New("node1", "node2"), wantNodes: sets.New("node1", "node2"),
name: "test 2", name: "test 2",
@ -1874,7 +1881,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("MatchFilter", tf.NewMatchFilterPlugin), tf.RegisterFilterPlugin("MatchFilter", tf.NewMatchFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("node2").UID("node2").Obj(), pod: st.MakePod().Name("node2").UID("node2").Obj(),
wantNodes: sets.New("node2"), wantNodes: sets.New("node2"),
name: "test 3", name: "test 3",
@ -1887,7 +1897,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3", "2", "1"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
},
pod: st.MakePod().Name("ignore").UID("ignore").Obj(), pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
wantNodes: sets.New("3"), wantNodes: sets.New("3"),
name: "test 4", name: "test 4",
@ -1900,7 +1914,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3", "2", "1"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
},
pod: st.MakePod().Name("2").UID("2").Obj(), pod: st.MakePod().Name("2").UID("2").Obj(),
wantNodes: sets.New("2"), wantNodes: sets.New("2"),
name: "test 5", name: "test 5",
@ -1914,7 +1932,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("ReverseNumericMap", newReverseNumericMapPlugin(), 2), tf.RegisterScorePlugin("ReverseNumericMap", newReverseNumericMapPlugin(), 2),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3", "2", "1"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
},
pod: st.MakePod().Name("2").UID("2").Obj(), pod: st.MakePod().Name("2").UID("2").Obj(),
wantNodes: sets.New("1"), wantNodes: sets.New("1"),
name: "test 6", name: "test 6",
@ -1928,9 +1950,13 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3", "2", "1"}, nodes: []*v1.Node{
pod: st.MakePod().Name("2").UID("2").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
name: "test 7", {ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
},
pod: st.MakePod().Name("2").UID("2").Obj(),
name: "test 7",
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("2").UID("2").Obj(), Pod: st.MakePod().Name("2").UID("2").Obj(),
NumAllNodes: 3, NumAllNodes: 3,
@ -1955,9 +1981,12 @@ func TestSchedulerSchedulePod(t *testing.T) {
pods: []*v1.Pod{ pods: []*v1.Pod{
st.MakePod().Name("2").UID("2").Node("2").Phase(v1.PodRunning).Obj(), st.MakePod().Name("2").UID("2").Node("2").Phase(v1.PodRunning).Obj(),
}, },
pod: st.MakePod().Name("2").UID("2").Obj(), pod: st.MakePod().Name("2").UID("2").Obj(),
nodes: []string{"1", "2"}, nodes: []*v1.Node{
name: "test 8", {ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
},
name: "test 8",
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("2").UID("2").Obj(), Pod: st.MakePod().Name("2").UID("2").Obj(),
NumAllNodes: 2, NumAllNodes: 2,
@ -1979,13 +2008,19 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pvcs: []v1.PersistentVolumeClaim{ pvcs: []v1.PersistentVolumeClaim{
{ {
ObjectMeta: metav1.ObjectMeta{Name: "existingPVC", UID: types.UID("existingPVC"), Namespace: v1.NamespaceDefault}, ObjectMeta: metav1.ObjectMeta{Name: "existingPVC", UID: types.UID("existingPVC"), Namespace: v1.NamespaceDefault},
Spec: v1.PersistentVolumeClaimSpec{VolumeName: "existingPV"}, Spec: v1.PersistentVolumeClaimSpec{VolumeName: "existingPV"},
}, },
}, },
pvs: []v1.PersistentVolume{
{ObjectMeta: metav1.ObjectMeta{Name: "existingPV"}},
},
pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(), pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(),
wantNodes: sets.New("node1", "node2"), wantNodes: sets.New("node1", "node2"),
name: "existing PVC", name: "existing PVC",
@ -1999,9 +2034,12 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
pod: st.MakePod().Name("ignore").UID("ignore").PVC("unknownPVC").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
name: "unknown PVC", {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("ignore").UID("ignore").PVC("unknownPVC").Obj(),
name: "unknown PVC",
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("ignore").UID("ignore").PVC("unknownPVC").Obj(), Pod: st.MakePod().Name("ignore").UID("ignore").PVC("unknownPVC").Obj(),
NumAllNodes: 2, NumAllNodes: 2,
@ -2020,10 +2058,13 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
pvcs: []v1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "existingPVC", UID: types.UID("existingPVC"), Namespace: v1.NamespaceDefault, DeletionTimestamp: &metav1.Time{}}}}, {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
name: "deleted PVC", },
pvcs: []v1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "existingPVC", UID: types.UID("existingPVC"), Namespace: v1.NamespaceDefault, DeletionTimestamp: &metav1.Time{}}}},
pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(),
name: "deleted PVC",
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(), Pod: st.MakePod().Name("ignore").UID("ignore").Namespace(v1.NamespaceDefault).PVC("existingPVC").Obj(),
NumAllNodes: 2, NumAllNodes: 2,
@ -2042,10 +2083,13 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("TrueMap", newTrueMapPlugin(), 2), tf.RegisterScorePlugin("TrueMap", newTrueMapPlugin(), 2),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"2", "1"}, nodes: []*v1.Node{
pod: st.MakePod().Name("2").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
name: "test error with priority map", {ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
wErr: fmt.Errorf("running Score plugins: %w", fmt.Errorf(`plugin "FalseMap" failed with: %w`, errPrioritize)), },
pod: st.MakePod().Name("2").Obj(),
name: "test error with priority map",
wErr: fmt.Errorf("running Score plugins: %w", fmt.Errorf(`plugin "FalseMap" failed with: %w`, errPrioritize)),
}, },
{ {
name: "test podtopologyspread plugin - 2 nodes with maxskew=1", name: "test podtopologyspread plugin - 2 nodes with maxskew=1",
@ -2059,8 +2103,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
pod: st.MakePod().Name("p").UID("p").Label("foo", "").SpreadConstraint(1, "hostname", v1.DoNotSchedule, &metav1.LabelSelector{ {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("p").UID("p").Label("foo", "").SpreadConstraint(1, "kubernetes.io/hostname", v1.DoNotSchedule, &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
Key: "foo", Key: "foo",
@ -2087,8 +2134,12 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
pod: st.MakePod().Name("p").UID("p").Label("foo", "").SpreadConstraint(2, "hostname", v1.DoNotSchedule, &metav1.LabelSelector{ {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("p").UID("p").Label("foo", "").SpreadConstraint(2, "kubernetes.io/hostname", v1.DoNotSchedule, &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
Key: "foo", Key: "foo",
@ -2115,7 +2166,9 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
},
pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(), pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: &framework.FitError{ wErr: &framework.FitError{
@ -2146,7 +2199,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
Predicates: []tf.FitPredicate{tf.FalsePredicateExtender}, Predicates: []tf.FitPredicate{tf.FalsePredicateExtender},
}, },
}, },
nodes: []string{"1", "2", "3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
},
pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(), pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: &framework.FitError{ wErr: &framework.FitError{
@ -2173,7 +2230,9 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "3", Labels: map[string]string{"kubernetes.io/hostname": "3"}}},
},
pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(), pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: &framework.FitError{ wErr: &framework.FitError{
@ -2198,7 +2257,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1), tf.RegisterScorePlugin("NumericMap", newNumericMapPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"1", "2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
},
pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(), pod: st.MakePod().Name("test-filter").UID("test-filter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: nil, wErr: nil,
@ -2213,7 +2275,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"1", "2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: &framework.FitError{ wErr: &framework.FitError{
@ -2236,7 +2301,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"1", "2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: fmt.Errorf(`running PreFilter plugin "FakePreFilter": %w`, errors.New("injected error status")), wErr: fmt.Errorf(`running PreFilter plugin "FakePreFilter": %w`, errors.New("injected error status")),
@ -2259,7 +2327,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: sets.New("node2"), wantNodes: sets.New("node2"),
// since this case has no score plugin, we'll only try to find one node in Filter stage // since this case has no score plugin, we'll only try to find one node in Filter stage
@ -2283,8 +2355,12 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
NumAllNodes: 3, NumAllNodes: 3,
@ -2309,8 +2385,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1"}, nodes: []*v1.Node{
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
NumAllNodes: 1, NumAllNodes: 1,
@ -2335,8 +2413,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wErr: &framework.FitError{ wErr: &framework.FitError{
Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), Pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
NumAllNodes: 2, NumAllNodes: 2,
@ -2381,7 +2462,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: sets.New("node2", "node3"), wantNodes: sets.New("node2", "node3"),
wantEvaluatedNodes: ptr.To[int32](3), wantEvaluatedNodes: ptr.To[int32](3),
@ -2397,7 +2482,10 @@ func TestSchedulerSchedulePod(t *testing.T) {
framework.NewStatus(framework.Error, "this score function shouldn't be executed because this plugin returned Skip in the PreScore"), framework.NewStatus(framework.Error, "this score function shouldn't be executed because this plugin returned Skip in the PreScore"),
), "PreScore", "Score"), ), "PreScore", "Score"),
}, },
nodes: []string{"node1", "node2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
},
pod: st.MakePod().Name("ignore").UID("ignore").Obj(), pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
wantNodes: sets.New("node1", "node2"), wantNodes: sets.New("node1", "node2"),
}, },
@ -2408,7 +2496,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("pod1").UID("pod1").Obj(), pod: st.MakePod().Name("pod1").UID("pod1").Obj(),
wantNodes: sets.New("node1", "node2", "node3"), wantNodes: sets.New("node1", "node2", "node3"),
wantEvaluatedNodes: ptr.To[int32](1), wantEvaluatedNodes: ptr.To[int32](1),
@ -2423,7 +2515,11 @@ func TestSchedulerSchedulePod(t *testing.T) {
), ),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"node1", "node2", "node3"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: sets.New("node1", "node2"), wantNodes: sets.New("node1", "node2"),
// since this case has no score plugin, we'll only try to find one node in Filter stage // since this case has no score plugin, we'll only try to find one node in Filter stage
@ -2442,7 +2538,9 @@ func TestSchedulerSchedulePod(t *testing.T) {
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
}, },
nodes: []string{"1", "2"}, nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "1", Labels: map[string]string{"kubernetes.io/hostname": "1"}}}, {ObjectMeta: metav1.ObjectMeta{Name: "2", Labels: map[string]string{"kubernetes.io/hostname": "2"}}},
},
pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(),
wantNodes: nil, wantNodes: nil,
wErr: &framework.FitError{ wErr: &framework.FitError{
@ -2454,6 +2552,50 @@ func TestSchedulerSchedulePod(t *testing.T) {
}, },
}, },
}, },
{
registerPlugins: []tf.RegisterPluginFunc{
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
tf.RegisterPreFilterPlugin(volumebinding.Name, frameworkruntime.FactoryAdapter(fts, volumebinding.New)),
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
},
nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "host1"}}},
},
pvcs: []v1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{Name: "PVC1", UID: types.UID("PVC1"), Namespace: v1.NamespaceDefault},
Spec: v1.PersistentVolumeClaimSpec{VolumeName: "PV1"},
},
},
pvs: []v1.PersistentVolume{
{
ObjectMeta: metav1.ObjectMeta{Name: "PV1", UID: types.UID("PV1")},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/hostname",
Operator: v1.NodeSelectorOpIn,
Values: []string{"host1"},
},
},
},
},
},
},
},
},
},
pod: st.MakePod().Name("pod1").UID("pod1").Namespace(v1.NamespaceDefault).PVC("PVC1").Obj(),
wantNodes: sets.New("node1"),
name: "hostname and nodename of the node do not match",
wErr: nil,
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
@ -2466,8 +2608,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
cache.AddPod(logger, pod) cache.AddPod(logger, pod)
} }
var nodes []*v1.Node var nodes []*v1.Node
for _, name := range test.nodes { for _, node := range test.nodes {
node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: name, Labels: map[string]string{"hostname": name}}}
nodes = append(nodes, node) nodes = append(nodes, node)
cache.AddNode(logger, node) cache.AddNode(logger, node)
} }
@ -2477,10 +2618,9 @@ func TestSchedulerSchedulePod(t *testing.T) {
for _, pvc := range test.pvcs { for _, pvc := range test.pvcs {
metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, volume.AnnBindCompleted, "true") metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, volume.AnnBindCompleted, "true")
cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, &pvc, metav1.CreateOptions{}) cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, &pvc, metav1.CreateOptions{})
if pvName := pvc.Spec.VolumeName; pvName != "" { }
pv := v1.PersistentVolume{ObjectMeta: metav1.ObjectMeta{Name: pvName}} for _, pv := range test.pvs {
cs.CoreV1().PersistentVolumes().Create(ctx, &pv, metav1.CreateOptions{}) _, _ = cs.CoreV1().PersistentVolumes().Create(ctx, &pv, metav1.CreateOptions{})
}
} }
snapshot := internalcache.NewSnapshot(test.pods, nodes) snapshot := internalcache.NewSnapshot(test.pods, nodes)
fwk, err := tf.NewFramework( fwk, err := tf.NewFramework(

View File

@ -511,44 +511,6 @@ func IsLocalEphemeralVolume(volume v1.Volume) bool {
volume.ConfigMap != nil volume.ConfigMap != nil
} }
// GetLocalPersistentVolumeNodeNames returns the node affinity node name(s) for
// local PersistentVolumes. nil is returned if the PV does not have any
// specific node affinity node selector terms and match expressions.
// PersistentVolume with node affinity has select and match expressions
// in the form of:
//
// nodeAffinity:
// required:
// nodeSelectorTerms:
// - matchExpressions:
// - key: kubernetes.io/hostname
// operator: In
// values:
// - <node1>
// - <node2>
func GetLocalPersistentVolumeNodeNames(pv *v1.PersistentVolume) []string {
if pv == nil || pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil {
return nil
}
var result sets.Set[string]
for _, term := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
var nodes sets.Set[string]
for _, matchExpr := range term.MatchExpressions {
if matchExpr.Key == v1.LabelHostname && matchExpr.Operator == v1.NodeSelectorOpIn {
if nodes == nil {
nodes = sets.New(matchExpr.Values...)
} else {
nodes = nodes.Intersection(sets.New(matchExpr.Values...))
}
}
}
result = result.Union(nodes)
}
return sets.List(result)
}
// GetPodVolumeNames returns names of volumes that are used in a pod, // GetPodVolumeNames returns names of volumes that are used in a pod,
// either as filesystem mount or raw block device, together with list // either as filesystem mount or raw block device, together with list
// of all SELinux contexts of all containers that use the volumes. // of all SELinux contexts of all containers that use the volumes.

View File

@ -22,7 +22,6 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/google/go-cmp/cmp"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -962,304 +961,3 @@ func TestGetPodVolumeNames(t *testing.T) {
}) })
} }
} }
func TestGetPersistentVolumeNodeNames(t *testing.T) {
tests := []struct {
name string
pv *v1.PersistentVolume
expectedNodeNames []string
}{
{
name: "nil PV",
pv: nil,
},
{
name: "PV missing node affinity",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
},
{
name: "PV node affinity missing required",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{},
},
},
},
{
name: "PV node affinity required zero selector terms",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{},
},
},
},
},
expectedNodeNames: []string{},
},
{
name: "PV node affinity required zero selector terms",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{},
},
},
},
},
expectedNodeNames: []string{},
},
{
name: "PV node affinity required zero match expressions",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{},
},
},
},
},
},
},
expectedNodeNames: []string{},
},
{
name: "PV node affinity required multiple match expressions",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "foo",
Operator: v1.NodeSelectorOpIn,
},
{
Key: "bar",
Operator: v1.NodeSelectorOpIn,
},
},
},
},
},
},
},
},
expectedNodeNames: []string{},
},
{
name: "PV node affinity required single match expression with no values",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{},
},
},
},
},
},
},
},
},
expectedNodeNames: []string{},
},
{
name: "PV node affinity required single match expression with single node",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node1",
},
},
},
},
},
},
},
},
},
expectedNodeNames: []string{
"node1",
},
},
{
name: "PV node affinity required single match expression with multiple nodes",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node1",
"node2",
},
},
},
},
},
},
},
},
},
expectedNodeNames: []string{
"node1",
"node2",
},
},
{
name: "PV node affinity required multiple match expressions with multiple nodes",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "bar",
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node1",
"node2",
},
},
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node3",
"node4",
},
},
},
},
},
},
},
},
},
expectedNodeNames: []string{
"node3",
"node4",
},
},
{
name: "PV node affinity required multiple node selectors multiple match expressions with multiple nodes",
pv: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.PersistentVolumeSpec{
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node1",
"node2",
},
},
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node2",
"node3",
},
},
},
},
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: v1.LabelHostname,
Operator: v1.NodeSelectorOpIn,
Values: []string{
"node1",
},
},
},
},
},
},
},
},
},
expectedNodeNames: []string{
"node1",
"node2",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
nodeNames := GetLocalPersistentVolumeNodeNames(test.pv)
if diff := cmp.Diff(test.expectedNodeNames, nodeNames); diff != "" {
t.Errorf("Unexpected nodeNames (-want, +got):\n%s", diff)
}
})
}
}