diff --git a/pkg/quota/v1/install/update_filter.go b/pkg/quota/v1/install/update_filter.go index eca16ec8fc9..507ff05ad87 100644 --- a/pkg/quota/v1/install/update_filter.go +++ b/pkg/quota/v1/install/update_filter.go @@ -18,7 +18,11 @@ package install import ( v1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/util/feature" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/quota/v1/evaluator/core" "k8s.io/utils/clock" ) @@ -30,6 +34,10 @@ func DefaultUpdateFilter() func(resource schema.GroupVersionResource, oldObj, ne case schema.GroupResource{Resource: "pods"}: oldPod := oldObj.(*v1.Pod) newPod := newObj.(*v1.Pod) + // when Resources changed + if feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) && hasResourcesChanged(oldPod, newPod) { + return true + } return core.QuotaV1Pod(oldPod, clock.RealClock{}) && !core.QuotaV1Pod(newPod, clock.RealClock{}) case schema.GroupResource{Resource: "services"}: oldService := oldObj.(*v1.Service) @@ -44,3 +52,20 @@ func DefaultUpdateFilter() func(resource schema.GroupVersionResource, oldObj, ne return false } } + +// hasResourcesChanged function to compare resources in container statuses +func hasResourcesChanged(oldPod *v1.Pod, newPod *v1.Pod) bool { + for _, oldStatus := range oldPod.Status.ContainerStatuses { + newStatus, ok := podutil.GetContainerStatus(newPod.Status.ContainerStatuses, oldStatus.Name) + if ok && !apiequality.Semantic.DeepEqual(oldStatus.Resources, newStatus.Resources) { + return true + } + } + for _, oldInitContainerStatus := range oldPod.Status.InitContainerStatuses { + newInitContainerStatus, ok := podutil.GetContainerStatus(newPod.Status.InitContainerStatuses, oldInitContainerStatus.Name) + if ok && !apiequality.Semantic.DeepEqual(oldInitContainerStatus.Resources, newInitContainerStatus.Resources) { + return true + } + } + return false +} diff --git a/pkg/quota/v1/install/update_filter_test.go b/pkg/quota/v1/install/update_filter_test.go new file mode 100644 index 00000000000..2eee3b29862 --- /dev/null +++ b/pkg/quota/v1/install/update_filter_test.go @@ -0,0 +1,141 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed 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 install + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + containerFoo = v1.Container{ + + Name: "foo", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + } + containerFooInitialStatus = v1.ContainerStatus{ + Name: "foo", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + } + containerFooChangedStatus = v1.ContainerStatus{ + Name: "foo", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + } +) + +func TestHasResourcesChanged(t *testing.T) { + + oldNotChangedPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + containerFoo, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + containerFooInitialStatus, + }, + }, + } + newNotChangedPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + containerFoo, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + containerFooInitialStatus, + }, + }, + } + + oldChangedPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + containerFoo, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + containerFooInitialStatus, + }, + }, + } + newChangedPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + containerFoo, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + containerFooChangedStatus, + }, + }, + } + + tests := []struct { + name string + oldPod *v1.Pod + newPod *v1.Pod + expected bool + }{ + { + name: "not-changed-pod", + oldPod: oldNotChangedPod, + newPod: newNotChangedPod, + expected: false, + }, + { + name: "changed-pod", + oldPod: oldChangedPod, + newPod: newChangedPod, + expected: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := hasResourcesChanged(test.oldPod, test.newPod); got != test.expected { + t.Errorf("TestHasResourcesChanged = %v, expected %v", got, test.expected) + } + }) + } +}