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]

Reply via email to