@@ -14,6 +14,7 @@ import (
1414 "github.com/google/go-cmp/cmp"
1515 "github.com/stretchr/testify/assert"
1616 corev1 "k8s.io/api/core/v1"
17+ "k8s.io/apimachinery/pkg/api/resource"
1718 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819
1920 "github.com/newrelic/nri-kubernetes/v3/internal/config"
@@ -463,3 +464,92 @@ func TestFetchPodData_WithPriorityClassNameOnly(t *testing.T) {
463464 assert .False (t , hasPriority , "priority should not be present when nil" )
464465 assert .Equal (t , "system-cluster-critical" , result ["priorityClassName" ])
465466}
467+
468+ // TestFetchContainersData_InPlaceVerticalScaling verifies that when
469+ // ContainerStatus.Resources is populated (K8s 1.33+ in-place pod vertical scaling),
470+ // the actual applied resources are used instead of Spec resources (desired state).
471+ func TestFetchContainersData_InPlaceVerticalScaling (t * testing.T ) {
472+ t .Parallel ()
473+
474+ specCPU := resource .MustParse ("100m" )
475+ specMemory := resource .MustParse ("128Mi" )
476+ statusCPU := resource .MustParse ("200m" ) // resized up
477+ statusMemory := resource .MustParse ("256Mi" )
478+
479+ pod := & corev1.Pod {
480+ ObjectMeta : metav1.ObjectMeta {
481+ Name : "test-pod" ,
482+ Namespace : "default" ,
483+ },
484+ Spec : corev1.PodSpec {
485+ NodeName : "test-node" ,
486+ Containers : []corev1.Container {
487+ {
488+ Name : "app" ,
489+ Image : "app:latest" ,
490+ Resources : corev1.ResourceRequirements {
491+ Requests : corev1.ResourceList {
492+ corev1 .ResourceCPU : specCPU ,
493+ corev1 .ResourceMemory : specMemory ,
494+ },
495+ Limits : corev1.ResourceList {
496+ corev1 .ResourceCPU : specCPU ,
497+ corev1 .ResourceMemory : specMemory ,
498+ },
499+ },
500+ },
501+ {
502+ Name : "sidecar" ,
503+ Image : "sidecar:latest" ,
504+ Resources : corev1.ResourceRequirements {
505+ Requests : corev1.ResourceList {
506+ corev1 .ResourceCPU : specCPU ,
507+ corev1 .ResourceMemory : specMemory ,
508+ },
509+ },
510+ },
511+ },
512+ },
513+ Status : corev1.PodStatus {
514+ HostIP : "192.168.0.1" ,
515+ ContainerStatuses : []corev1.ContainerStatus {
516+ {
517+ Name : "app" ,
518+ // Resources populated: actual applied state after resize
519+ Resources : & corev1.ResourceRequirements {
520+ Requests : corev1.ResourceList {
521+ corev1 .ResourceCPU : statusCPU ,
522+ corev1 .ResourceMemory : statusMemory ,
523+ },
524+ Limits : corev1.ResourceList {
525+ corev1 .ResourceCPU : statusCPU ,
526+ corev1 .ResourceMemory : statusMemory ,
527+ },
528+ },
529+ },
530+ {
531+ Name : "sidecar" ,
532+ Resources : nil , // no status resources; fall back to spec
533+ },
534+ },
535+ },
536+ }
537+
538+ podFetcher := & PodsFetcher {}
539+ result := podFetcher .fetchContainersData (pod )
540+
541+ appID := "default_test-pod_app"
542+ sidecarID := "default_test-pod_sidecar"
543+
544+ // "app" container: status resources should take precedence
545+ assert .Equal (t , statusCPU .MilliValue (), result [appID ]["cpuRequestedCores" ], "app: should use status CPU request" )
546+ assert .Equal (t , statusCPU .MilliValue (), result [appID ]["cpuLimitCores" ], "app: should use status CPU limit" )
547+ assert .Equal (t , statusMemory .Value (), result [appID ]["memoryRequestedBytes" ], "app: should use status memory request" )
548+ assert .Equal (t , statusMemory .Value (), result [appID ]["memoryLimitBytes" ], "app: should use status memory limit" )
549+
550+ // "sidecar" container: status resources nil, should fall back to spec
551+ assert .Equal (t , specCPU .MilliValue (), result [sidecarID ]["cpuRequestedCores" ], "sidecar: should fall back to spec CPU request" )
552+ assert .Equal (t , specMemory .Value (), result [sidecarID ]["memoryRequestedBytes" ], "sidecar: should fall back to spec memory request" )
553+ _ , hasLimit := result [sidecarID ]["cpuLimitCores" ]
554+ assert .False (t , hasLimit , "sidecar: no CPU limit in spec, should not be set" )
555+ }
0 commit comments