Skip to content

Commit c525644

Browse files
baiyutangRainbowMango
authored andcommitted
feat: add PodDisruptionBudget support for Karmada control plane components
Signed-off-by: baiyutang <[email protected]>
1 parent 25cf101 commit c525644

File tree

16 files changed

+553
-196
lines changed

16 files changed

+553
-196
lines changed

charts/karmada-operator/templates/karmada-operator-clusterrole.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ rules:
2828
- apiGroups: ["apps"]
2929
resources: ["statefulsets", "deployments"] # to manage statefulsets, e.g. etcd, and deployments, e.g. karmada-operator
3030
verbs: ["get", "create", "update", "delete"]
31+
- apiGroups: ["policy"]
32+
resources: ["poddisruptionbudgets"] # to manage pod disruption budgets for karmada components
33+
verbs: ["get", "create", "update", "delete"]
3134
- nonResourceURLs: ["/healthz"] # used to check whether the karmada apiserver is health
3235
verbs: ["get"]

operator/config/deploy/karmada-operator-clusterrole.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@ rules:
2929
- apiGroups: ["apps"]
3030
resources: ["statefulsets", "deployments"] # to manage statefulsets, e.g. etcd, and deployments, e.g. karmada-operator
3131
verbs: ["get", "create", "update", "delete"]
32+
- apiGroups: ["policy"]
33+
resources: ["poddisruptionbudgets"] # to manage pod disruption budgets for karmada components
34+
verbs: ["get", "create", "update", "delete"]
3235
- nonResourceURLs: ["/healthz"] # used to check whether the karmada apiserver is health
3336
verbs: ["get"]

operator/pkg/controlplane/apiserver/apiserver.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
3030
"github.com/karmada-io/karmada/operator/pkg/controlplane/etcd"
31+
"github.com/karmada-io/karmada/operator/pkg/controlplane/pdb"
3132
"github.com/karmada-io/karmada/operator/pkg/util"
3233
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
3334
"github.com/karmada-io/karmada/operator/pkg/util/patcher"
@@ -87,6 +88,11 @@ func installKarmadaAPIServer(client clientset.Interface, cfg *operatorv1alpha1.K
8788
if err := apiclient.CreateOrUpdateDeployment(client, apiserverDeployment); err != nil {
8889
return fmt.Errorf("error when creating deployment for %s, err: %w", apiserverDeployment.Name, err)
8990
}
91+
92+
if err := pdb.EnsurePodDisruptionBudget(client, util.KarmadaAPIServerName(name), namespace, cfg.CommonSettings.PodDisruptionBudgetConfig, apiserverDeployment.Spec.Template.Labels, apiserverDeployment); err != nil {
93+
return fmt.Errorf("failed to ensure PDB for apiserver component, err: %w", err)
94+
}
95+
9096
return nil
9197
}
9298

@@ -158,6 +164,11 @@ func installKarmadaAggregatedAPIServer(client clientset.Interface, cfg *operator
158164
if err := apiclient.CreateOrUpdateDeployment(client, aggregatedAPIServerDeployment); err != nil {
159165
return fmt.Errorf("error when creating deployment for %s, err: %w", aggregatedAPIServerDeployment.Name, err)
160166
}
167+
168+
if err := pdb.EnsurePodDisruptionBudget(client, util.KarmadaAggregatedAPIServerName(name), namespace, cfg.CommonSettings.PodDisruptionBudgetConfig, aggregatedAPIServerDeployment.Spec.Template.Labels, aggregatedAPIServerDeployment); err != nil {
169+
return fmt.Errorf("failed to ensure PDB for aggregated apiserver component, err: %w", err)
170+
}
171+
161172
return nil
162173
}
163174

operator/pkg/controlplane/apiserver/apiserver_test.go

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,35 @@ func TestEnsureKarmadaAPIServer(t *testing.T) {
6767
}
6868

6969
actions := fakeClient.Actions()
70-
if len(actions) != 2 {
71-
t.Fatalf("expected 2 actions, but got %d", len(actions))
70+
// We now create deployment, service, and PDB, so expect 3 actions
71+
if len(actions) != 3 {
72+
t.Fatalf("expected 3 actions, but got %d", len(actions))
73+
}
74+
75+
// Check that we have deployment, service, and PDB
76+
deploymentCount := 0
77+
serviceCount := 0
78+
pdbCount := 0
79+
for _, action := range actions {
80+
if action.GetResource().Resource == "deployments" {
81+
deploymentCount++
82+
} else if action.GetResource().Resource == "services" {
83+
serviceCount++
84+
} else if action.GetResource().Resource == "poddisruptionbudgets" {
85+
pdbCount++
86+
}
87+
}
88+
89+
if deploymentCount != 1 {
90+
t.Errorf("expected 1 deployment action, but got %d", deploymentCount)
91+
}
92+
93+
if serviceCount != 1 {
94+
t.Errorf("expected 1 service action, but got %d", serviceCount)
95+
}
96+
97+
if pdbCount != 1 {
98+
t.Errorf("expected 1 PDB action, but got %d", pdbCount)
7299
}
73100
}
74101

@@ -108,8 +135,35 @@ func TestEnsureKarmadaAggregatedAPIServer(t *testing.T) {
108135
}
109136

110137
actions := fakeClient.Actions()
111-
if len(actions) != 2 {
112-
t.Fatalf("expected 2 actions, but got %d", len(actions))
138+
// We now create deployment, service, and PDB, so expect 3 actions
139+
if len(actions) != 3 {
140+
t.Fatalf("expected 3 actions, but got %d", len(actions))
141+
}
142+
143+
// Check that we have deployment, service, and PDB
144+
deploymentCount := 0
145+
serviceCount := 0
146+
pdbCount := 0
147+
for _, action := range actions {
148+
if action.GetResource().Resource == "deployments" {
149+
deploymentCount++
150+
} else if action.GetResource().Resource == "services" {
151+
serviceCount++
152+
} else if action.GetResource().Resource == "poddisruptionbudgets" {
153+
pdbCount++
154+
}
155+
}
156+
157+
if deploymentCount != 1 {
158+
t.Errorf("expected 1 deployment action, but got %d", deploymentCount)
159+
}
160+
161+
if serviceCount != 1 {
162+
t.Errorf("expected 1 service action, but got %d", serviceCount)
163+
}
164+
165+
if pdbCount != 1 {
166+
t.Errorf("expected 1 PDB action, but got %d", pdbCount)
113167
}
114168
}
115169

@@ -152,9 +206,14 @@ func TestInstallKarmadaAPIServer(t *testing.T) {
152206
t.Fatalf("expected no error, but got: %v", err)
153207
}
154208

155-
deployment, err := verifyDeploymentCreation(fakeClient, &replicas, imagePullPolicy, cfg.ExtraArgs, name, namespace, image, util.KarmadaAPIServerName(name), priorityClassName)
209+
deployment, err := verifyDeploymentCreation(fakeClient)
156210
if err != nil {
157-
t.Fatalf("failed to verify karmada apiserver correct deployment creation correct details: %v", err)
211+
t.Fatalf("failed to verify karmada apiserver deployment creation: %v", err)
212+
}
213+
214+
// Verify deployment details using the existing function
215+
if err := verifyDeploymentDetails(deployment, &replicas, imagePullPolicy, cfg.ExtraArgs, name, namespace, image, util.KarmadaAPIServerName(name), priorityClassName); err != nil {
216+
t.Fatalf("failed to verify deployment details: %v", err)
158217
}
159218

160219
err = verifyAPIServerDeploymentAdditionalDetails(deployment, name, serviceSubnet)
@@ -248,9 +307,9 @@ func TestInstallKarmadaAggregatedAPIServer(t *testing.T) {
248307
t.Fatalf("Failed to install Karmada Aggregated API Server: %v", err)
249308
}
250309

251-
deployment, err := verifyDeploymentCreation(fakeClient, &replicas, imagePullPolicy, cfg.ExtraArgs, name, namespace, image, util.KarmadaAggregatedAPIServerName(name), priorityClassName)
310+
deployment, err := verifyDeploymentCreation(fakeClient)
252311
if err != nil {
253-
t.Fatalf("failed to verify karmada aggregated apiserver deployment creation correct details: %v", err)
312+
t.Fatalf("failed to verify karmada aggregated apiserver deployment creation: %v", err)
254313
}
255314

256315
err = verifyAggregatedAPIServerDeploymentAdditionalDetails(featureGates, deployment, name)
@@ -315,29 +374,32 @@ func contains(slice []string, item string) bool {
315374
// based on the given parameters. It ensures that the deployment has the correct
316375
// number of replicas, image pull policy, extra arguments, and labels, as well
317376
// as the correct image for the Karmada API server.
318-
func verifyDeploymentCreation(client *fakeclientset.Clientset, replicas *int32, imagePullPolicy corev1.PullPolicy, extraArgs map[string]string, name, namespace, image, expectedDeploymentName, priorityClassName string) (*appsv1.Deployment, error) {
319-
// Assert that a Deployment was created.
377+
func verifyDeploymentCreation(client *fakeclientset.Clientset) (*appsv1.Deployment, error) {
378+
// Assert that a Deployment and PDB were created.
320379
actions := client.Actions()
321-
if len(actions) != 1 {
322-
return nil, fmt.Errorf("expected exactly 1 action either create or update, but got %d actions", len(actions))
323-
}
324-
325-
// Check that the action was a Deployment creation.
326-
createAction, ok := actions[0].(coretesting.CreateAction)
327-
if !ok {
328-
return nil, fmt.Errorf("expected a CreateAction, but got %T", actions[0])
380+
// We now create both deployment and PDB, so expect 2 actions
381+
if len(actions) != 2 {
382+
return nil, fmt.Errorf("expected exactly 2 actions (deployment + PDB), but got %d actions", len(actions))
383+
}
384+
385+
// Find the deployment action
386+
var deployment *appsv1.Deployment
387+
for _, action := range actions {
388+
if action.GetResource().Resource == "deployments" {
389+
createAction, ok := action.(coretesting.CreateAction)
390+
if !ok {
391+
return nil, fmt.Errorf("expected a CreateAction for deployment, but got %T", action)
392+
}
393+
deployment = createAction.GetObject().(*appsv1.Deployment)
394+
break
395+
}
329396
}
330397

331-
// Check that the action was performed on the correct resource.
332-
if createAction.GetResource().Resource != "deployments" {
333-
return nil, fmt.Errorf("expected action on 'deployments', but got '%s'", createAction.GetResource().Resource)
398+
if deployment == nil {
399+
return nil, fmt.Errorf("expected deployment action, but none found")
334400
}
335401

336-
deployment := createAction.GetObject().(*appsv1.Deployment)
337-
err := verifyDeploymentDetails(deployment, replicas, imagePullPolicy, extraArgs, name, namespace, image, expectedDeploymentName, priorityClassName)
338-
if err != nil {
339-
return nil, err
340-
}
402+
// Don't validate details here, let the caller do it
341403

342404
return deployment, nil
343405
}

operator/pkg/controlplane/controlplane.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
2929
"github.com/karmada-io/karmada/operator/pkg/constants"
30+
"github.com/karmada-io/karmada/operator/pkg/controlplane/pdb"
3031
"github.com/karmada-io/karmada/operator/pkg/util"
3132
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
3233
"github.com/karmada-io/karmada/operator/pkg/util/patcher"
@@ -48,6 +49,42 @@ func EnsureControlPlaneComponent(component, name, namespace string, featureGates
4849
if err := apiclient.CreateOrUpdateDeployment(client, deployment); err != nil {
4950
return fmt.Errorf("failed to create deployment resource for component %s, err: %w", component, err)
5051
}
52+
53+
// Ensure PDB for the component if configured
54+
// Map PascalCase component constants to naming functions for PDB
55+
var (
56+
commonSettings *operatorv1alpha1.CommonSettings
57+
pdbName string
58+
)
59+
switch component {
60+
case constants.KarmadaControllerManagerComponent:
61+
if cfg.KarmadaControllerManager != nil {
62+
commonSettings = &cfg.KarmadaControllerManager.CommonSettings
63+
pdbName = util.KarmadaControllerManagerName(name)
64+
}
65+
case constants.KubeControllerManagerComponent:
66+
if cfg.KubeControllerManager != nil {
67+
commonSettings = &cfg.KubeControllerManager.CommonSettings
68+
pdbName = util.KubeControllerManagerName(name)
69+
}
70+
case constants.KarmadaSchedulerComponent:
71+
if cfg.KarmadaScheduler != nil {
72+
commonSettings = &cfg.KarmadaScheduler.CommonSettings
73+
pdbName = util.KarmadaSchedulerName(name)
74+
}
75+
case constants.KarmadaDeschedulerComponent:
76+
if cfg.KarmadaDescheduler != nil {
77+
commonSettings = &cfg.KarmadaDescheduler.CommonSettings
78+
pdbName = util.KarmadaDeschedulerName(name)
79+
}
80+
}
81+
82+
if commonSettings != nil {
83+
if err := pdb.EnsurePodDisruptionBudget(client, pdbName, namespace, commonSettings.PodDisruptionBudgetConfig, deployment.Spec.Template.Labels, deployment); err != nil {
84+
return fmt.Errorf("failed to ensure PDB for component %s, err: %w", component, err)
85+
}
86+
}
87+
5188
return nil
5289
}
5390

operator/pkg/controlplane/controlplane_test.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
appsv1 "k8s.io/api/apps/v1"
2121
corev1 "k8s.io/api/core/v1"
2222
fakeclientset "k8s.io/client-go/kubernetes/fake"
23-
coretesting "k8s.io/client-go/testing"
2423
"k8s.io/utils/ptr"
2524

2625
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
@@ -111,19 +110,29 @@ func TestEnsureAllControlPlaneComponents(t *testing.T) {
111110
}
112111

113112
actions := fakeClient.Actions()
114-
if len(actions) != len(components) {
115-
t.Fatalf("expected %d actions, but got %d", len(components), len(actions))
113+
// We now create both deployments and PDBs, so expect 2 actions per component
114+
expectedActions := len(components) * 2
115+
if len(actions) != expectedActions {
116+
t.Fatalf("expected %d actions, but got %d", expectedActions, len(actions))
116117
}
117118

119+
// Check that we have both deployments and PDBs
120+
deploymentCount := 0
121+
pdbCount := 0
118122
for _, action := range actions {
119-
createAction, ok := action.(coretesting.CreateAction)
120-
if !ok {
121-
t.Errorf("expected CreateAction, but got %T", action)
123+
if action.GetResource().Resource == "deployments" {
124+
deploymentCount++
125+
} else if action.GetResource().Resource == "poddisruptionbudgets" {
126+
pdbCount++
122127
}
128+
}
123129

124-
if createAction.GetResource().Resource != "deployments" {
125-
t.Errorf("expected action on 'deployments', but got '%s'", createAction.GetResource().Resource)
126-
}
130+
if deploymentCount != len(components) {
131+
t.Errorf("expected %d deployment actions, but got %d", len(components), deploymentCount)
132+
}
133+
134+
if pdbCount != len(components) {
135+
t.Errorf("expected %d PDB actions, but got %d", len(components), pdbCount)
127136
}
128137
}
129138

operator/pkg/controlplane/etcd/etcd.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
3131
"github.com/karmada-io/karmada/operator/pkg/constants"
32+
"github.com/karmada-io/karmada/operator/pkg/controlplane/pdb"
3233
"github.com/karmada-io/karmada/operator/pkg/util"
3334
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
3435
"github.com/karmada-io/karmada/operator/pkg/util/patcher"
@@ -98,6 +99,10 @@ func installKarmadaEtcd(client clientset.Interface, name, namespace string, cfg
9899
return fmt.Errorf("error when creating Etcd statefulset, err: %w", err)
99100
}
100101

102+
if err := pdb.EnsurePodDisruptionBudget(client, util.KarmadaEtcdName(name), namespace, cfg.CommonSettings.PodDisruptionBudgetConfig, etcdStatefulSet.Spec.Template.Labels, etcdStatefulSet); err != nil {
103+
return fmt.Errorf("failed to ensure PDB for etcd component, err: %w", err)
104+
}
105+
101106
return nil
102107
}
103108

0 commit comments

Comments
 (0)