This is an automated email from the ASF dual-hosted git repository.

ccondit 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 16181374 [YUNIKORN-3039] Add support for pod-level resource requests 
(#959)
16181374 is described below

commit 16181374599ac508a3b5e53a288099a65252a4d3
Author: Craig Condit <[email protected]>
AuthorDate: Thu Mar 6 10:04:21 2025 -0600

    [YUNIKORN-3039] Add support for pod-level resource requests (#959)
    
    Kubernetes v1.32 adds alpha-level support for pod-level resource
    specifications. Add support for this feature in YuniKorn to ensure that
    our calculations match what Kubernetes uses.
    
    Closes: #959
---
 pkg/cmd/schedulerplugin/main.go                  |  3 ++
 pkg/cmd/shim/main.go                             |  3 ++
 pkg/common/resource.go                           | 34 ++++++++++--
 pkg/common/resource_test.go                      | 66 ++++++++++++++++++++++++
 pkg/plugin/predicates/predicate_manager.go       | 19 +++++++
 pkg/plugin/predicates/predicate_manager_test.go  |  8 +++
 scripts/{kind-pod-resize.yaml => kind-1.32.yaml} |  1 +
 scripts/run-e2e-tests.sh                         |  8 +--
 8 files changed, 133 insertions(+), 9 deletions(-)

diff --git a/pkg/cmd/schedulerplugin/main.go b/pkg/cmd/schedulerplugin/main.go
index 0651a5eb..66a0a7cd 100644
--- a/pkg/cmd/schedulerplugin/main.go
+++ b/pkg/cmd/schedulerplugin/main.go
@@ -24,9 +24,12 @@ import (
        "k8s.io/kubernetes/cmd/kube-scheduler/app"
 
        "github.com/apache/yunikorn-k8shim/pkg/plugin"
+       "github.com/apache/yunikorn-k8shim/pkg/plugin/predicates"
 )
 
 func main() {
+       predicates.EnableOptionalKubernetesFeatureGates()
+
        command := app.NewSchedulerCommand(
                app.WithPlugin(plugin.SchedulerPluginName, 
plugin.NewSchedulerPlugin))
 
diff --git a/pkg/cmd/shim/main.go b/pkg/cmd/shim/main.go
index b053194c..45bd7eaf 100644
--- a/pkg/cmd/shim/main.go
+++ b/pkg/cmd/shim/main.go
@@ -27,6 +27,7 @@ import (
 
        "github.com/apache/yunikorn-k8shim/pkg/client"
        "github.com/apache/yunikorn-k8shim/pkg/common/constants"
+       "github.com/apache/yunikorn-k8shim/pkg/plugin/predicates"
 
        "github.com/apache/yunikorn-core/pkg/entrypoint"
        "github.com/apache/yunikorn-k8shim/pkg/conf"
@@ -37,6 +38,8 @@ import (
 func main() {
        log.Log(log.Shim).Info(conf.GetBuildInfoString())
 
+       predicates.EnableOptionalKubernetesFeatureGates()
+
        configMaps, err := client.LoadBootstrapConfigMaps()
        if err != nil {
                log.Log(log.Shim).Fatal("Unable to bootstrap configuration", 
zap.Error(err))
diff --git a/pkg/common/resource.go b/pkg/common/resource.go
index f96456a0..36c8c259 100644
--- a/pkg/common/resource.go
+++ b/pkg/common/resource.go
@@ -22,6 +22,7 @@ import (
        "go.uber.org/zap"
        v1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/resource"
+       helpers "k8s.io/component-helpers/resource"
 
        "github.com/apache/yunikorn-k8shim/pkg/log"
        siCommon "github.com/apache/yunikorn-scheduler-interface/lib/go/common"
@@ -68,12 +69,19 @@ func GetPodResource(pod *v1.Pod) (resource *si.Resource) {
                podResource = checkInitContainerRequest(pod, podResource)
        }
 
-       // K8s pod EnableOverHead from:
-       // alpha: v1.16
-       // beta: v1.18
-       // Enables PodOverhead, for accounting pod overheads which are specific 
to a given RuntimeClass
+       // PodLevelResources feature:
+       // alpha: v1.32
+       // beta: v1.33
+       if pod.Spec.Resources != nil && len(pod.Spec.Resources.Requests) > 0 {
+               // pod-level resources, if present, override sum of 
container-level resources
+               // only memory and cpu are supported
+               for name, value := range 
getPodLevelResource(pod.Spec.Resources.Requests).GetResources() {
+                       podResource.Resources[name] = value
+               }
+       }
 
-       // If Overhead is being utilized, add to the total requests for the pod
+       // K8s EnableOverHead feature:
+       // Enables PodOverhead, for accounting pod overheads which are specific 
to a given RuntimeClass
        if pod.Spec.Overhead != nil {
                podOverHeadResource := getResource(pod.Spec.Overhead)
                podResource = Add(podResource, podOverHeadResource)
@@ -268,6 +276,22 @@ func getResource(resourceList v1.ResourceList) 
*si.Resource {
        return resources.Build()
 }
 
+func getPodLevelResource(resourceList v1.ResourceList) *si.Resource {
+       resources := NewResourceBuilder()
+       for name, value := range resourceList {
+               if helpers.IsSupportedPodLevelResource(name) {
+                       switch name {
+                       case v1.ResourceCPU:
+                               vcore := value.MilliValue()
+                               resources.AddResource(siCommon.CPU, vcore)
+                       default:
+                               resources.AddResource(string(name), 
value.Value())
+                       }
+               }
+       }
+       return resources.Build()
+}
+
 func Equals(left *si.Resource, right *si.Resource) bool {
        if left == right {
                return true
diff --git a/pkg/common/resource_test.go b/pkg/common/resource_test.go
index da5f5723..2d1d0197 100644
--- a/pkg/common/resource_test.go
+++ b/pkg/common/resource_test.go
@@ -27,6 +27,7 @@ import (
        apis "k8s.io/apimachinery/pkg/apis/meta/v1"
        k8res "k8s.io/component-helpers/resource"
 
+       "github.com/apache/yunikorn-k8shim/pkg/plugin/predicates"
        siCommon "github.com/apache/yunikorn-scheduler-interface/lib/go/common"
        "github.com/apache/yunikorn-scheduler-interface/lib/go/si"
 )
@@ -373,8 +374,73 @@ func siResourceFromList(list v1.ResourceList) *si.Resource 
{
        return builder.Build()
 }
 
+func TestGetPodResourcesWithPodLevelRequests(t *testing.T) {
+       // ensure required K8s feature gates are enabled
+       predicates.EnableOptionalKubernetesFeatureGates()
+
+       containers := make([]v1.Container, 0)
+
+       // container 01
+       c1Resources := make(map[v1.ResourceName]resource.Quantity)
+       c1Resources[v1.ResourceMemory] = resource.MustParse("500M")
+       c1Resources[v1.ResourceCPU] = resource.MustParse("1")
+       c1Resources["nvidia.com/gpu"] = resource.MustParse("1")
+       containers = append(containers, v1.Container{
+               Name: "container-01",
+               Resources: v1.ResourceRequirements{
+                       Requests: c1Resources,
+               },
+       })
+
+       // container 02
+       c2Resources := make(map[v1.ResourceName]resource.Quantity)
+       c2Resources[v1.ResourceMemory] = resource.MustParse("1024M")
+       c2Resources[v1.ResourceCPU] = resource.MustParse("2")
+       c2Resources["nvidia.com/gpu"] = resource.MustParse("4")
+       containers = append(containers, v1.Container{
+               Name: "container-02",
+               Resources: v1.ResourceRequirements{
+                       Requests: c2Resources,
+               },
+       })
+
+       // pod
+       pod := &v1.Pod{
+               TypeMeta: apis.TypeMeta{
+                       Kind:       "Pod",
+                       APIVersion: "v1",
+               },
+               ObjectMeta: apis.ObjectMeta{
+                       Name: "pod-resource-test-00001",
+                       UID:  "UID-00001",
+               },
+               Spec: v1.PodSpec{
+                       Resources: &v1.ResourceRequirements{
+                               Requests: map[v1.ResourceName]resource.Quantity{
+                                       v1.ResourceMemory: 
resource.MustParse("128M"),
+                                       v1.ResourceCPU:    
resource.MustParse("5"),
+                                       "invalid":         
resource.MustParse("1"),
+                               },
+                       },
+                       Containers: containers,
+               },
+       }
+
+       // verify cpu and memory overrides
+       res := GetPodResource(pod)
+       assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), 
int64(128*1000*1000))
+       assert.Equal(t, res.Resources[siCommon.CPU].GetValue(), int64(5000))
+       assert.Equal(t, res.Resources["nvidia.com/gpu"].GetValue(), int64(5))
+       assert.Equal(t, res.Resources["pods"].GetValue(), int64(1))
+       _, invalidOk := res.Resources["invalid"]
+       assert.Assert(t, !invalidOk, "invalid should not be present")
+}
+
 //nolint:funlen
 func TestGetPodResourcesWithInPlacePodVerticalScaling(t *testing.T) {
+       // ensure required K8s feature gates are enabled
+       predicates.EnableOptionalKubernetesFeatureGates()
+
        containers := make([]v1.Container, 0)
 
        // container 01
diff --git a/pkg/plugin/predicates/predicate_manager.go 
b/pkg/plugin/predicates/predicate_manager.go
index 5e966df6..cff56cd6 100644
--- a/pkg/plugin/predicates/predicate_manager.go
+++ b/pkg/plugin/predicates/predicate_manager.go
@@ -27,9 +27,11 @@ import (
        "go.uber.org/zap"
        v1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apiserver/pkg/util/feature"
        "k8s.io/component-base/config/v1alpha1"
        "k8s.io/klog/v2"
        schedConfig "k8s.io/kube-scheduler/config/v1"
+       "k8s.io/kubernetes/pkg/features"
        apiConfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
        "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme"
        "k8s.io/kubernetes/pkg/scheduler/framework"
@@ -272,6 +274,23 @@ func (p *predicateManagerImpl) runFilterPlugin(ctx 
context.Context, pl framework
        return pl.Filter(ctx, state, pod, nodeInfo)
 }
 
+// EnableOptionalKubernetesFeatureGates ensures that any optional Kubernetes 
feature gates that YuniKorn supports are
+// enabled. Currently, as of Kubernetes 1.32, this includes PodLevelResources 
and InPlacePodVerticalScaling. These are
+// both safe to enable as part of our default configuration, as they also 
require the appropriate feature gates to be
+// enabled at the API server to be functional.
+// This needs to be called before any Kubernetes-specific code is initialized 
(ideally top of main()) and in any unit
+// tests which require this functionality.
+func EnableOptionalKubernetesFeatureGates() {
+       log.Log(log.ShimPredicates).Debug("Enabling PodLevelResources feature 
gate")
+       if err := feature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", 
features.PodLevelResources)); err != nil {
+               log.Log(log.ShimPredicates).Fatal("Unable to set 
PodLevelResources feature gate", zap.Error(err))
+       }
+       log.Log(log.ShimPredicates).Debug("Enabling InPlacePodVerticalScaling 
feature gate")
+       if err := feature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", 
features.InPlacePodVerticalScaling)); err != nil {
+               log.Log(log.ShimPredicates).Fatal("Unable to set 
PodLevelResources feature gate", zap.Error(err))
+       }
+}
+
 func NewPredicateManager(handle framework.Handle) PredicateManager {
        /*
                Default K8S plugins as of 1.32 that implement PreFilter:
diff --git a/pkg/plugin/predicates/predicate_manager_test.go 
b/pkg/plugin/predicates/predicate_manager_test.go
index b1104cff..7f2d735c 100644
--- a/pkg/plugin/predicates/predicate_manager_test.go
+++ b/pkg/plugin/predicates/predicate_manager_test.go
@@ -29,10 +29,12 @@ import (
        v1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/resource"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apiserver/pkg/util/feature"
        "k8s.io/client-go/informers"
        "k8s.io/client-go/kubernetes"
        "k8s.io/klog/v2"
        v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
+       "k8s.io/kubernetes/pkg/features"
        "k8s.io/kubernetes/pkg/scheduler/framework"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
@@ -1107,6 +1109,12 @@ func newPodWithPort(hostPorts ...int) *v1.Pod {
        }
 }
 
+func TestEnableOptionalKubernetesFeatureGates(t *testing.T) {
+       EnableOptionalKubernetesFeatureGates()
+       assert.Assert(t, 
feature.DefaultFeatureGate.Enabled(features.PodLevelResources), "pod level 
resources not enabled")
+       assert.Assert(t, 
feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling), 
"in-place pod vertical scaling not enabled")
+}
+
 func TestRunGeneralPredicates(t *testing.T) {
        clientSet := clientSet()
        informerFactory := informerFactory(clientSet)
diff --git a/scripts/kind-pod-resize.yaml b/scripts/kind-1.32.yaml
similarity index 97%
rename from scripts/kind-pod-resize.yaml
rename to scripts/kind-1.32.yaml
index ae514fab..581885da 100644
--- a/scripts/kind-pod-resize.yaml
+++ b/scripts/kind-1.32.yaml
@@ -19,6 +19,7 @@ kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
 featureGates:
   "InPlacePodVerticalScaling": true
+  "PodLevelResources": true
 nodes:
   - role: control-plane
   - role: worker
diff --git a/scripts/run-e2e-tests.sh b/scripts/run-e2e-tests.sh
index 2c82b4c5..0042158c 100755
--- a/scripts/run-e2e-tests.sh
+++ b/scripts/run-e2e-tests.sh
@@ -47,12 +47,12 @@ function verlt() {
 function update_kind_config() {
   # use a different kind config for different cluster versions
   version=$(echo "$1" | sed 's/.*://' | sed 's/^v//')
-  if verlt "${version}" "1.27"; then
-    # 1.26 or earlier
+  if verlt "${version}" "1.32"; then
+    # 1.31 or earlier
     KIND_CONFIG=./scripts/kind.yaml
   else
-    # 1.27 or later; enable InPlacePodVerticalScaling feature flag
-    KIND_CONFIG=./scripts/kind-pod-resize.yaml
+    # 1.32 or later; enable InPlacePodVerticalScaling and PodLevelResources 
feature flags
+    KIND_CONFIG=./scripts/kind-1.32.yaml
   fi
 }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to