Skip to content

Commit 00c3974

Browse files
authored
testing: check that topology domain is reserved for preemptor workloads accross cycles (#14687)
* test: add integration test to verify pending workloads do not steal topology during preemptor eviction cycles * test: update PrioritizePreemptorWorkloads feature gate in TAS integration test * test: update test description to include feature gate condition in tas_test.go * refactor: simplify TAS integration test setup and restructure PrioritizePreemptorWorkloads test case * test: tighten TAS topology error message validation in integration tests * test: update TAS integration test topology and scenario to verify fragmented capacity handling
1 parent 8e661a4 commit 00c3974

1 file changed

Lines changed: 223 additions & 0 deletions

File tree

test/integration/singlecluster/tas/tas_test.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4803,6 +4803,229 @@ var _ = ginkgo.Describe("Topology Aware Scheduling", ginkgo.Ordered, func() {
48034803
})
48044804
})
48054805

4806+
ginkgo.When("PrioritizePreemptorWorkloads is enabled with multiple evictions within Cohort", func() {
4807+
var (
4808+
nodes []corev1.Node
4809+
localQueueB *kueue.LocalQueue
4810+
clusterQueueB *kueue.ClusterQueue
4811+
localQueueC *kueue.LocalQueue
4812+
clusterQueueC *kueue.ClusterQueue
4813+
)
4814+
4815+
ginkgo.BeforeEach(func() {
4816+
features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.PrioritizePreemptorWorkloads, true)
4817+
4818+
// b1
4819+
// / | \
4820+
// r1 r2 r3
4821+
// | | |
4822+
// x2 x1 x3
4823+
// (5) (5) (2)
4824+
nodes = []corev1.Node{
4825+
*testingnode.MakeNode("x2").
4826+
Label("node-group", "tas").
4827+
Label(utiltesting.DefaultBlockTopologyLevel, "b1").
4828+
Label(utiltesting.DefaultRackTopologyLevel, "r1").
4829+
Label(corev1.LabelHostname, "x2").
4830+
StatusAllocatable(corev1.ResourceList{
4831+
corev1.ResourceCPU: resource.MustParse("5"),
4832+
corev1.ResourceMemory: resource.MustParse("5Gi"),
4833+
corev1.ResourcePods: resource.MustParse("10"),
4834+
}).
4835+
Ready().
4836+
Obj(),
4837+
*testingnode.MakeNode("x1").
4838+
Label("node-group", "tas").
4839+
Label(utiltesting.DefaultBlockTopologyLevel, "b1").
4840+
Label(utiltesting.DefaultRackTopologyLevel, "r2").
4841+
Label(corev1.LabelHostname, "x1").
4842+
StatusAllocatable(corev1.ResourceList{
4843+
corev1.ResourceCPU: resource.MustParse("5"),
4844+
corev1.ResourceMemory: resource.MustParse("5Gi"),
4845+
corev1.ResourcePods: resource.MustParse("10"),
4846+
}).
4847+
Ready().
4848+
Obj(),
4849+
*testingnode.MakeNode("x3").
4850+
Label("node-group", "tas").
4851+
Label(utiltesting.DefaultBlockTopologyLevel, "b1").
4852+
Label(utiltesting.DefaultRackTopologyLevel, "r3").
4853+
Label(corev1.LabelHostname, "x3").
4854+
StatusAllocatable(corev1.ResourceList{
4855+
corev1.ResourceCPU: resource.MustParse("2"),
4856+
corev1.ResourceMemory: resource.MustParse("5Gi"),
4857+
corev1.ResourcePods: resource.MustParse("10"),
4858+
}).
4859+
Ready().
4860+
Obj(),
4861+
}
4862+
util.CreateNodesWithStatus(ctx, k8sClient, nodes)
4863+
4864+
topology = utiltestingapi.MakeDefaultThreeLevelTopology("default")
4865+
util.MustCreate(ctx, k8sClient, topology)
4866+
4867+
tasFlavor = utiltestingapi.MakeResourceFlavor("tas-flavor").
4868+
NodeLabel("node-group", "tas").
4869+
TopologyName("default").Obj()
4870+
util.MustCreate(ctx, k8sClient, tasFlavor)
4871+
4872+
clusterQueue = utiltestingapi.MakeClusterQueue("cluster-queue").
4873+
Cohort("cohort").
4874+
Preemption(kueue.ClusterQueuePreemption{
4875+
WithinClusterQueue: kueue.PreemptionPolicyLowerPriority,
4876+
ReclaimWithinCohort: kueue.PreemptionPolicyAny,
4877+
BorrowWithinCohort: &kueue.BorrowWithinCohort{
4878+
Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
4879+
},
4880+
}).
4881+
ResourceGroup(*utiltestingapi.MakeFlavorQuotas(tasFlavor.Name).
4882+
Resource(corev1.ResourceCPU, "4").
4883+
Resource(corev1.ResourceMemory, "5Gi").Obj()).
4884+
Obj()
4885+
util.MustCreate(ctx, k8sClient, clusterQueue)
4886+
util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueue)
4887+
4888+
localQueue = utiltestingapi.MakeLocalQueue("local-queue", ns.Name).ClusterQueue(clusterQueue.Name).Obj()
4889+
util.MustCreate(ctx, k8sClient, localQueue)
4890+
4891+
clusterQueueB = utiltestingapi.MakeClusterQueue("cluster-queue-b").
4892+
Cohort("cohort").
4893+
Preemption(kueue.ClusterQueuePreemption{
4894+
WithinClusterQueue: kueue.PreemptionPolicyLowerPriority,
4895+
ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
4896+
BorrowWithinCohort: &kueue.BorrowWithinCohort{
4897+
Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
4898+
},
4899+
}).
4900+
ResourceGroup(*utiltestingapi.MakeFlavorQuotas(tasFlavor.Name).
4901+
Resource(corev1.ResourceCPU, "3").
4902+
Resource(corev1.ResourceMemory, "5Gi").Obj()).
4903+
Obj()
4904+
util.MustCreate(ctx, k8sClient, clusterQueueB)
4905+
util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueueB)
4906+
4907+
localQueueB = utiltestingapi.MakeLocalQueue("local-queue-b", ns.Name).ClusterQueue(clusterQueueB.Name).Obj()
4908+
util.MustCreate(ctx, k8sClient, localQueueB)
4909+
4910+
clusterQueueC = utiltestingapi.MakeClusterQueue("cluster-queue-c").
4911+
Cohort("cohort").
4912+
Preemption(kueue.ClusterQueuePreemption{
4913+
WithinClusterQueue: kueue.PreemptionPolicyLowerPriority,
4914+
ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
4915+
BorrowWithinCohort: &kueue.BorrowWithinCohort{
4916+
Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
4917+
},
4918+
}).
4919+
ResourceGroup(*utiltestingapi.MakeFlavorQuotas(tasFlavor.Name).
4920+
Resource(corev1.ResourceCPU, "5").
4921+
Resource(corev1.ResourceMemory, "5Gi").Obj()).
4922+
Obj()
4923+
util.MustCreate(ctx, k8sClient, clusterQueueC)
4924+
util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueueC)
4925+
4926+
localQueueC = utiltestingapi.MakeLocalQueue("local-queue-c", ns.Name).ClusterQueue(clusterQueueC.Name).Obj()
4927+
util.MustCreate(ctx, k8sClient, localQueueC)
4928+
})
4929+
4930+
ginkgo.AfterEach(func() {
4931+
gomega.Expect(util.DeleteAllJobsInNamespace(ctx, k8sClient, ns)).Should(gomega.Succeed())
4932+
gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).Should(gomega.Succeed())
4933+
gomega.Expect(util.DeleteObject(ctx, k8sClient, localQueue)).Should(gomega.Succeed())
4934+
gomega.Expect(util.DeleteObject(ctx, k8sClient, localQueueB)).Should(gomega.Succeed())
4935+
gomega.Expect(util.DeleteObject(ctx, k8sClient, localQueueC)).Should(gomega.Succeed())
4936+
4937+
util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
4938+
util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueueB, true)
4939+
util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueueC, true)
4940+
util.ExpectObjectToBeDeleted(ctx, k8sClient, tasFlavor, true)
4941+
util.ExpectObjectToBeDeleted(ctx, k8sClient, topology, true)
4942+
for _, node := range nodes {
4943+
util.ExpectObjectToBeDeleted(ctx, k8sClient, &node, true)
4944+
}
4945+
})
4946+
4947+
ginkgo.It("should prevent other workloads from stealing topology when a preemptor is waiting for multiple evictions", func() {
4948+
var wlA, wlB, wlPending, preemptor *kueue.Workload
4949+
ginkgo.By("creating initial workload in clusterQueue consuming 4 CPU on first node", func() {
4950+
wlA = utiltestingapi.MakeWorkload("wl-a", ns.Name).
4951+
Priority(1).
4952+
PodSets(*utiltestingapi.MakePodSet("worker", 1).
4953+
PreferredTopologyRequest(utiltesting.DefaultBlockTopologyLevel).
4954+
Obj()).
4955+
Queue(kueue.LocalQueueName(localQueue.Name)).Request(corev1.ResourceCPU, "4").Obj()
4956+
util.MustCreate(ctx, k8sClient, wlA)
4957+
})
4958+
4959+
ginkgo.By("creating initial workload in clusterQueueB consuming 5 CPU on second node (borrowing 2 CPU)", func() {
4960+
wlB = utiltestingapi.MakeWorkload("wl-b", ns.Name).
4961+
Priority(2).
4962+
PodSets(*utiltestingapi.MakePodSet("worker", 1).
4963+
PreferredTopologyRequest(utiltesting.DefaultBlockTopologyLevel).
4964+
Obj()).
4965+
Queue(kueue.LocalQueueName(localQueueB.Name)).Request(corev1.ResourceCPU, "5").Obj()
4966+
util.MustCreate(ctx, k8sClient, wlB)
4967+
})
4968+
4969+
ginkgo.By("verify both workloads are admitted", func() {
4970+
util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, wlA, wlB)
4971+
})
4972+
4973+
ginkgo.By("creating a pending workload in clusterQueueC within its nominal quota (3 CPU) that cannot fit due to fragmented node capacity", func() {
4974+
wlPending = utiltestingapi.MakeWorkload("wl-pending", ns.Name).
4975+
Priority(1).
4976+
PodSets(*utiltestingapi.MakePodSet("worker", 1).
4977+
PreferredTopologyRequest(utiltesting.DefaultBlockTopologyLevel).
4978+
Obj()).
4979+
Queue(kueue.LocalQueueName(localQueueC.Name)).Request(corev1.ResourceCPU, "3").Obj()
4980+
util.MustCreate(ctx, k8sClient, wlPending)
4981+
util.ExpectWorkloadsToBePending(ctx, k8sClient, wlPending)
4982+
})
4983+
4984+
ginkgo.By("creating a high priority preemptor in clusterQueue requesting 2 pods of 4 CPU (borrowing from cohort)", func() {
4985+
preemptor = utiltestingapi.MakeWorkload("preemptor", ns.Name).
4986+
Priority(3).
4987+
PodSets(*utiltestingapi.MakePodSet("worker", 2).
4988+
PreferredTopologyRequest(utiltesting.DefaultBlockTopologyLevel).
4989+
Obj()).
4990+
Queue(kueue.LocalQueueName(localQueue.Name)).Request(corev1.ResourceCPU, "4").Obj()
4991+
util.MustCreate(ctx, k8sClient, preemptor)
4992+
})
4993+
4994+
ginkgo.By("verifying both wl-a and wl-b are marked for preemption", func() {
4995+
util.ExpectWorkloadsToBePreempted(ctx, k8sClient, wlA, wlB)
4996+
})
4997+
4998+
ginkgo.By("finishing eviction for wl-a only", func() {
4999+
util.FinishEvictionForWorkloads(ctx, k8sClient, wlA)
5000+
})
5001+
5002+
ginkgo.By("ensuring the pending workload wl-pending in clusterQueueC is not admitted in the interim across scheduling cycles", func() {
5003+
gomega.Consistently(func(g gomega.Gomega) {
5004+
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wlPending), wlPending)).To(gomega.Succeed())
5005+
g.Expect(workload.HasQuotaReservation(wlPending)).To(gomega.BeFalse())
5006+
}, util.ConsistentDuration, util.ShortInterval).Should(gomega.Succeed())
5007+
})
5008+
5009+
ginkgo.By("finishing eviction for wl-b", func() {
5010+
util.FinishEvictionForWorkloads(ctx, k8sClient, wlB)
5011+
})
5012+
5013+
ginkgo.By("verifying preemptor is admitted and wl-pending remains pending with available quota but fragmented topology", func() {
5014+
util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, preemptor)
5015+
util.ExpectWorkloadsToBePending(ctx, k8sClient, wlPending)
5016+
5017+
gomega.Eventually(func(g gomega.Gomega) {
5018+
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wlPending), wlPending)).To(gomega.Succeed())
5019+
cond := apimeta.FindStatusCondition(wlPending.Status.Conditions, kueue.WorkloadQuotaReserved)
5020+
g.Expect(cond).ToNot(gomega.BeNil())
5021+
g.Expect(cond.Status).To(gomega.Equal(metav1.ConditionFalse))
5022+
g.Expect(cond.Reason).To(gomega.Equal(kueue.WorkloadQuotaReservedReasonWaitingForQuota))
5023+
g.Expect(cond.Message).To(gomega.ContainSubstring(`topology "default" doesn't allow to fit any of 1 pod(s)`))
5024+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
5025+
})
5026+
})
5027+
})
5028+
48065029
ginkgo.When("Node structure is mutated during test cases", func() {
48075030
var (
48085031
nodes []corev1.Node

0 commit comments

Comments
 (0)