Skip to content

Commit 2670d22

Browse files
pkg/karmadactl/addons: unit test descheduler
In this commit, we unit test descheduler addon on enabling, disabling, and status operations. In addition to this we refactor some code to utilize some utilities across karmadactl addons. Signed-off-by: Mohamed Awnallah <[email protected]>
1 parent 057cf86 commit 2670d22

File tree

8 files changed

+336
-36
lines changed

8 files changed

+336
-36
lines changed

pkg/karmadactl/addons/descheduler/descheduler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ var enableDescheduler = func(opts *addoninit.CommandAddonsEnableOption) error {
7676
return fmt.Errorf("create karmada descheduler deployment error: %v", err)
7777
}
7878

79-
if err := cmdutil.WaitForDeploymentRollout(opts.KubeClientSet, karmadaDeschedulerDeployment, opts.WaitComponentReadyTimeout); err != nil {
79+
if err := addonutils.WaitForDeploymentRollout(opts.KubeClientSet, karmadaDeschedulerDeployment, opts.WaitComponentReadyTimeout); err != nil {
8080
return fmt.Errorf("wait karmada descheduler pod timeout: %v", err)
8181
}
8282

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/*
2+
Copyright 2024 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package descheduler
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
"testing"
24+
25+
appsv1 "k8s.io/api/apps/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
kuberuntime "k8s.io/apimachinery/pkg/runtime"
28+
clientset "k8s.io/client-go/kubernetes"
29+
fakeclientset "k8s.io/client-go/kubernetes/fake"
30+
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
31+
"k8s.io/utils/ptr"
32+
33+
addoninit "github.com/karmada-io/karmada/pkg/karmadactl/addons/init"
34+
addonutils "github.com/karmada-io/karmada/pkg/karmadactl/addons/utils"
35+
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
36+
)
37+
38+
func TestStatus(t *testing.T) {
39+
name, namespace := addoninit.DeschedulerResourceName, "test"
40+
var replicas int32 = 2
41+
tests := []struct {
42+
name string
43+
listOpts *addoninit.CommandAddonsListOption
44+
prep func(*addoninit.CommandAddonsListOption) error
45+
wantStatus string
46+
wantErr bool
47+
errMsg string
48+
}{
49+
{
50+
name: "Status_WithoutDescheduler_AddonDisabledStatus",
51+
listOpts: &addoninit.CommandAddonsListOption{
52+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
53+
KubeClientSet: fakeclientset.NewSimpleClientset(),
54+
},
55+
},
56+
prep: func(*addoninit.CommandAddonsListOption) error { return nil },
57+
wantStatus: addoninit.AddonDisabledStatus,
58+
},
59+
{
60+
name: "Status_WithNetworkIssue_AddonUnknownStatus",
61+
listOpts: &addoninit.CommandAddonsListOption{
62+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
63+
KubeClientSet: fakeclientset.NewSimpleClientset(),
64+
},
65+
},
66+
prep: func(listOpts *addoninit.CommandAddonsListOption) error {
67+
return addonutils.SimulateNetworkErrorOnOp(listOpts.KubeClientSet, "get", "deployments")
68+
},
69+
wantStatus: addoninit.AddonUnknownStatus,
70+
wantErr: true,
71+
errMsg: "unexpected error: encountered a network issue while get the deployments",
72+
},
73+
{
74+
name: "Status_ForKarmadaDeschedulerNotFullyAvailable_AddonUnhealthyStatus",
75+
listOpts: &addoninit.CommandAddonsListOption{
76+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
77+
Namespace: namespace,
78+
KubeClientSet: fakeclientset.NewSimpleClientset(),
79+
},
80+
},
81+
prep: func(listOpts *addoninit.CommandAddonsListOption) error {
82+
if err := createKarmadaDeschedulerDeployment(listOpts.KubeClientSet, replicas, listOpts.Namespace); err != nil {
83+
return fmt.Errorf("failed to create karmada descheduler deployment, got error: %v", err)
84+
}
85+
return addonutils.SimulateDeploymentUnready(listOpts.KubeClientSet, name, listOpts.Namespace)
86+
},
87+
wantStatus: addoninit.AddonUnhealthyStatus,
88+
},
89+
{
90+
name: "Status_WithAvailableKarmadaDeschedulerDeployment_AddonEnabledStatus",
91+
listOpts: &addoninit.CommandAddonsListOption{
92+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
93+
Namespace: namespace,
94+
KubeClientSet: fakeclientset.NewSimpleClientset(),
95+
},
96+
},
97+
prep: func(listOpts *addoninit.CommandAddonsListOption) error {
98+
if err := createKarmadaDeschedulerDeployment(listOpts.KubeClientSet, replicas, listOpts.Namespace); err != nil {
99+
return fmt.Errorf("failed to create karmada descheduler deployment, got error: %v", err)
100+
}
101+
return nil
102+
},
103+
wantStatus: addoninit.AddonEnabledStatus,
104+
},
105+
}
106+
for _, test := range tests {
107+
t.Run(test.name, func(t *testing.T) {
108+
if err := test.prep(test.listOpts); err != nil {
109+
t.Fatalf("failed to prep test env before checking on karmada descheduler addon status, got error: %v", err)
110+
}
111+
deschedulerAddonStatus, err := status(test.listOpts)
112+
if err == nil && test.wantErr {
113+
t.Fatal("expected an error, but got none")
114+
}
115+
if err != nil && !test.wantErr {
116+
t.Fatalf("unexpected error, got: %v", err)
117+
}
118+
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
119+
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
120+
}
121+
if deschedulerAddonStatus != test.wantStatus {
122+
t.Errorf("expected addon status to be %s, but got %s", test.wantStatus, deschedulerAddonStatus)
123+
}
124+
})
125+
}
126+
}
127+
128+
func TestEnableDescheduler(t *testing.T) {
129+
name, namespace := addoninit.DeschedulerResourceName, "test"
130+
var replicas int32 = 2
131+
tests := []struct {
132+
name string
133+
enableOpts *addoninit.CommandAddonsEnableOption
134+
prep func() error
135+
wantErr bool
136+
errMsg string
137+
}{
138+
{
139+
name: "EnableDescheduler_WaitingForKarmadaDescheduler_Created",
140+
enableOpts: &addoninit.CommandAddonsEnableOption{
141+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
142+
Namespace: namespace,
143+
KubeClientSet: fakeclientset.NewSimpleClientset(),
144+
},
145+
KarmadaDeschedulerReplicas: replicas,
146+
},
147+
prep: func() error {
148+
addonutils.WaitForDeploymentRollout = func(client clientset.Interface, _ *appsv1.Deployment, _ int) error {
149+
_, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
150+
if err != nil {
151+
return fmt.Errorf("failed to get deployment %s, got an error: %v", name, err)
152+
}
153+
return nil
154+
}
155+
return nil
156+
},
157+
wantErr: false,
158+
},
159+
}
160+
for _, test := range tests {
161+
t.Run(test.name, func(t *testing.T) {
162+
if err := test.prep(); err != nil {
163+
t.Fatalf("failed to prep test environment before enabling descheduler, got an error: %v", err)
164+
}
165+
err := enableDescheduler(test.enableOpts)
166+
if err == nil && test.wantErr {
167+
t.Fatal("expected an error, but got none")
168+
}
169+
if err != nil && !test.wantErr {
170+
t.Errorf("unexpected error, got: %v", err)
171+
}
172+
})
173+
}
174+
}
175+
176+
func TestDisableDescheduler(t *testing.T) {
177+
name, namespace := addoninit.DeschedulerResourceName, "test"
178+
client := fakeclientset.NewSimpleClientset()
179+
var replicas int32 = 2
180+
tests := []struct {
181+
name string
182+
enableOpts *addoninit.CommandAddonsEnableOption
183+
disableOpts *addoninit.CommandAddonsDisableOption
184+
prep func(*addoninit.CommandAddonsEnableOption) error
185+
verify func(clientset.Interface) error
186+
wantErr bool
187+
errMsg string
188+
}{
189+
{
190+
name: "DisableDescheduler_DisablingKarmadaDescheduler_Disabled",
191+
enableOpts: &addoninit.CommandAddonsEnableOption{
192+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
193+
Namespace: namespace,
194+
KubeClientSet: client,
195+
},
196+
KarmadaDeschedulerReplicas: replicas,
197+
},
198+
disableOpts: &addoninit.CommandAddonsDisableOption{
199+
GlobalCommandOptions: addoninit.GlobalCommandOptions{
200+
Namespace: namespace,
201+
KubeClientSet: client,
202+
},
203+
},
204+
prep: func(enableOpts *addoninit.CommandAddonsEnableOption) error {
205+
addonutils.WaitForDeploymentRollout = func(client clientset.Interface, _ *appsv1.Deployment, _ int) error {
206+
_, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
207+
if err != nil {
208+
return fmt.Errorf("failed to get deployment %s, got an error: %v", name, err)
209+
}
210+
return nil
211+
}
212+
if err := enableDescheduler(enableOpts); err != nil {
213+
return fmt.Errorf("failed to enable descheduler, got an error: %v", err)
214+
}
215+
return nil
216+
},
217+
verify: func(client clientset.Interface) error {
218+
_, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
219+
if err == nil {
220+
return fmt.Errorf("deployment %s was expected to be deleted, but it was still found", name)
221+
}
222+
return nil
223+
},
224+
},
225+
}
226+
for _, test := range tests {
227+
t.Run(test.name, func(t *testing.T) {
228+
if err := test.prep(test.enableOpts); err != nil {
229+
t.Fatalf("failed to prep test environment before disabling descheduler, got an error: %v", err)
230+
}
231+
err := disableDescheduler(test.disableOpts)
232+
if err == nil && test.wantErr {
233+
t.Fatal("expected an error, but got none")
234+
}
235+
if err != nil && !test.wantErr {
236+
t.Errorf("unexpected error, got: %v", err)
237+
}
238+
if err := test.verify(client); err != nil {
239+
t.Errorf("failed to verify disabling descheduler, got an error: %v", err)
240+
}
241+
})
242+
}
243+
}
244+
245+
// createKarmadaDeschedulerDeployment creates or updates a Deployment for the Karmada descheduler
246+
// in the specified namespace with the provided number of replicas.
247+
// It parses and decodes the template for the Deployment before applying it to the cluster.
248+
func createKarmadaDeschedulerDeployment(c clientset.Interface, replicas int32, namespace string) error {
249+
karmadaDeschedulerDeploymentBytes, err := addonutils.ParseTemplate(karmadaDeschedulerDeployment, DeploymentReplace{
250+
Namespace: namespace,
251+
Replicas: ptr.To[int32](replicas),
252+
})
253+
if err != nil {
254+
return fmt.Errorf("error when parsing karmada descheduler deployment template: %v", err)
255+
}
256+
257+
karmadaDeschedulerDeployment := &appsv1.Deployment{}
258+
if err = kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaDeschedulerDeploymentBytes, karmadaDeschedulerDeployment); err != nil {
259+
return fmt.Errorf("failed to decode karmada descheduler deployment, got error: %v", err)
260+
}
261+
if err = cmdutil.CreateOrUpdateDeployment(c, karmadaDeschedulerDeployment); err != nil {
262+
return fmt.Errorf("failed to create karmada descheduler deployment, got error: %v", err)
263+
}
264+
return nil
265+
}

pkg/karmadactl/addons/estimator/estimator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ var enableEstimator = func(opts *addoninit.CommandAddonsEnableOption) error {
127127
return fmt.Errorf("create or update scheduler estimator deployment error: %v", err)
128128
}
129129

130-
if err := cmdutil.WaitForDeploymentRollout(opts.KubeClientSet, karmadaEstimatorDeployment, opts.WaitComponentReadyTimeout); err != nil {
130+
if err := addonutils.WaitForDeploymentRollout(opts.KubeClientSet, karmadaEstimatorDeployment, opts.WaitComponentReadyTimeout); err != nil {
131131
klog.Warning(err)
132132
}
133133
klog.Infof("Karmada scheduler estimator of member cluster %s is installed successfully.", opts.Cluster)

pkg/karmadactl/addons/metricsadapter/metricsadapter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
169169
return fmt.Errorf("create karmada metrics adapter deployment error: %v", err)
170170
}
171171

172-
if err = cmdutil.WaitForDeploymentRollout(opts.KubeClientSet, karmadaMetricsAdapterDeployment, opts.WaitComponentReadyTimeout); err != nil {
172+
if err = addonutils.WaitForDeploymentRollout(opts.KubeClientSet, karmadaMetricsAdapterDeployment, opts.WaitComponentReadyTimeout); err != nil {
173173
return fmt.Errorf("wait karmada metrics adapter pod status ready timeout: %v", err)
174174
}
175175

pkg/karmadactl/addons/metricsadapter/metricsadapter_test.go

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ import (
2424

2525
appsv1 "k8s.io/api/apps/v1"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27-
"k8s.io/apimachinery/pkg/runtime"
2827
kuberuntime "k8s.io/apimachinery/pkg/runtime"
2928
clientset "k8s.io/client-go/kubernetes"
3029
fakeclientset "k8s.io/client-go/kubernetes/fake"
3130
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
32-
coretesting "k8s.io/client-go/testing"
3331
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
3432
aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
3533
fakeAggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
@@ -69,7 +67,7 @@ func TestStatus(t *testing.T) {
6967
},
7068
},
7169
prep: func(listOpts *addoninit.CommandAddonsListOption) error {
72-
return simulateNetworkErrorOnOp(listOpts.KubeClientSet, "get", "deployments")
70+
return addonutils.SimulateNetworkErrorOnOp(listOpts.KubeClientSet, "get", "deployments")
7371
},
7472
wantStatus: addoninit.AddonUnknownStatus,
7573
wantErr: true,
@@ -87,7 +85,7 @@ func TestStatus(t *testing.T) {
8785
if err := createKarmadaMetricsDeployment(listOpts.KubeClientSet, replicas, listOpts.Namespace); err != nil {
8886
return fmt.Errorf("failed to create karmada metrics deployment, got error: %v", err)
8987
}
90-
return simulateKarmadaMetricsDeploymentUnready(listOpts.KubeClientSet, name, listOpts.Namespace)
88+
return addonutils.SimulateDeploymentUnready(listOpts.KubeClientSet, name, listOpts.Namespace)
9189
},
9290
wantStatus: addoninit.AddonUnhealthyStatus,
9391
},
@@ -167,15 +165,6 @@ func TestStatus(t *testing.T) {
167165
}
168166
}
169167

170-
// simulateNetworkErrorOnOp simulates a network error during the specified
171-
// operation on a resource by prepending a reactor to the fake client.
172-
func simulateNetworkErrorOnOp(c clientset.Interface, operation, resource string) error {
173-
c.(*fakeclientset.Clientset).Fake.PrependReactor(operation, resource, func(coretesting.Action) (bool, runtime.Object, error) {
174-
return true, nil, fmt.Errorf("unexpected error: encountered a network issue while %s the %s", operation, resource)
175-
})
176-
return nil
177-
}
178-
179168
// createKarmadaMetricsDeployment creates or updates a Deployment for the Karmada metrics adapter
180169
// in the specified namespace with the provided number of replicas.
181170
// It parses and decodes the template for the Deployment before applying it to the cluster.
@@ -236,24 +225,6 @@ func updateAAAPIServicesCondition(services []*apiregistrationv1.APIService, a ag
236225
return nil
237226
}
238227

239-
// simulateKarmadaMetricsDeploymentUnready simulates a "not ready" status by incrementing the replicas
240-
// of the specified Deployment, thus marking it as unready. This is useful for testing the handling
241-
// of Deployment readiness in Karmada.
242-
func simulateKarmadaMetricsDeploymentUnready(c clientset.Interface, name, namespace string) error {
243-
deployment, err := c.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
244-
if err != nil {
245-
return fmt.Errorf("failed to get deployment %s in namespace %s, got error: %v", name, namespace, err)
246-
}
247-
248-
deployment.Status.Replicas = *deployment.Spec.Replicas + 1
249-
_, err = c.AppsV1().Deployments(namespace).UpdateStatus(context.TODO(), deployment, metav1.UpdateOptions{})
250-
if err != nil {
251-
return fmt.Errorf("failed to update replicas status of deployment %s in namespace %s, got error: %v", name, namespace, err)
252-
}
253-
254-
return nil
255-
}
256-
257228
// createAndMarkAAAPIServicesAvailable creates the specified AA API services and then
258229
// updates their conditions to mark them as available, setting a "ConditionTrue" status.
259230
// This function is a combination of the creation and condition-setting operations for convenience.

pkg/karmadactl/addons/search/search.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
182182
return fmt.Errorf("create karmada search deployment error: %v", err)
183183
}
184184

185-
if err := cmdutil.WaitForDeploymentRollout(opts.KubeClientSet, karmadaSearchDeployment, opts.WaitComponentReadyTimeout); err != nil {
185+
if err := addonutils.WaitForDeploymentRollout(opts.KubeClientSet, karmadaSearchDeployment, opts.WaitComponentReadyTimeout); err != nil {
186186
return fmt.Errorf("wait karmada search pod status ready timeout: %v", err)
187187
}
188188

0 commit comments

Comments
 (0)