diff --git a/vertical-pod-autoscaler/pkg/admission-controller/config_test.go b/vertical-pod-autoscaler/pkg/admission-controller/config_test.go index 032987657a18..d19c382152f8 100644 --- a/vertical-pod-autoscaler/pkg/admission-controller/config_test.go +++ b/vertical-pod-autoscaler/pkg/admission-controller/config_test.go @@ -35,8 +35,10 @@ func TestSelfRegistrationBase(t *testing.T) { url := "http://example.com/" registerByURL := true timeoutSeconds := int32(32) + selectedNamespaces := []string{} + ignoredNamespaces := []string{} - selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds) + selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds, selectedNamespaces, ignoredNamespaces) webhookConfigInterface := testClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations() webhookConfig, err := webhookConfigInterface.Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) @@ -75,8 +77,10 @@ func TestSelfRegistrationWithURL(t *testing.T) { url := "http://example.com/" registerByURL := true timeoutSeconds := int32(32) + selectedNamespaces := []string{} + ignoredNamespaces := []string{} - selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds) + selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds, selectedNamespaces, ignoredNamespaces) webhookConfigInterface := testClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations() webhookConfig, err := webhookConfigInterface.Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) @@ -100,8 +104,10 @@ func TestSelfRegistrationWithOutURL(t *testing.T) { url := "http://example.com/" registerByURL := false timeoutSeconds := int32(32) + selectedNamespaces := []string{} + ignoredNamespaces := []string{} - selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds) + selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds, selectedNamespaces, ignoredNamespaces) webhookConfigInterface := testClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations() webhookConfig, err := webhookConfigInterface.Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) @@ -117,3 +123,63 @@ func TestSelfRegistrationWithOutURL(t *testing.T) { assert.Nil(t, webhook.ClientConfig.URL, "expected URL to be set") } + +func TestSelfRegistrationWithIgnoredNamespaces(t *testing.T) { + + testClientSet := fake.NewSimpleClientset() + caCert := []byte("fake") + namespace := "default" + serviceName := "vpa-service" + url := "http://example.com/" + registerByURL := false + timeoutSeconds := int32(32) + selectedNamespaces := []string{} + ignoredNamespaces := []string{"test"} + + selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds, selectedNamespaces, ignoredNamespaces) + + webhookConfigInterface := testClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations() + webhookConfig, err := webhookConfigInterface.Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) + + assert.NoError(t, err, "expected no error fetching webhook configuration") + + assert.Len(t, webhookConfig.Webhooks, 1, "expected one webhook configuration") + webhook := webhookConfig.Webhooks[0] + + assert.NotNil(t, webhook.NamespaceSelector.MatchExpressions, "expected namespace selector not to be nil") + assert.Len(t, webhook.NamespaceSelector.MatchExpressions, 1, "expected one match expression") + + matchExpression := webhook.NamespaceSelector.MatchExpressions[0] + assert.Equal(t, matchExpression.Operator, metav1.LabelSelectorOpNotIn, "expected namespace operator to be OpNotIn") + assert.Equal(t, matchExpression.Values, ignoredNamespaces, "expected namespace selector match expression to be equal") +} + +func TestSelfRegistrationWithSelectedNamespaces(t *testing.T) { + + testClientSet := fake.NewSimpleClientset() + caCert := []byte("fake") + namespace := "default" + serviceName := "vpa-service" + url := "http://example.com/" + registerByURL := false + timeoutSeconds := int32(32) + selectedNamespaces := []string{"test"} + ignoredNamespaces := []string{} + + selfRegistration(testClientSet, caCert, namespace, serviceName, url, registerByURL, timeoutSeconds, selectedNamespaces, ignoredNamespaces) + + webhookConfigInterface := testClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations() + webhookConfig, err := webhookConfigInterface.Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) + + assert.NoError(t, err, "expected no error fetching webhook configuration") + + assert.Len(t, webhookConfig.Webhooks, 1, "expected one webhook configuration") + webhook := webhookConfig.Webhooks[0] + + assert.NotNil(t, webhook.NamespaceSelector.MatchExpressions, "expected namespace selector not to be nil") + assert.Len(t, webhook.NamespaceSelector.MatchExpressions, 1, "expected one match expression") + + matchExpression := webhook.NamespaceSelector.MatchExpressions[0] + assert.Equal(t, matchExpression.Operator, metav1.LabelSelectorOpIn, "expected namespace operator to be OpIn") + assert.Equal(t, matchExpression.Values, selectedNamespaces, "expected namespace selector match expression to be equal") +} diff --git a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go index e2d2a81d17a5..04220adbd0f6 100644 --- a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go +++ b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go @@ -32,7 +32,7 @@ import ( "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/spec" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" - "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test" ) @@ -568,3 +568,66 @@ func TestFilterVPAs(t *testing.T) { assert.ElementsMatch(t, expectedResult, result) } + +func TestFilterVPAsIgnoreNamespaces(t *testing.T) { + + vpa1 := &vpa_types.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "namespace1", + }, + Spec: vpa_types.VerticalPodAutoscalerSpec{ + Recommenders: []*vpa_types.VerticalPodAutoscalerRecommenderSelector{ + {Name: DefaultRecommenderName}, + }, + }, + } + vpa2 := &vpa_types.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "namespace2", + }, + Spec: vpa_types.VerticalPodAutoscalerSpec{ + Recommenders: []*vpa_types.VerticalPodAutoscalerRecommenderSelector{ + {Name: DefaultRecommenderName}, + }, + }, + } + vpa3 := &vpa_types.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ignore1", + }, + Spec: vpa_types.VerticalPodAutoscalerSpec{ + Recommenders: []*vpa_types.VerticalPodAutoscalerRecommenderSelector{ + {Name: DefaultRecommenderName}, + }, + }, + } + vpa4 := &vpa_types.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ignore2", + }, + Spec: vpa_types.VerticalPodAutoscalerSpec{ + Recommenders: []*vpa_types.VerticalPodAutoscalerRecommenderSelector{ + {Name: DefaultRecommenderName}, + }, + }, + } + + allVpaCRDs := []*vpa_types.VerticalPodAutoscaler{vpa1, vpa2, vpa3, vpa4} + + feeder := &clusterStateFeeder{ + recommenderName: DefaultRecommenderName, + ignoredNamespaces: []string{"ignore1", "ignore2"}, + } + + // Set expected results + expectedResult := []*vpa_types.VerticalPodAutoscaler{vpa1, vpa2} + + // Run the filterVPAs function + result := filterVPAs(feeder, allVpaCRDs) + + if len(result) != len(expectedResult) { + t.Fatalf("expected %d VPAs, got %d", len(expectedResult), len(result)) + } + + assert.ElementsMatch(t, expectedResult, result) +} diff --git a/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go b/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go index 9a3f10dcca20..c376f5a5a876 100644 --- a/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go +++ b/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go @@ -260,3 +260,100 @@ func newFakeValidator(isValid bool) status.Validator { func (f *fakeValidator) IsStatusValid(statusTimeout time.Duration) (bool, error) { return f.isValid, nil } + +func TestRunOnceIgnoreNamespaceMatchingPods(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + replicas := int32(5) + livePods := 5 + labels := map[string]string{"app": "testingApp"} + selector := parseLabelSelector("app = testingApp") + + containerName := "container1" + rc := apiv1.ReplicationController{ + TypeMeta: metav1.TypeMeta{ + Kind: "ReplicationController", + APIVersion: "apps/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "rc", + Namespace: "default", + }, + Spec: apiv1.ReplicationControllerSpec{ + Replicas: &replicas, + }, + } + pods := make([]*apiv1.Pod, livePods) + eviction := &test.PodsEvictionRestrictionMock{} + + for i := range pods { + pods[i] = test.Pod().WithName("test_"+strconv.Itoa(i)). + AddContainer(test.Container().WithName(containerName).WithCPURequest(resource.MustParse("1")).WithMemRequest(resource.MustParse("100M")).Get()). + WithCreator(&rc.ObjectMeta, &rc.TypeMeta). + Get() + + pods[i].Labels = labels + eviction.On("CanEvict", pods[i]).Return(true) + eviction.On("Evict", pods[i], nil).Return(nil) + } + + factory := &fakeEvictFactory{eviction} + vpaLister := &test.VerticalPodAutoscalerListerMock{} + + podLister := &test.PodListerMock{} + podLister.On("List").Return(pods, nil) + targetRef := &v1.CrossVersionObjectReference{ + Kind: rc.Kind, + Name: rc.Name, + APIVersion: rc.APIVersion, + } + vpaObj := test.VerticalPodAutoscaler(). + WithNamespace("default"). + WithContainer(containerName). + WithTarget("2", "200M"). + WithMinAllowed(containerName, "1", "100M"). + WithMaxAllowed(containerName, "3", "1G"). + WithTargetRef(targetRef).Get() + + vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpaObj}, nil).Once() + + mockSelectorFetcher := target_mock.NewMockVpaTargetSelectorFetcher(ctrl) + mockSelectorFetcher.EXPECT().Fetch(gomock.Eq(vpaObj)).Return(selector, nil) + + updater := &updater{ + vpaLister: vpaLister, + podLister: podLister, + evictionFactory: factory, + evictionRateLimiter: rate.NewLimiter(rate.Inf, 0), + evictionAdmission: priority.NewDefaultPodEvictionAdmission(), + recommendationProcessor: &test.FakeRecommendationProcessor{}, + selectorFetcher: mockSelectorFetcher, + controllerFetcher: controllerfetcher.FakeControllerFetcher{}, + useAdmissionControllerStatus: true, + priorityProcessor: priority.NewProcessor(), + ignoredNamespaces: []string{"not-default"}, + statusValidator: newFakeValidator(true), + } + + updater.RunOnce(context.Background()) + eviction.AssertNumberOfCalls(t, "Evict", 5) +} + +func TestRunOnceIgnoreNamespaceMatching(t *testing.T) { + eviction := &test.PodsEvictionRestrictionMock{} + vpaLister := &test.VerticalPodAutoscalerListerMock{} + vpaObj := test.VerticalPodAutoscaler(). + WithNamespace("default"). + WithContainer("container").Get() + + vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpaObj}, nil).Once() + + updater := &updater{ + vpaLister: vpaLister, + ignoredNamespaces: []string{"default"}, + } + + updater.RunOnce(context.Background()) + eviction.AssertNumberOfCalls(t, "Evict", 0) +}