autoscaler/vertical-pod-autoscaler/e2e/v1/admission_controller.go

1143 lines
49 KiB
Go

/*
Copyright 2019 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 autoscaling
import (
"context"
"fmt"
"io"
"strings"
"time"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
"k8s.io/kubernetes/test/e2e/framework"
framework_deployment "k8s.io/kubernetes/test/e2e/framework/deployment"
podsecurity "k8s.io/pod-security-admission/api"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)
const (
webhookConfigName = "vpa-webhook-config"
webhookName = "vpa.k8s.io"
)
var _ = AdmissionControllerE2eDescribe("Admission-controller", ginkgo.Label("FG:InPlaceOrRecreate"), func() {
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
ginkgo.BeforeEach(func() {
waitForVpaWebhookRegistration(f)
})
ginkgo.It("starts pods with new recommended request with InPlaceOrRecreate mode", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
}
})
ginkgo.It("starts pods with new recommended request", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
}
})
ginkgo.It("starts pods with new recommended request when recommendation includes an extra container", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
removedContainerName := "removed"
container1Name := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(removedContainerName).
AppendRecommendation(
test.Recommendation().
WithContainer(removedContainerName).
WithTarget("500m", "500Mi").
WithLowerBound("500m", "500Mi").
WithUpperBound("500m", "500Mi").
GetContainerResources()).
WithContainer(container1Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container1Name).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
}
})
ginkgo.It("starts pods with old recommended request when recommendation has only a container that doesn't match", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
removedContainerName := "removed"
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(removedContainerName).
AppendRecommendation(
test.Recommendation().
WithContainer(removedContainerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("100m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("100Mi")))
}
})
ginkgo.It("starts pod with recommendation when one container has a recommendation and one other one doesn't", func() {
d := NewNHamstersDeployment(f, 2)
d.Spec.Template.Spec.Containers[0].Resources.Requests = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("100m"),
apiv1.ResourceMemory: ParseQuantityOrDie("100Mi"),
}
d.Spec.Template.Spec.Containers[1].Resources.Requests = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("100m"),
apiv1.ResourceMemory: ParseQuantityOrDie("100Mi"),
}
framework.Logf("Created hamster deployment %v", d)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
gomega.Expect(pod.Spec.Containers[1].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("100m")))
gomega.Expect(pod.Spec.Containers[1].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("100Mi")))
}
})
ginkgo.It("starts pods with default request when recommendation includes an extra container when a limit range applies", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
InstallLimitRangeWithMax(f, "300m", "1Gi", apiv1.LimitTypeContainer)
ginkgo.By("Setting up a VPA CRD")
container1Name := GetHamsterContainerNameByIndex(0)
container2Name := GetHamsterContainerNameByIndex(1)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(container1Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container1Name).
WithTarget("500m", "500Mi").
WithLowerBound("500m", "500Mi").
WithUpperBound("500m", "500Mi").
GetContainerResources()).
WithContainer(container2Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container2Name).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
// This is a bug; VPA should behave here like it does without a limit range
// Like in "starts pods with new recommended request when recommendation includes an extra container"
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("100m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("100Mi")))
}
})
ginkgo.It("starts pods with old recommended request when recommendation has only a container that doesn't match when a limit range applies", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
InstallLimitRangeWithMax(f, "300m", "1Gi", apiv1.LimitTypeContainer)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 250m CPU and 200Mi of memory.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("100m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("100Mi")))
}
})
ginkgo.It("starts pod with default request when one container has a recommendation and one other one doesn't when a limit range applies", func() {
d := NewNHamstersDeployment(f, 2)
InstallLimitRangeWithMax(f, "400m", "1Gi", apiv1.LimitTypePod)
d.Spec.Template.Spec.Containers[0].Resources.Requests = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("100m"),
apiv1.ResourceMemory: ParseQuantityOrDie("100Mi"),
}
d.Spec.Template.Spec.Containers[0].Resources.Limits = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("100m"),
apiv1.ResourceMemory: ParseQuantityOrDie("100Mi"),
}
d.Spec.Template.Spec.Containers[1].Resources.Requests = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("400m"),
apiv1.ResourceMemory: ParseQuantityOrDie("600Mi"),
}
d.Spec.Template.Spec.Containers[1].Resources.Limits = apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("400m"),
apiv1.ResourceMemory: ParseQuantityOrDie("600Mi"),
}
framework.Logf("Created hamster deployment %v", d)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("400m", "600Mi").
WithLowerBound("400m", "600Mi").
WithUpperBound("400m", "600Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally both containers in each Pod had 400m CPU (one from
// recommendation the other one from request), 600Mi of memory (similarly),
// but admission controller should change it to recommended 200m CPU
// (1/2 of max in limit range) and 512Mi of memory (similarly).
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("200m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("512Mi")))
gomega.Expect(pod.Spec.Containers[1].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("200m")))
gomega.Expect(pod.Spec.Containers[1].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("512Mi")))
}
})
ginkgo.It("doesn't block patches", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
ginkgo.By("Verifying hamster deployment")
for i, pod := range podList.Items {
podInfo := fmt.Sprintf("pod at index %d", i)
cpuDescription := fmt.Sprintf("%s: originally Pods had 100m CPU, admission controller should change it to recommended 250m CPU", podInfo)
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")), cpuDescription)
memDescription := fmt.Sprintf("%s: originally Pods had 100Mi of memory, admission controller should change it to recommended 200Mi memory", podInfo)
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")), memDescription)
}
ginkgo.By("Modifying recommendation.")
PatchVpaRecommendation(f, vpaCRD, &vpa_types.RecommendedPodResources{
ContainerRecommendations: []vpa_types.RecommendedContainerResources{{
ContainerName: "hamster",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: ParseQuantityOrDie("100m"),
apiv1.ResourceMemory: ParseQuantityOrDie("100Mi"),
},
}},
})
podName := podList.Items[0].Name
ginkgo.By(fmt.Sprintf("Modifying pod %v.", podName))
AnnotatePod(f, podName, "someAnnotation", "someValue")
})
ginkgo.It("keeps limits equal to request", func() {
d := NewHamsterDeploymentWithGuaranteedResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to 250m CPU and 200Mi of memory. Limits and requests should stay equal.
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
}
})
ginkgo.It("keeps limits to request ratio constant", func() {
d := NewHamsterDeploymentWithResourcesAndLimits(f,
ParseQuantityOrDie("100m") /*cpu request*/, ParseQuantityOrDie("100Mi"), /*memory request*/
ParseQuantityOrDie("150m") /*cpu limit*/, ParseQuantityOrDie("200Mi") /*memory limit*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to 250m CPU and 200Mi of memory. Limits to request ratio should stay unchanged.
for _, pod := range podList.Items {
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Memory()).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()) / float64(pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue())).To(gomega.BeNumerically("~", 1.5))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Memory().Value()) / float64(pod.Spec.Containers[0].Resources.Requests.Memory().Value())).To(gomega.BeNumerically("~", 2.))
}
})
ginkgo.It("keeps limits unchanged when container controlled values is requests only", func() {
d := NewHamsterDeploymentWithResourcesAndLimits(f,
ParseQuantityOrDie("100m") /*cpu request*/, ParseQuantityOrDie("100Mi"), /*memory request*/
ParseQuantityOrDie("500m") /*cpu limit*/, ParseQuantityOrDie("500Mi") /*memory limit*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithControlledValues(containerName, vpa_types.ContainerControlledValuesRequestsOnly).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to 250m CPU and 200Mi of memory. Limits should stay unchanged.
for _, pod := range podList.Items {
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Memory()).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Limits.Cpu()).To(gomega.Equal(ParseQuantityOrDie("500m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Limits.Memory()).To(gomega.Equal(ParseQuantityOrDie("500Mi")))
}
})
ginkgo.It("caps request according to container max limit set in LimitRange", func() {
startCpuRequest := ParseQuantityOrDie("100m")
startCpuLimit := ParseQuantityOrDie("150m")
startMemRequest := ParseQuantityOrDie("100Mi")
startMemLimit := ParseQuantityOrDie("200Mi")
memRecommendation := ParseQuantityOrDie("200Mi")
d := NewHamsterDeploymentWithResourcesAndLimits(f, startCpuRequest, startMemRequest, startCpuLimit, startMemLimit)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
// Max CPU limit is 300m and ratio is 1.5, so max request is 200m, while
// recommendation is 250m
// Max memory limit is 1Gi and ratio is 2., so max request is 0.5Gi
maxCpu := ParseQuantityOrDie("300m")
InstallLimitRangeWithMax(f, maxCpu.String(), "1Gi", apiv1.LimitTypeContainer)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
ginkgo.By("Verifying hamster deployment")
for i, pod := range podList.Items {
podInfo := fmt.Sprintf("pod %s at index %d", pod.Name, i)
cpuRequestMsg := fmt.Sprintf("%s: CPU request didn't increase to the recommendation capped to max limit in LimitRange", podInfo)
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("200m")), cpuRequestMsg)
cpuLimitMsg := fmt.Sprintf("%s: CPU limit above max in LimitRange", podInfo)
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()).To(gomega.BeNumerically("<=", maxCpu.MilliValue()), cpuLimitMsg)
cpuRatioMsg := fmt.Sprintf("%s: CPU limit / request ratio isn't approximately equal to the original ratio", podInfo)
cpuRatio := float64(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()) / float64(pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue())
gomega.Expect(cpuRatio).To(gomega.BeNumerically("~", 1.5), cpuRatioMsg)
memRequestMsg := fmt.Sprintf("%s: memory request didn't increase to the recommendation capped to max limit in LimitRange", podInfo)
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Memory().Value()).To(gomega.Equal(memRecommendation.Value()), memRequestMsg)
memLimitMsg := fmt.Sprintf("%s: memory limit above max limit in LimitRange", podInfo)
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Memory().Value()).To(gomega.BeNumerically("<=", 1024*1024*1024), memLimitMsg)
memRatioMsg := fmt.Sprintf("%s: memory limit / request ratio isn't approximately equal to the original ratio", podInfo)
memRatio := float64(pod.Spec.Containers[0].Resources.Limits.Memory().Value()) / float64(pod.Spec.Containers[0].Resources.Requests.Memory().Value())
gomega.Expect(memRatio).To(gomega.BeNumerically("~", 2.), memRatioMsg)
}
})
ginkgo.It("raises request according to container min limit set in LimitRange", func() {
d := NewHamsterDeploymentWithResourcesAndLimits(f,
ParseQuantityOrDie("100m") /*cpu request*/, ParseQuantityOrDie("200Mi"), /*memory request*/
ParseQuantityOrDie("150m") /*cpu limit*/, ParseQuantityOrDie("400Mi") /*memory limit*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "100Mi").
WithLowerBound("250m", "100Mi").
WithUpperBound("250m", "100Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
// Min CPU from limit range is 50m and ratio is 1.5. Min applies to both limit and request so min
// request is 50m and min limit is 75
// Min memory limit is 250Mi and it applies to both limit and request. Recommendation is 100Mi.
// It should be scaled up to 250Mi.
InstallLimitRangeWithMin(f, "50m", "250Mi", apiv1.LimitTypeContainer)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 200Mi of memory, but admission controller
// should change it to 250m CPU and 125Mi of memory, since this is the lowest
// request that limitrange allows.
// Limit to request ratio should stay unchanged.
for _, pod := range podList.Items {
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("250m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Memory()).To(gomega.Equal(ParseQuantityOrDie("250Mi")))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()).To(gomega.BeNumerically(">=", 75))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Memory().Value()).To(gomega.BeNumerically(">=", 250*1024*1024))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()) / float64(pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue())).To(gomega.BeNumerically("~", 1.5))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Memory().Value()) / float64(pod.Spec.Containers[0].Resources.Requests.Memory().Value())).To(gomega.BeNumerically("~", 2.))
}
})
ginkgo.It("caps request according to pod max limit set in LimitRange", func() {
d := NewHamsterDeploymentWithResourcesAndLimits(f,
ParseQuantityOrDie("100m") /*cpu request*/, ParseQuantityOrDie("100Mi"), /*memory request*/
ParseQuantityOrDie("150m") /*cpu limit*/, ParseQuantityOrDie("200Mi") /*memory limit*/)
d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, d.Spec.Template.Spec.Containers[0])
container2Name := "hamster2"
d.Spec.Template.Spec.Containers[1].Name = container2Name
ginkgo.By("Setting up a VPA CRD")
container1Name := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(container1Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container1Name).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
WithContainer(container2Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container2Name).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
// Max CPU limit is 600m for pod, 300 per container and ratio is 1.5, so max request is 200m,
// while recommendation is 250m
// Max memory limit is 1Gi and ratio is 2., so max request is 0.5Gi
InstallLimitRangeWithMax(f, "600m", "1Gi", apiv1.LimitTypePod)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to 200m CPU (as this is the recommendation
// capped according to max limit in LimitRange) and 200Mi of memory,
// which is uncapped. Limit to request ratio should stay unchanged.
for _, pod := range podList.Items {
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("200m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Memory()).To(gomega.Equal(ParseQuantityOrDie("200Mi")))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()).To(gomega.BeNumerically("<=", 300))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Memory().Value()).To(gomega.BeNumerically("<=", 1024*1024*1024))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()) / float64(pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue())).To(gomega.BeNumerically("~", 1.5))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Memory().Value()) / float64(pod.Spec.Containers[0].Resources.Requests.Memory().Value())).To(gomega.BeNumerically("~", 2.))
}
})
ginkgo.It("raises request according to pod min limit set in LimitRange", func() {
d := NewHamsterDeploymentWithResourcesAndLimits(f,
ParseQuantityOrDie("100m") /*cpu request*/, ParseQuantityOrDie("200Mi"), /*memory request*/
ParseQuantityOrDie("150m") /*cpu limit*/, ParseQuantityOrDie("400Mi") /*memory limit*/)
d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, d.Spec.Template.Spec.Containers[0])
container2Name := "hamster2"
d.Spec.Template.Spec.Containers[1].Name = container2Name
ginkgo.By("Setting up a VPA CRD")
container1Name := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(container1Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container1Name).
WithTarget("120m", "100Mi").
WithLowerBound("120m", "100Mi").
WithUpperBound("120m", "100Mi").
GetContainerResources()).
WithContainer(container2Name).
AppendRecommendation(
test.Recommendation().
WithContainer(container2Name).
WithTarget("120m", "100Mi").
WithLowerBound("120m", "100Mi").
WithUpperBound("120m", "100Mi").
GetContainerResources()).
Get()
InstallVPA(f, vpaCRD)
// Min CPU from limit range is 100m, 50m per pod and ratio is 1.5. Min applies to both limit and
// request so min request is 50m and min limit is 75
// Min memory limit is 500Mi per pod, 250 per container and it applies to both limit and request.
// Recommendation is 100Mi it should be scaled up to 250Mi.
InstallLimitRangeWithMin(f, "100m", "500Mi", apiv1.LimitTypePod)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 200Mi of memory, but admission controller
// should change it to 250m CPU and 125Mi of memory, since this is the lowest
// request that limitrange allows.
// Limit to request ratio should stay unchanged.
for _, pod := range podList.Items {
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Cpu()).To(gomega.Equal(ParseQuantityOrDie("120m")))
gomega.Expect(*pod.Spec.Containers[0].Resources.Requests.Memory()).To(gomega.Equal(ParseQuantityOrDie("250Mi")))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()).To(gomega.BeNumerically(">=", 75))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Memory().Value()).To(gomega.BeNumerically(">=", 250*1024*1024))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()) / float64(pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue())).To(gomega.BeNumerically("~", 1.5))
gomega.Expect(float64(pod.Spec.Containers[0].Resources.Limits.Memory().Value()) / float64(pod.Spec.Containers[0].Resources.Requests.Memory().Value())).To(gomega.BeNumerically("~", 2.))
}
})
ginkgo.It("caps request to max set in VPA", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("250m", "200Mi").
WithLowerBound("250m", "200Mi").
WithUpperBound("250m", "200Mi").
GetContainerResources()).
WithMaxAllowed(containerName, "233m", "150Mi").
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to 233m CPU and 150Mi of memory (as this is the recommendation
// capped to max specified in VPA)
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("233m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("150Mi")))
}
})
ginkgo.It("raises request to min set in VPA", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("50m", "60Mi").
WithLowerBound("50m", "60Mi").
WithUpperBound("50m", "60Mi").
GetContainerResources()).
WithMinAllowed(containerName, "90m", "80Mi").
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// Originally Pods had 100m CPU, 100Mi of memory, but admission controller
// should change it to recommended 90m CPU and 800Mi of memory (as this the
// recommendation raised to min specified in VPA)
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("90m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("80Mi")))
}
})
ginkgo.It("leaves users request when no recommendation", func() {
d := NewHamsterDeploymentWithResources(f, ParseQuantityOrDie("100m") /*cpu*/, ParseQuantityOrDie("100Mi") /*memory*/)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// VPA has no recommendation, so user's request is passed through
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceCPU]).To(gomega.Equal(ParseQuantityOrDie("100m")))
gomega.Expect(pod.Spec.Containers[0].Resources.Requests[apiv1.ResourceMemory]).To(gomega.Equal(ParseQuantityOrDie("100Mi")))
}
})
ginkgo.It("passes empty request when no recommendation and no user-specified request", func() {
d := NewHamsterDeployment(f)
ginkgo.By("Setting up a VPA CRD")
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
Get()
InstallVPA(f, vpaCRD)
ginkgo.By("Setting up a hamster deployment")
podList := startDeploymentPods(f, d)
// VPA has no recommendation, deployment has no request specified
for _, pod := range podList.Items {
gomega.Expect(pod.Spec.Containers[0].Resources.Requests).To(gomega.BeEmpty())
}
})
ginkgo.It("accepts valid and rejects invalid VPA object", func() {
ginkgo.By("Setting up valid VPA object")
validVPA := []byte(`{
"kind": "VerticalPodAutoscaler",
"apiVersion": "autoscaling.k8s.io/v1",
"metadata": {"name": "hamster-vpa-valid"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name":"hamster"
},
"resourcePolicy": {
"containerPolicies": [{"containerName": "*", "minAllowed":{"cpu":"50m"}}]
}
}
}`)
err := InstallRawVPA(f, validVPA)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Valid VPA object rejected")
ginkgo.By("Setting up invalid VPA objects")
testCases := []struct {
name string
vpaJSON string
expectedErr string
}{
{
name: "Invalid oomBumpUpRatio (negative value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": -1,
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio: Invalid value: -1: spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio in body should be greater than or equal to 1",
},
{
name: "Invalid oomBumpUpRatio (string value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": "12",
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "json: cannot unmarshal string into Go struct field ContainerResourcePolicy.spec.resourcePolicy.containerPolicies.oomBumpUpRatio of type float64",
},
{
name: "Invalid oomBumpUpRatio (less than 1)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": 0.5,
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio: Invalid value: 0.5: spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio in body should be greater than or equal to 1",
},
{
name: "Invalid oomMinBumpUp (negative value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": 2,
"oomMinBumpUp": -1
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomMinBumpUp: Invalid value: -1: spec.resourcePolicy.containerPolicies[0].oomMinBumpUp in body should be greater than or equal to 0",
},
{
name: "Invalid minAllowed (invalid requests field)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "hamster-vpa-invalid"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "hamster"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"minAllowed": {
"requests": {
"cpu": "50m"
}
}
}]
}
}
}`,
expectedErr: "admission webhook .*vpa.* denied the request:",
},
}
for _, tc := range testCases {
ginkgo.By(fmt.Sprintf("Testing %s", tc.name))
err := InstallRawVPA(f, []byte(tc.vpaJSON))
gomega.Expect(err).To(gomega.HaveOccurred(), "Invalid VPA object accepted")
gomega.Expect(err.Error()).To(gomega.MatchRegexp(tc.expectedErr))
}
})
ginkgo.It("reloads the webhook leaf and CA certificate", func(ctx ginkgo.SpecContext) {
ginkgo.By("Retrieving alternative certificates")
c := f.ClientSet
e2eCertsSecret, err := c.CoreV1().Secrets(metav1.NamespaceSystem).Get(ctx, "vpa-e2e-certs", metav1.GetOptions{})
gomega.Expect(err).To(gomega.Succeed(), "Failed to get vpa-e2e-certs secret")
actualCertsSecret, err := c.CoreV1().Secrets(metav1.NamespaceSystem).Get(ctx, "vpa-tls-certs", metav1.GetOptions{})
gomega.Expect(err).To(gomega.Succeed(), "Failed to get vpa-tls-certs secret")
actualCertsSecret.Data["serverKey.pem"] = e2eCertsSecret.Data["e2eKey.pem"]
actualCertsSecret.Data["serverCert.pem"] = e2eCertsSecret.Data["e2eCert.pem"]
actualCertsSecret.Data["caCert.pem"] = e2eCertsSecret.Data["e2eCaCert.pem"]
_, err = c.CoreV1().Secrets(metav1.NamespaceSystem).Update(ctx, actualCertsSecret, metav1.UpdateOptions{})
gomega.Expect(err).To(gomega.Succeed(), "Failed to update vpa-tls-certs secret with e2e rotation certs")
ginkgo.By("Waiting for certificate reloads")
pods, err := c.CoreV1().Pods(metav1.NamespaceSystem).List(ctx, metav1.ListOptions{})
gomega.Expect(err).To(gomega.Succeed())
var admissionController apiv1.Pod
for _, p := range pods.Items {
if strings.HasPrefix(p.Name, "vpa-admission-controller") {
admissionController = p
}
}
gomega.Expect(admissionController.Name).ToNot(gomega.BeEmpty())
gomega.Eventually(func(g gomega.Gomega) string {
reader, err := c.CoreV1().Pods(metav1.NamespaceSystem).GetLogs(admissionController.Name, &apiv1.PodLogOptions{}).Stream(ctx)
g.Expect(err).To(gomega.Succeed())
logs, err := io.ReadAll(reader)
g.Expect(err).To(gomega.Succeed())
return string(logs)
}).Should(gomega.And(gomega.ContainSubstring("New certificate found, reloading"), gomega.ContainSubstring("New client CA found, reloading and patching webhook"), gomega.ContainSubstring("Successfully patched webhook with new client CA")))
ginkgo.By("Setting up invalid VPA object")
// there is an invalid "requests" field.
invalidVPA := []byte(`{
"kind": "VerticalPodAutoscaler",
"apiVersion": "autoscaling.k8s.io/v1",
"metadata": {"name": "cert-vpa-invalid"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name":"hamster"
},
"resourcePolicy": {
"containerPolicies": [{"containerName": "*", "minAllowed":{"requests":{"cpu":"50m"}}}]
}
}
}`)
err = InstallRawVPA(f, invalidVPA)
gomega.Expect(err).To(gomega.HaveOccurred(), "Invalid VPA object accepted")
gomega.Expect(err.Error()).To(gomega.MatchRegexp(`.*admission webhook .*vpa.* denied the request: .*`), "Admission controller did not inspect the object")
})
})
func startDeploymentPods(f *framework.Framework, deployment *appsv1.Deployment) *apiv1.PodList {
// Apiserver watch can lag depending on cached object count and apiserver resource usage.
// We assume that watch can lag up to 5 seconds.
const apiserverWatchLag = 5 * time.Second
// In admission controller e2e tests a recommendation is created before deployment.
// Creating deployment with size greater than 0 would create a race between information
// about pods and information about deployment getting to the admission controller.
// Any pods that get processed by AC before it receives information about the deployment
// don't receive recommendation.
// To avoid this create deployment with size 0, then scale it up to the desired size.
desiredPodCount := *deployment.Spec.Replicas
zero := int32(0)
deployment.Spec.Replicas = &zero
c, ns := f.ClientSet, f.Namespace.Name
deployment, err := c.AppsV1().Deployments(ns).Create(context.TODO(), deployment, metav1.CreateOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "when creating deployment with size 0")
err = framework_deployment.WaitForDeploymentComplete(c, deployment)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "when waiting for empty deployment to create")
// If admission controller receives pod before controller it will not apply recommendation and test will fail.
// Wait after creating deployment to ensure VPA knows about it, then scale up.
// Normally watch lag is not a problem in terms of correctness:
// - Mode "Auto": created pod without assigned resources will be handled by the eviction loop.
// - Mode "Initial": calculating recommendations takes more than potential ectd lag.
// - Mode "Off": pods are not handled by the admission controller.
// In e2e admission controller tests we want to focus on scenarios without considering watch lag.
// TODO(#2631): Remove sleep when issue is fixed.
time.Sleep(apiserverWatchLag)
scale := autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: deployment.ObjectMeta.Name,
Namespace: deployment.ObjectMeta.Namespace,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: desiredPodCount,
},
}
afterScale, err := c.AppsV1().Deployments(ns).UpdateScale(context.TODO(), deployment.Name, &scale, metav1.UpdateOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(afterScale.Spec.Replicas).To(gomega.Equal(desiredPodCount), fmt.Sprintf("expected %d replicas after scaling", desiredPodCount))
// After scaling deployment we need to retrieve current version with updated replicas count.
deployment, err = c.AppsV1().Deployments(ns).Get(context.TODO(), deployment.Name, metav1.GetOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "when getting scaled deployment")
err = framework_deployment.WaitForDeploymentComplete(c, deployment)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "when waiting for deployment to resize")
podList, err := framework_deployment.GetPodsForDeployment(context.TODO(), c, deployment)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "when listing pods after deployment resize")
return podList
}
func waitForVpaWebhookRegistration(f *framework.Framework) {
ginkgo.By("Waiting for VPA webhook registration")
gomega.Eventually(func() bool {
webhook, err := f.ClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.TODO(), webhookConfigName, metav1.GetOptions{})
if err != nil {
return false
}
if webhook != nil && len(webhook.Webhooks) > 0 && webhook.Webhooks[0].Name == webhookName {
return true
}
return false
}, 3*time.Minute, 5*time.Second).Should(gomega.BeTrue(), "Webhook was not registered in the cluster")
}