This is an automated email from the ASF dual-hosted git repository.
manirajv06 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-k8shim.git
The following commit(s) were added to refs/heads/master by this push:
new 65ba4695 [YUNIKORN-3204] Quota Preemption E2E testing(#1009)
65ba4695 is described below
commit 65ba46959350355ba829a035efaa82ba99411d2f
Author: Aditya Maheshwari <[email protected]>
AuthorDate: Mon May 18 15:48:05 2026 +0530
[YUNIKORN-3204] Quota Preemption E2E testing(#1009)
Closes: #1009
Signed-off-by: Manikandan R <[email protected]>
---
go.mod | 2 +-
go.sum | 2 +
test/e2e/framework/helpers/k8s/k8s_utils.go | 30 +
.../quota_preemption_suite_test.go | 77 +++
test/e2e/quota-preemption/quota_preemption_test.go | 675 +++++++++++++++++++++
...n-configs-parent-delay-child-quota-reduced.yaml | 54 ++
...nikorn-configs-parent-delay-no-child-delay.yaml | 54 ++
...yunikorn-configs-quota-above-current-usage.yaml | 58 ++
...figs-quota-further-reduced-100Mi-10s-delay.yaml | 53 ++
.../configs/yunikorn-configs-quota-increased.yaml | 50 ++
...yunikorn-configs-quota-preemption-disabled.yaml | 53 ++
.../yunikorn-configs-quota-preemption-enabled.yaml | 50 ++
...nikorn-configs-quota-preemption-zero-delay.yaml | 49 ++
.../yunikorn-configs-quota-reduced-15s-delay.yaml | 53 ++
...korn-configs-quota-reduced-250Mi-30s-delay.yaml | 53 ++
.../yunikorn-configs-quota-reduced-5s-delay.yaml | 51 ++
...-configs-quota-reduced-preemption-disabled.yaml | 53 ++
.../configs/yunikorn-configs-quota-reduced.yaml | 50 ++
...-reduced-quota-preemption-20-seconds-delay.yaml | 49 ++
...onfigs-reduced-quota-preemption-zero-delay.yaml | 49 ++
.../deployments/deployment-app-b.yaml | 47 ++
.../quota-preemption/deployments/deployment1.yaml | 48 ++
.../quota-preemption/deployments/deployment2.yaml | 47 ++
23 files changed, 1706 insertions(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index fa24562f..4c80f094 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
github.com/google/uuid v1.6.0
github.com/looplab/fsm v1.0.3
github.com/onsi/ginkgo/v2 v2.27.2
- github.com/onsi/gomega v1.38.2
+ github.com/onsi/gomega v1.40.0
github.com/prometheus/client_golang v1.23.2
github.com/sasha-s/go-deadlock v0.3.9
go.uber.org/zap v1.27.1
diff --git a/go.sum b/go.sum
index 02ade139..69a7e225 100644
--- a/go.sum
+++ b/go.sum
@@ -153,6 +153,8 @@ github.com/onsi/ginkgo/v2 v2.27.2
h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns
github.com/onsi/ginkgo/v2 v2.27.2/go.mod
h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod
h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
+github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
+github.com/onsi/gomega v1.40.0/go.mod
h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
github.com/opencontainers/go-digest v1.0.0
h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod
h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/peterbourgon/diskv v2.0.1+incompatible
h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
diff --git a/test/e2e/framework/helpers/k8s/k8s_utils.go
b/test/e2e/framework/helpers/k8s/k8s_utils.go
index f53ebe46..e450bec8 100644
--- a/test/e2e/framework/helpers/k8s/k8s_utils.go
+++ b/test/e2e/framework/helpers/k8s/k8s_utils.go
@@ -795,6 +795,18 @@ func GetPodObj(yamlPath string) (*v1.Pod, error) {
return pod, nil
}
+func GetDeploymentObj(yamlPath string) (*appsv1.Deployment, error) {
+ o, err := common.Yaml2Obj(yamlPath)
+ if err != nil {
+ return nil, err
+ }
+ deployment, ok := o.(*appsv1.Deployment)
+ if !ok {
+ return nil, fmt.Errorf("failed to convert object to Deployment")
+ }
+ return deployment, nil
+}
+
func (k *KubeCtl) CreateDeployment(deployment *appsv1.Deployment, namespace
string) (*appsv1.Deployment, error) {
return
k.clientSet.AppsV1().Deployments(namespace).Create(context.TODO(), deployment,
metav1.CreateOptions{})
}
@@ -1113,6 +1125,24 @@ func (k *KubeCtl) WaitForPodBySelectorRunning(namespace
string, selector string,
return nil
}
+func (k *KubeCtl) WaitForNPodsBySelectorRunning(namespace string, selector
string, expectedCount int, timeout time.Duration) error {
+ return wait.PollUntilContextTimeout(context.TODO(),
time.Millisecond*100, timeout, false, func(ctx context.Context) (bool, error) {
+ podList, err := k.ListPods(namespace, selector)
+ if err != nil {
+ return false, err
+ }
+ if len(podList.Items) != expectedCount {
+ return false, nil
+ }
+ for _, pod := range podList.Items {
+ if pod.Status.Phase != v1.PodRunning {
+ return false, nil
+ }
+ }
+ return true, nil
+ })
+}
+
// Wait for all pods in 'namespace' with given 'selector' to enter succeeded
state.
// Returns an error if no pods are found or not all discovered pods enter
succeeded state.
func (k *KubeCtl) WaitForPodBySelectorSucceeded(namespace string, selector
string, timeout time.Duration) error {
diff --git a/test/e2e/quota-preemption/quota_preemption_suite_test.go
b/test/e2e/quota-preemption/quota_preemption_suite_test.go
new file mode 100644
index 00000000..83abc1b9
--- /dev/null
+++ b/test/e2e/quota-preemption/quota_preemption_suite_test.go
@@ -0,0 +1,77 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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 quota_preemption_test
+
+import (
+ "path/filepath"
+ "runtime"
+ "testing"
+
+ "github.com/onsi/ginkgo/v2"
+ "github.com/onsi/ginkgo/v2/reporters"
+ "github.com/onsi/gomega"
+
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/configmanager"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/common"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/k8s"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/yunikorn"
+)
+
+func init() {
+ configmanager.YuniKornTestConfig.ParseFlags()
+}
+
+func TestQuotaPreemption(t *testing.T) {
+ ginkgo.ReportAfterSuite("TestQuotaPreemption", func(report
ginkgo.Report) {
+ err := reporters.GenerateJUnitReportWithConfig(
+ report,
+ filepath.Join(configmanager.YuniKornTestConfig.LogDir,
"TEST-quotapreemption_junit.xml"),
+ reporters.JunitReportConfig{OmitSpecLabels: true},
+ )
+ Ω(err).NotTo(HaveOccurred())
+ })
+ gomega.RegisterFailHandler(ginkgo.Fail)
+ ginkgo.RunSpecs(t, "TestQuotaPreemption",
ginkgo.Label("TestQuotaPreemption"))
+}
+
+var Ω = gomega.Ω
+var HaveOccurred = gomega.HaveOccurred
+
+var _ = ginkgo.BeforeSuite(func() {
+ _, filename, _, _ := runtime.Caller(0)
+ suiteName = common.GetSuiteName(filename)
+ // Initializing kubectl client
+ kClient = k8s.KubeCtl{}
+ Ω(kClient.SetClient()).To(gomega.Succeed())
+ // Initializing rest client
+ restClient = yunikorn.RClient{}
+ Ω(restClient).NotTo(gomega.BeNil())
+
+ yunikorn.EnsureYuniKornConfigsPresent()
+
+ ginkgo.By("Port-forward the scheduler pod")
+ var err = kClient.PortForwardYkSchedulerPod()
+ Ω(err).NotTo(gomega.HaveOccurred())
+})
+
+var _ = ginkgo.AfterSuite(func() {
+ ginkgo.By("Check Yunikorn's health")
+ checks, err := yunikorn.GetFailedHealthChecks()
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(checks).To(gomega.Equal(""), checks)
+})
diff --git a/test/e2e/quota-preemption/quota_preemption_test.go
b/test/e2e/quota-preemption/quota_preemption_test.go
new file mode 100644
index 00000000..d9bde2e0
--- /dev/null
+++ b/test/e2e/quota-preemption/quota_preemption_test.go
@@ -0,0 +1,675 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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 quota_preemption_test
+
+import (
+ "time"
+
+ "github.com/onsi/ginkgo/v2"
+ "github.com/onsi/gomega"
+ v1 "k8s.io/api/core/v1"
+
+ tests "github.com/apache/yunikorn-k8shim/test/e2e"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/configmanager"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/common"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/k8s"
+ "github.com/apache/yunikorn-k8shim/test/e2e/framework/helpers/yunikorn"
+)
+
+var suiteName string
+var kClient k8s.KubeCtl
+var restClient yunikorn.RClient
+var oldConfigMap = new(v1.ConfigMap)
+var dev string
+var ns *v1.Namespace
+
+var _ = ginkgo.Describe("QuotaPreemption", func() {
+
+ ginkgo.BeforeEach(func() {
+ dev = "dev-" + common.RandSeq(5)
+ ginkgo.By("Creating namespace " + dev + " for testing")
+ var err error
+ ns, err = kClient.CreateNamespace(dev, nil)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(ns.Status.Phase).To(gomega.Equal(v1.NamespaceActive))
+ ginkgo.By("Get previous config")
+ oldConfigMap, err =
kClient.GetConfigMaps(configmanager.YuniKornTestConfig.YkNamespace,
+ configmanager.DefaultYuniKornConfigMap)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(oldConfigMap).NotTo(gomega.BeNil())
+ })
+
+ ginkgo.It("Check_Basic_Quota_Preemption", func() {
+ ginkgo.By("Quota preemption should be triggered when quota is
reduced and delay is set.")
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
enabled")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
10*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 10)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All pods are running as expected before quota
preemption is triggered")
+
+ // update configmap and reduce quota to trigger quota preemption
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with reduced quota to
trigger quota preemption")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for quota preemption to take effect
(delay=2s)")
+ gomega.Eventually(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount, pendingCount := 0, 0
+ for _, pod := range pods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ runningCount++
+ case v1.PodPending:
+ pendingCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(2), "Expected 2 pods
to be running after quota preemption, but got %d", runningCount)
+ g.Ω(pendingCount).To(gomega.Equal(1), "Expected 1 pod
to be pending after quota preemption, but got %d", pendingCount)
+ }, 10*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Quota_Preemption_NOT_triggered_On_Quota_Increase", func() {
+ ginkgo.By("Quota preemption should not be triggered when quota
is increased.")
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
enabled")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
10*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 10)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All pods are running as expected before quota
preemption is triggered")
+
+ // update configmap and increase quota, quota preemption should
not be triggered
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-increased.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with reduced quota to
trigger quota preemption")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Verifying no quota preemption occurs for 5 seconds
after quota increase")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(3), "Expected 3 pods
to remain running after quota increase, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Check_Quota_Preemption_With_Delay", func() {
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
enabled")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
30*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All pods are running as expected before quota
preemption is triggered")
+
+ // update configmap and reduce quota to trigger quota preemption
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-20-seconds-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with reduced quota to
trigger quota preemption")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("Verifying preemption has not triggered yet (within
20s delay window)")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(3), "Expected all 3
pods still running before delay elapses, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ ginkgo.By("All pods are still running during delay window as
expected")
+
+ ginkgo.By("Waiting for quota preemption to fire after 20s
delay")
+ gomega.Eventually(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount, pendingCount := 0, 0
+ for _, pod := range pods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ runningCount++
+ case v1.PodPending:
+ pendingCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(2), "Expected 2 pods
to be running after quota preemption, but got %d", runningCount)
+ g.Ω(pendingCount).To(gomega.Equal(1), "Expected 1 pod
to be pending after quota preemption, but got %d", pendingCount)
+ }, 30*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Quota_Preemption_With_Zero_Delay", func() {
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-zero-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
enabled and zero delay")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
30*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // update configmap and reduce quota to trigger quota preemption
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-preemption-disabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with reduced quota to
trigger quota preemption")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Verifying no quota preemption occurs for 5 seconds
(preemption disabled)")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(3), "Expected 3 pods
to remain running with preemption disabled, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Basic_Quota_Preemption_Disabled", func() {
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-disabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
disabled")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
30*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // update configmap and reduce quota to trigger quota preemption
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-preemption-disabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with reduced quota to
trigger quota preemption")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Verifying no quota preemption occurs for 5 seconds
(preemption disabled)")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(3), "Expected 3 pods
to remain running with preemption disabled, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.PIt("Needs Investigation -
Quota_Preemption_Delay_Inherited_From_Parent", func() {
+ ginkgo.By("Quota preemption delay set on parent queue should be
inherited by child queues that do not set it explicitly.")
+
+ // Apply initial config: parent has quota.preemption.delay=20s,
child (queue-a) has no delay set.
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-parent-delay-no-child-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map: parent delay=20s, child
queue has no explicit delay")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all 3 deployment pods to be running")
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All 3 pods are running before quota is reduced")
+
+ // Reduce queue-a's quota to trigger quota preemption. Parent
still has delay=20s; child still has no delay.
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-parent-delay-child-quota-reduced.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Reducing queue-a quota to trigger preemption; delay
should be inherited from parent (20s)")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // The inherited delay is 20s. After 5s preemption must NOT
have fired yet.
+ ginkgo.By("Verifying preemption has not triggered yet
(inherited 20s delay has not elapsed)")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(3), "Expected all 3
pods still running before inherited delay elapses, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ ginkgo.By("All 3 pods are still running 5s after quota
reduction — inherited delay is being honoured")
+
+ ginkgo.By("Waiting for inherited preemption delay to fire after
~20s")
+ gomega.Eventually(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount, pendingCount := 0, 0
+ for _, pod := range pods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ runningCount++
+ case v1.PodPending:
+ pendingCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(2), "Expected 2 pods
running after preemption triggered by inherited delay, but got %d",
runningCount)
+ g.Ω(pendingCount).To(gomega.Equal(1), "Expected 1 pod
pending after preemption triggered by inherited delay, but got %d",
pendingCount)
+ }, 30*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Quota_Preemption_NOT_Triggered_When_New_Quota_Above_Usage",
func() {
+ ginkgo.By("Quota preemption should NOT be triggered when the
new (reduced) quota is still above current resource usage.")
+
+ // Initial config: max=300Mi/300m, quota preemption enabled,
delay=2s.
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating the config map with quota preemption
enabled (max=300Mi)")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Deploy only 2 pods → 200Mi/200m in use, which is below the
initial max of 300Mi.
+ deployment2, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment2.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment2).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment with 2 replicas in namespace " +
dev)
+ _, err = kClient.CreateDeployment(deployment2, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all 2 deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
30*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("Both pods are running (200Mi in use) before quota is
changed")
+
+ // Reduce max quota to 250Mi — still above the 200Mi currently
in use.
+ // Preemption must NOT fire because usage (200Mi) <= new quota
(250Mi).
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-above-current-usage.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Reducing max quota to 250Mi — still above current
usage of 200Mi")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Verifying no preemption occurs for 5s (new quota is
still above current usage)")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ runningCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(2), "Expected 2 pods
to remain running when new quota is above usage, but got %d", runningCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Quota_Preemption_Delay_Timer_Reset_On_Delay_Update", func() {
+ ginkgo.By("The preemption delay timer must restart from zero
when quota.preemption.delay is updated in config.")
+
+ // Step 1: Set up initial state — 3 pods running, max=300Mi,
quota preemption enabled.
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Applying initial config: quota preemption enabled,
max=300Mi")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all 3 deployment pods to be running")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
30*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All 3 pods are running before quota is reduced")
+
+ // Step 2: Reduce quota below current usage with a short 5s
delay.
+ // The scheduler detects a violation and starts a 5s countdown
timer.
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-5s-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Reducing quota to 200Mi with delay=5s; preemption
violation timer starts")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Step 3: Before the original 5s delay elapses, update the
delay to 15s.
+ // This must reset the timer: preemption should fire 15s from
*this* update,
+ // not 5s from the original quota reduction.
+ ginkgo.By("Sleeping 2s (before the original 5s delay fires)")
+ time.Sleep(2 * time.Second)
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-15s-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Updating delay to 15s while quota violation is still
active — timer must reset")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Step 4: Verify all 3 pods remain running for 5s past the
original 5s deadline.
+ // If the timer was NOT reset, preemption would have already
fired. Consistent
+ // observation proves the reset 15s window is still counting
down.
+ ginkgo.By("Verifying all 3 pods stay running for 5s — past
original deadline, within reset 15s window")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ stillRunning := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ stillRunning++
+ }
+ }
+ g.Ω(stillRunning).To(gomega.Equal(3), "Expected all 3
pods still running (timer was reset); got %d running", stillRunning)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+
+ // Step 5: Wait for preemption to fire after the reset 15s
delay.
+ ginkgo.By("Waiting for preemption to fire after the reset 15s
delay elapses")
+ gomega.Eventually(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount, pendingCount := 0, 0
+ for _, pod := range pods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ runningCount++
+ case v1.PodPending:
+ pendingCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(2), "Expected 2 pods
running after preemption fired with reset delay, but got %d", runningCount)
+ g.Ω(pendingCount).To(gomega.Equal(1), "Expected 1 pod
pending after preemption fired with reset delay, but got %d", pendingCount)
+ }, 15*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.It("Quota_Preemption_Delay_Timer_Reset_On_Quota_Re_Reduction",
func() {
+ ginkgo.By("The preemption delay timer must restart from zero
when quota is reduced again before the delay elapses.")
+
+ // Step 1: Start with quota preemption enabled, 3 pods running
(3×100Mi = 300Mi).
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Applying initial config: quota preemption enabled,
max=300Mi")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating deployment in namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ ginkgo.By("Waiting for all 3 deployment pods to be running")
+ err = kClient.WaitForNPodsBySelectorRunning(dev, "app=app-a",
3, 5*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("All 3 pods are running before quota is first
reduced")
+
+ // Step 2: First quota reduction — max=200Mi with delay=5s.
+ // 300Mi in use > 200Mi max → violation detected; 5s countdown
timer starts.
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-5s-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("First quota reduction: max=200Mi, delay=5s —
violation timer starts")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Step 3: Before the 5s fires, reduce quota again to 100Mi
(still delay=10s).
+ // The timer must restart from zero.
+ // After preemption at 100Mi only 1 pod (100Mi) can remain
running.
+ ginkgo.By("Sleeping 3s — before the original 5s delay fires")
+ time.Sleep(3 * time.Second)
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-further-reduced-100Mi-10s-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Second quota reduction: max=100Mi, delay=10s — timer
must reset")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Step 4: Verify all 3 pods remain running for 6s past the
original 5s deadline.
+ // The original countdown would have fired at t=5s from the
first reduction;
+ // consistent observation of 3 running pods proves the timer
was properly reset.
+ ginkgo.By("Verifying all 3 pods stay running for 6s — past
original deadline, within reset 5s window")
+ gomega.Consistently(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ stillRunning := 0
+ for _, pod := range pods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ stillRunning++
+ }
+ }
+ g.Ω(stillRunning).To(gomega.Equal(3), "Expected all 3
pods still running (timer was reset); got %d running", stillRunning)
+ }, 6*time.Second, time.Second).Should(gomega.Succeed())
+
+ // Step 5: Wait for preemption to fire after the reset 10s
delay (~11s since second reduction).
+ // At the new 100Mi quota, only 1 pod can remain running.
+ ginkgo.By("Waiting for preemption to fire after the reset 5s
delay elapses")
+ gomega.Eventually(func(g gomega.Gomega) {
+ pods, err := kClient.ListPodsByLabelSelector(dev,
"queue=root.parent.queue-a")
+ g.Ω(err).NotTo(gomega.HaveOccurred())
+ runningCount, pendingCount := 0, 0
+ for _, pod := range pods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ runningCount++
+ case v1.PodPending:
+ pendingCount++
+ }
+ }
+ g.Ω(runningCount).To(gomega.Equal(1), "Expected 1 pod
running after preemption at 100Mi quota, but got %d", runningCount)
+ g.Ω(pendingCount).To(gomega.Equal(2), "Expected 2 pods
pending after preemption at 100Mi quota, but got %d", pendingCount)
+ }, 5*time.Second, time.Second).Should(gomega.Succeed())
+ })
+
+ ginkgo.PIt("Needs
Investigation-New_Pods_Not_Allocated_While_Over_Quota_Then_Scheduled_After_Preemption",
func() {
+ ginkgo.By("New pods must stay Pending while the queue is over
quota; they become schedulable once old pods are preempted back within quota.")
+
+ // Step 1: Set up initial state — 3 × 100Mi pods in queue-a
(300Mi total), max=300Mi.
+ configMap, err :=
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Applying initial config: max=300Mi, quota preemption
enabled")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred(), "Failed to apply initial
config with quota preemption enabled")
+
+ deployment1, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment1.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deployment1).NotTo(gomega.BeNil())
+ ginkgo.By("Creating 3-replica deployment (app-a, 100Mi each) in
namespace " + dev)
+ _, err = kClient.CreateDeployment(deployment1, dev)
+ Ω(err).NotTo(gomega.HaveOccurred(), "Failed to create initial
deployment with 3 pods")
+
+ ginkgo.By("Waiting for all 3 app-a pods to be running (300Mi
used = 300Mi max)")
+ err = kClient.WaitForPodBySelector(dev, "app=app-a",
10*time.Second)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-a", 10)
+ Ω(err).NotTo(gomega.HaveOccurred(), "Expected all 3 app-a pods
to be running before quota reduction, but they were not: %v", err)
+
+ // Step 2: Reduce quota to 250Mi with a 30s delay — violation
detected (300Mi > 250Mi),
+ // and simultaneously submit a new 50Mi pod (app-b) to the same
over-quota queue.
+ configMap, err =
k8s.GetConfigMapObj("../testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-250Mi-30s-delay.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(configMap).NotTo(gomega.BeNil())
+ ginkgo.By("Reducing max quota to 250Mi with 30s delay —
violation timer starts")
+ _, err = kClient.UpdateConfigMap(configMap,
configmanager.YuniKornTestConfig.YkNamespace)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ deploymentAppB, err :=
k8s.GetDeploymentObj("../testdata/quota-preemption/deployments/deployment-app-b.yaml")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ Ω(deploymentAppB).NotTo(gomega.BeNil())
+ ginkgo.By("Submitting new 50Mi pod (app-b) to queue-a while it
is over quota (300Mi > 250Mi)")
+ _, err = kClient.CreateDeployment(deploymentAppB, dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // Step 3: During the 30s delay window, verify continuously
that no new allocations occur:
+ // app-a: all 3 pods still Running (preemption has not fired
yet).
+ // app-b: pod must stay Pending — the queue is over its max
quota.
+ ginkgo.By("Verifying app-a still running and app-b Pending for
25s within the 30s delay window")
+ gomega.Consistently(func(g gomega.Gomega) {
+ appAPods, listErr :=
kClient.ListPodsByLabelSelector(dev, "app=app-a")
+ g.Ω(listErr).NotTo(gomega.HaveOccurred())
+ appARunning := 0
+ for _, pod := range appAPods.Items {
+ if pod.Status.Phase == v1.PodRunning {
+ appARunning++
+ }
+ }
+ g.Ω(appARunning).To(gomega.Equal(3), "Expected all 3
app-a pods still running during the delay, got %d", appARunning)
+
+ appBPods, listErr2 :=
kClient.ListPodsByLabelSelector(dev, "app=app-b")
+ g.Ω(listErr2).NotTo(gomega.HaveOccurred())
+ appBPending := 0
+ for _, pod := range appBPods.Items {
+ if pod.Status.Phase == v1.PodPending {
+ appBPending++
+ }
+ }
+ g.Ω(appBPending).To(gomega.Equal(1), "Expected app-b
pod to be Pending while queue is over quota, got %d pending", appBPending)
+ }, 25*time.Second, time.Second).Should(gomega.Succeed())
+ ginkgo.By("app-b pod is Pending — new allocations correctly
blocked while queue is over quota")
+
+ // Step 4: Wait for preemption to fire (30s delay elapses).
+ // After preemption: 1 app-a pod evicted → 2 app-a running
(200Mi ≤ 250Mi).
+ // 50Mi headroom is now available → app-b (50Mi) should be
scheduled:
+ // 200Mi (app-a) + 50Mi (app-b) = 250Mi = max.
+ ginkgo.By("Waiting for the 30s preemption delay to elapse and
app-b to become schedulable (~35s timeout)")
+ err = kClient.WaitForPodBySelectorRunning(dev, "app=app-b", 30)
+ Ω(err).NotTo(gomega.HaveOccurred())
+ ginkgo.By("app-b pod is now Running — allocated once old pods
settled within quota")
+
+ appAPods, err := kClient.ListPodsByLabelSelector(dev,
"app=app-a")
+ Ω(err).NotTo(gomega.HaveOccurred())
+ appARunning := 0
+ appAPending := 0
+ for _, pod := range appAPods.Items {
+ switch pod.Status.Phase {
+ case v1.PodRunning:
+ appARunning++
+ case v1.PodPending:
+ appAPending++
+ }
+ }
+ // 2 app-a pods running (200Mi), deployment controller created
a 3rd replacement that
+ // stays Pending (200Mi + 100Mi = 300Mi > 250Mi max — no room
at the full quota).
+ Ω(appARunning).To(gomega.Equal(2), "Expected 2 app-a pods
running after preemption, got %d", appARunning)
+ Ω(appAPending).To(gomega.Equal(1), "Expected 1 app-a pod
pending (replacement, no quota headroom), got %d", appAPending)
+ })
+
+ ginkgo.AfterEach(func() {
+ tests.DumpClusterInfoIfSpecFailed(suiteName, []string{dev})
+ ginkgo.By("Tear down namespace: " + dev)
+ err := kClient.TearDownNamespace(dev)
+ Ω(err).NotTo(gomega.HaveOccurred())
+
+ // reset config
+ ginkgo.By("Resetting the config map to the original state")
+ yunikorn.RestoreConfigMapWrapper(oldConfigMap)
+ })
+})
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-child-quota-reduced.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-child-quota-reduced.yaml
new file mode 100644
index 00000000..ae57fa4d
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-child-quota-reduced.yaml
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Config used to reduce queue-a's quota so that quota preemption is triggered.
+# The parent queue retains quota.preemption.delay: 20s; queue-a still has no
+# explicit delay, so it continues to inherit the 20s delay from the parent.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ properties:
+ # Delay set on parent; child queue-a inherits this value
+ quota.preemption.delay: 20s
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ # No quota.preemption.delay — inherited from parent (20s)
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-no-child-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-no-child-delay.yaml
new file mode 100644
index 00000000..6afaccdf
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-parent-delay-no-child-delay.yaml
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Config used to verify that quota.preemption.delay set on a parent queue is
+# inherited by child queues that do not set the property explicitly.
+# The parent queue has quota.preemption.delay: 20s; queue-a has no delay.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ properties:
+ # Delay set on parent; child queue-a will inherit this value
+ quota.preemption.delay: 20s
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 200Mi
+ vcore: 200m
+ max:
+ memory: 300Mi
+ vcore: 300m
+ # No quota.preemption.delay — should be inherited from
parent
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-above-current-usage.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-above-current-usage.yaml
new file mode 100644
index 00000000..b188f62d
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-above-current-usage.yaml
@@ -0,0 +1,58 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# This config is used to test that quota preemption is NOT triggered when the
+# new quota is reduced but remains above current resource usage.
+# Initial state: 2 pods running, each 100Mi/100m → 200Mi/200m in use.
+# New quota max = 250Mi/250m, which is above the 200Mi/200m in use, so no
+# preemption should occur.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 200Mi
+ vcore: 200m
+ max:
+ memory: 250Mi
+ vcore: 250m
+ properties:
+ # quota.preemption.delay is set so the preemption engine
+ # would fire if usage exceeded quota — but it must not
+ # because new max (250Mi) is above current usage (200Mi).
+ quota.preemption.delay: 2s
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-further-reduced-100Mi-10s-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-further-reduced-100Mi-10s-delay.yaml
new file mode 100644
index 00000000..75a06f84
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-further-reduced-100Mi-10s-delay.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Quota is further reduced to 100Mi (only 1 of the 3 running 100Mi-pods fits)
+# while the preemption delay is kept at 5s. Used in the quota-re-reduction
+# timer-reset test as the second quota reduction. Only 1 pod can remain
+# running once preemption fires (100Mi max / 100Mi per pod = 1 pod).
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 100Mi
+ vcore: 100m
+ properties:
+ quota.preemption.delay: 10s
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-increased.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-increased.yaml
new file mode 100644
index 00000000..c645fce4
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-increased.yaml
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 400Mi
+ vcore: 400m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 400Mi
+ vcore: 400m
+ properties:
+ # Quota preemption in a queue requires delay to be set
+ quota.preemption.delay: 1s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-disabled.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-disabled.yaml
new file mode 100644
index 00000000..7e5420f7
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-disabled.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ placementrules:
+ - name: provided
+ create: true
+ preemption:
+ enabled: true
+ # Disabling quota preemption
+ quotapreemptionenabled: false
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 200Mi
+ vcore: 200m
+ max:
+ memory: 300Mi
+ vcore: 300m
+ properties:
+ # Quota preemption in a queue requires delay to be set
+ quota.preemption.delay: 2s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml
new file mode 100644
index 00000000..ac6fa425
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-enabled.yaml
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 200Mi
+ vcore: 200m
+ max:
+ memory: 300Mi
+ vcore: 300m
+ properties:
+ # Quota preemption in a queue requires delay to be set
+ quota.preemption.delay: 2s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-zero-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-zero-delay.yaml
new file mode 100644
index 00000000..70288556
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-preemption-zero-delay.yaml
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 200Mi
+ vcore: 200m
+ max:
+ memory: 300Mi
+ vcore: 300m
+ properties:
+ quota.preemption.delay: 0
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-15s-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-15s-delay.yaml
new file mode 100644
index 00000000..cb8cecda
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-15s-delay.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Quota remains reduced at 200Mi (below 3-pod usage of 300Mi) but the
+# preemption delay is updated to 15s. Used in the delay-timer-reset test to
+# verify that the in-flight delay timer is restarted from zero when the delay
+# property changes.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ quota.preemption.delay: 15s
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-250Mi-30s-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-250Mi-30s-delay.yaml
new file mode 100644
index 00000000..4983d9ef
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-250Mi-30s-delay.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Quota is reduced to 250Mi with a 30s preemption delay. With 3 pods × 100Mi =
+# 300Mi in use this triggers a violation. After preemption one 100Mi pod is
+# evicted, leaving 200Mi used (≤ 250Mi max) and 50Mi of free headroom — enough
+# to schedule the 50Mi app-b pod created during the delay window.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 250Mi
+ vcore: 250m
+ properties:
+ quota.preemption.delay: 30s
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-5s-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-5s-delay.yaml
new file mode 100644
index 00000000..cd282565
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-5s-delay.yaml
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+# Quota is reduced to 200Mi (below 3-pod usage of 300Mi) with a 5s preemption
+# delay. Used as the initial violation trigger in the delay-timer-reset test.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ quota.preemption.delay: 5s
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-preemption-disabled.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-preemption-disabled.yaml
new file mode 100644
index 00000000..c12041fb
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced-preemption-disabled.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ placementrules:
+ - name: provided
+ create: true
+ preemption:
+ enabled: true
+ # Disabling quota preemption
+ quotapreemptionenabled: false
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ # Quota preemption in a queue requires delay to be set
+ quota.preemption.delay: 2s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced.yaml
new file mode 100644
index 00000000..5300cda8
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-quota-reduced.yaml
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ # Quota preemption in a queue requires delay to be set
+ quota.preemption.delay: 1s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-20-seconds-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-20-seconds-delay.yaml
new file mode 100644
index 00000000..a1231d64
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-20-seconds-delay.yaml
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ quota.preemption.delay: 20s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-zero-delay.yaml
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-zero-delay.yaml
new file mode 100644
index 00000000..175bc51d
--- /dev/null
+++
b/test/e2e/testdata/quota-preemption/configs/yunikorn-configs-reduced-quota-preemption-zero-delay.yaml
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: yunikorn-configs
+ namespace: yunikorn
+data:
+ log.level: DEBUG
+ queues.yaml: |
+ partitions:
+ - name: default
+ preemption:
+ enabled: true
+ # Enabling quota preemption
+ quotapreemptionenabled: true
+ queues:
+ - name: root
+ submitacl: '*'
+ queues:
+ - name: parent
+ resources:
+ max:
+ memory: 300Mi
+ vcore: 300m
+ queues:
+ - name: queue-a
+ resources:
+ guaranteed:
+ memory: 100Mi
+ vcore: 100m
+ max:
+ memory: 200Mi
+ vcore: 200m
+ properties:
+ quota.preemption.delay: 0s
\ No newline at end of file
diff --git
a/test/e2e/testdata/quota-preemption/deployments/deployment-app-b.yaml
b/test/e2e/testdata/quota-preemption/deployments/deployment-app-b.yaml
new file mode 100644
index 00000000..d1036398
--- /dev/null
+++ b/test/e2e/testdata/quota-preemption/deployments/deployment-app-b.yaml
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-b
+ labels:
+ app: app-b
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: app-b
+ template:
+ metadata:
+ labels:
+ app: app-b
+ applicationId: "app-b-01"
+ queue: "root.parent.queue-a"
+ annotations:
+ yunikorn.apache.org/queue: "root.parent.queue-a"
+ spec:
+ terminationGracePeriodSeconds: 1
+ schedulerName: yunikorn
+ containers:
+ - name: sleep-container
+ image: registry.k8s.io/pause:3.7
+ resources:
+ requests:
+ cpu: "50m"
+ memory: "50Mi"
+ limits:
+ cpu: "50m"
+ memory: "50Mi"
diff --git a/test/e2e/testdata/quota-preemption/deployments/deployment1.yaml
b/test/e2e/testdata/quota-preemption/deployments/deployment1.yaml
new file mode 100644
index 00000000..2a161bba
--- /dev/null
+++ b/test/e2e/testdata/quota-preemption/deployments/deployment1.yaml
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-a
+ labels:
+ app: app-a
+# namespace: default
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: app-a
+ template:
+ metadata:
+ labels:
+ app: app-a
+ applicationId: "app-a-01"
+ queue: "root.parent.queue-a"
+ annotations:
+ yunikorn.apache.org/queue: "root.parent.queue-a"
+ spec:
+ terminationGracePeriodSeconds: 1
+ schedulerName: yunikorn
+ containers:
+ - name: sleep-container
+ image: registry.k8s.io/pause:3.7
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "100Mi"
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
\ No newline at end of file
diff --git a/test/e2e/testdata/quota-preemption/deployments/deployment2.yaml
b/test/e2e/testdata/quota-preemption/deployments/deployment2.yaml
new file mode 100644
index 00000000..46eaeeb0
--- /dev/null
+++ b/test/e2e/testdata/quota-preemption/deployments/deployment2.yaml
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-a
+ labels:
+ app: app-a
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: app-a
+ template:
+ metadata:
+ labels:
+ app: app-a
+ applicationId: "app-a-01"
+ queue: "root.parent.queue-a"
+ annotations:
+ yunikorn.apache.org/queue: "root.parent.queue-a"
+ spec:
+ terminationGracePeriodSeconds: 1
+ schedulerName: yunikorn
+ containers:
+ - name: sleep-container
+ image: registry.k8s.io/pause:3.7
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "100Mi"
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]