Skip to content

Commit edaca92

Browse files
authored
feat: new mode of resource resize for 1.32 (#106)
1 parent e3bec98 commit edaca92

15 files changed

+811
-176
lines changed

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/main.go

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

2828
"k8s.io/apimachinery/pkg/runtime"
2929
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
30+
"k8s.io/apimachinery/pkg/version"
3031
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3132
ctrl "sigs.k8s.io/controller-runtime"
3233
"sigs.k8s.io/controller-runtime/pkg/healthz"
@@ -125,7 +126,7 @@ func main() {
125126
}
126127

127128
boostMgr := boost.NewManager(mgr.GetClient())
128-
go setupControllers(mgr, boostMgr, cfg, certsReady)
129+
go setupControllers(mgr, boostMgr, cfg, versionInfo, certsReady)
129130

130131
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
131132
setupLog.Error(err, "unable to set up health check")
@@ -145,7 +146,8 @@ func main() {
145146
}
146147
}
147148

148-
func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, cfg *config.Config, certsReady chan struct{}) {
149+
func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, cfg *config.Config, serverVersion *version.Info,
150+
certsReady chan struct{}) {
149151
setupLog.Info("Waiting for certificate generation to complete")
150152
<-certsReady
151153
setupLog.Info("Certificate generation has completed")
@@ -163,7 +165,7 @@ func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, cfg *config.Conf
163165
Manager: boostMgr,
164166
}
165167
boostMgr.SetStartupCPUBoostReconciler(boostCtrl)
166-
if err := boostCtrl.SetupWithManager(mgr); err != nil {
168+
if err := boostCtrl.SetupWithManager(mgr, serverVersion); err != nil {
167169
setupLog.Error(err, "unable to create controller", "controller", "StartupCPUBoost")
168170
os.Exit(1)
169171
}

config/rbac/role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ rules:
1515
verbs:
1616
- get
1717
- list
18+
- patch
1819
- update
1920
- watch
21+
- apiGroups:
22+
- ""
23+
resources:
24+
- pods/resize
25+
verbs:
26+
- patch
2027
- apiGroups:
2128
- admissionregistration.k8s.io
2229
resourceNames:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
toolchain go1.23.2
66

77
require (
8+
github.com/evanphx/json-patch/v5 v5.9.11
89
github.com/go-logr/logr v1.4.2
910
github.com/onsi/ginkgo/v2 v2.23.2
1011
github.com/onsi/gomega v1.36.2
@@ -28,7 +29,6 @@ require (
2829
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2930
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3031
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
31-
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
3232
github.com/fsnotify/fsnotify v1.8.0 // indirect
3333
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
3434
github.com/go-logr/zapr v1.3.0 // indirect

hack/boilerplate.go.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Google LLC
1+
// Copyright 2025 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

internal/boost/manager_test.go

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
autoscaling "github.com/google/kube-startup-cpu-boost/api/v1alpha1"
2222
cpuboost "github.com/google/kube-startup-cpu-boost/internal/boost"
2323
"github.com/google/kube-startup-cpu-boost/internal/boost/duration"
24+
bpod "github.com/google/kube-startup-cpu-boost/internal/boost/pod"
2425
"github.com/google/kube-startup-cpu-boost/internal/metrics"
2526
"github.com/google/kube-startup-cpu-boost/internal/mock"
2627
. "github.com/onsi/ginkgo/v2"
@@ -39,16 +40,17 @@ var _ = Describe("Manager", func() {
3940
})
4041
Describe("Registers startup-cpu-boost", func() {
4142
var (
42-
spec *autoscaling.StartupCPUBoost
43-
boost cpuboost.StartupCPUBoost
44-
err error
43+
spec *autoscaling.StartupCPUBoost
44+
boost cpuboost.StartupCPUBoost
45+
useLegacyRevertMode bool
46+
err error
4547
)
4648
BeforeEach(func() {
4749
spec = specTemplate.DeepCopy()
4850
})
4951
JustBeforeEach(func() {
5052
manager = cpuboost.NewManager(nil)
51-
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
53+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec, useLegacyRevertMode)
5254
Expect(err).ToNot(HaveOccurred())
5355
})
5456
When("startup-cpu-boost exists", func() {
@@ -81,16 +83,17 @@ var _ = Describe("Manager", func() {
8183
})
8284
Describe("De-registers startup-cpu-boost", func() {
8385
var (
84-
spec *autoscaling.StartupCPUBoost
85-
boost cpuboost.StartupCPUBoost
86-
err error
86+
spec *autoscaling.StartupCPUBoost
87+
boost cpuboost.StartupCPUBoost
88+
useLegacyRevertMode bool
89+
err error
8790
)
8891
BeforeEach(func() {
8992
spec = specTemplate.DeepCopy()
9093
})
9194
JustBeforeEach(func() {
9295
manager = cpuboost.NewManager(nil)
93-
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
96+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec, useLegacyRevertMode)
9497
Expect(err).ToNot(HaveOccurred())
9598
})
9699
When("startup-cpu-boost exists", func() {
@@ -110,10 +113,11 @@ var _ = Describe("Manager", func() {
110113
})
111114
Describe("updates startup-cpu-boost from spec", func() {
112115
var (
113-
boost cpuboost.StartupCPUBoost
114-
err error
115-
spec *autoscaling.StartupCPUBoost
116-
updatedSpec *autoscaling.StartupCPUBoost
116+
boost cpuboost.StartupCPUBoost
117+
err error
118+
useLegacyRevertMode bool
119+
spec *autoscaling.StartupCPUBoost
120+
updatedSpec *autoscaling.StartupCPUBoost
117121
)
118122
BeforeEach(func() {
119123
spec = specTemplate.DeepCopy()
@@ -124,7 +128,7 @@ var _ = Describe("Manager", func() {
124128
}
125129
})
126130
JustBeforeEach(func() {
127-
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
131+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec, useLegacyRevertMode)
128132
Expect(err).ToNot(HaveOccurred())
129133
})
130134
When("startup-cpu-boost is registered", func() {
@@ -148,11 +152,12 @@ var _ = Describe("Manager", func() {
148152
})
149153
Describe("retrieves startup-cpu-boost for a POD", func() {
150154
var (
151-
pod *corev1.Pod
152-
podNameLabel string
153-
podNameLabelValue string
154-
boost cpuboost.StartupCPUBoost
155-
found bool
155+
pod *corev1.Pod
156+
podNameLabel string
157+
podNameLabelValue string
158+
boost cpuboost.StartupCPUBoost
159+
useLegacyRevertMode bool
160+
found bool
156161
)
157162
BeforeEach(func() {
158163
podNameLabel = "app.kubernetes.io/name"
@@ -184,7 +189,7 @@ var _ = Describe("Manager", func() {
184189
spec.Selector = *metav1.AddLabelToSelector(&metav1.LabelSelector{}, podNameLabel, podNameLabelValue)
185190
})
186191
JustBeforeEach(func() {
187-
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
192+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec, useLegacyRevertMode)
188193
Expect(err).NotTo(HaveOccurred())
189194
err = manager.AddStartupCPUBoost(context.TODO(), boost)
190195
Expect(err).NotTo(HaveOccurred())
@@ -242,12 +247,13 @@ var _ = Describe("Manager", func() {
242247
})
243248
When("There are startup-cpu-boosts with fixed duration policy", func() {
244249
var (
245-
spec *autoscaling.StartupCPUBoost
246-
boost cpuboost.StartupCPUBoost
247-
pod *corev1.Pod
248-
mockClient *mock.MockClient
249-
mockReconciler *mock.MockReconciler
250-
c chan time.Time
250+
spec *autoscaling.StartupCPUBoost
251+
boost cpuboost.StartupCPUBoost
252+
useLegacyRevertMode bool
253+
pod *corev1.Pod
254+
mockClient *mock.MockClient
255+
mockReconciler *mock.MockReconciler
256+
c chan time.Time
251257
)
252258
BeforeEach(func() {
253259
spec = specTemplate.DeepCopy()
@@ -265,13 +271,12 @@ var _ = Describe("Manager", func() {
265271
c = make(chan time.Time, 1)
266272
mockTicker.EXPECT().Tick().MinTimes(1).Return(c)
267273
mockTicker.EXPECT().Stop().Return()
268-
mockClient.EXPECT().Update(gomock.Any(), gomock.Eq(pod)).MinTimes(1).Return(nil)
269274
reconcileReq := reconcile.Request{NamespacedName: types.NamespacedName{Name: spec.Name, Namespace: spec.Namespace}}
270275
mockReconciler.EXPECT().Reconcile(gomock.Any(), gomock.Eq(reconcileReq)).Times(1)
271276
})
272277
JustBeforeEach(func() {
273278
manager.SetStartupCPUBoostReconciler(mockReconciler)
274-
boost, err = cpuboost.NewStartupCPUBoost(mockClient, spec)
279+
boost, err = cpuboost.NewStartupCPUBoost(mockClient, spec, useLegacyRevertMode)
275280
Expect(err).ShouldNot(HaveOccurred())
276281
err = boost.UpsertPod(ctx, pod)
277282
Expect(err).ShouldNot(HaveOccurred())
@@ -283,8 +288,30 @@ var _ = Describe("Manager", func() {
283288
cancel()
284289
<-done
285290
})
286-
It("doesn't error", func() {
287-
Expect(err).NotTo(HaveOccurred())
291+
When("legacy revert mode is not used", func() {
292+
var (
293+
mockSubResourceClient *mock.MockSubResourceClient
294+
)
295+
BeforeEach(func() {
296+
mockSubResourceClient = mock.NewMockSubResourceClient(mockCtrl)
297+
mockSubResourceClient.EXPECT().Patch(gomock.Any(), gomock.Eq(pod),
298+
gomock.Eq(bpod.NewRevertBootsResourcesPatch())).Return(nil).Times(1)
299+
mockClient.EXPECT().SubResource("resize").Return(mockSubResourceClient).Times(1)
300+
mockClient.EXPECT().Patch(gomock.Any(), gomock.Eq(pod),
301+
gomock.Eq(bpod.NewRevertBoostLabelsPatch())).Return(nil).Times(1)
302+
})
303+
It("doesn't error", func() {
304+
Expect(err).NotTo(HaveOccurred())
305+
})
306+
})
307+
When("legacy revert mode is used", func() {
308+
BeforeEach(func() {
309+
useLegacyRevertMode = true
310+
mockClient.EXPECT().Update(gomock.Any(), gomock.Eq(pod)).MinTimes(1).Return(nil)
311+
})
312+
It("doesn't error", func() {
313+
Expect(err).NotTo(HaveOccurred())
314+
})
288315
})
289316
})
290317
})

internal/boost/pod/pod.go

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ import (
2222
"fmt"
2323
"time"
2424

25+
jsonpatch "github.com/evanphx/json-patch/v5"
2526
corev1 "k8s.io/api/core/v1"
2627
apiResource "k8s.io/apimachinery/pkg/api/resource"
28+
"k8s.io/apimachinery/pkg/types"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
2730
)
2831

2932
const (
3033
BoostLabelKey = "autoscaling.x-k8s.io/startup-cpu-boost"
3134
BoostAnnotationKey = "autoscaling.x-k8s.io/startup-cpu-boost"
35+
EmptyPatchString = "{}"
3236
)
3337

3438
type BoostPodAnnotation struct {
@@ -37,6 +41,8 @@ type BoostPodAnnotation struct {
3741
InitCPULimits map[string]string `json:"initCPULimits,omitempty"`
3842
}
3943

44+
type mutatePodFunc func(pod *corev1.Pod) error
45+
4046
func NewBoostAnnotation() *BoostPodAnnotation {
4147
return &BoostPodAnnotation{
4248
BoostTimestamp: time.Now(),
@@ -66,12 +72,23 @@ func BoostAnnotationFromPod(pod *corev1.Pod) (*BoostPodAnnotation, error) {
6672
}
6773

6874
func RevertResourceBoost(pod *corev1.Pod) error {
75+
if err := revertBoostResources(pod); err != nil {
76+
return err
77+
}
78+
return revertBoostLabels(pod)
79+
}
80+
81+
func revertBoostLabels(pod *corev1.Pod) error {
82+
delete(pod.Labels, BoostLabelKey)
83+
delete(pod.Annotations, BoostAnnotationKey)
84+
return nil
85+
}
86+
87+
func revertBoostResources(pod *corev1.Pod) error {
6988
annotation, err := BoostAnnotationFromPod(pod)
7089
if err != nil {
7190
return fmt.Errorf("failed to get boost annotation from pod: %s", err)
7291
}
73-
delete(pod.Labels, BoostLabelKey)
74-
delete(pod.Annotations, BoostAnnotationKey)
7592
for i := range pod.Spec.Containers {
7693
container := &pod.Spec.Containers[i]
7794
if request, ok := annotation.InitCPURequests[container.Name]; ok {
@@ -97,3 +114,61 @@ func RevertResourceBoost(pod *corev1.Pod) error {
97114
}
98115
return nil
99116
}
117+
118+
func buildPodPatch(pod *corev1.Pod, mutatePodFunc mutatePodFunc) ([]byte, error) {
119+
podJSON, err := json.Marshal(pod)
120+
if err != nil {
121+
return nil, err
122+
}
123+
if err := mutatePodFunc(pod); err != nil {
124+
return nil, err
125+
}
126+
updatedPodJSON, err := json.Marshal(pod)
127+
if err != nil {
128+
return nil, err
129+
}
130+
return jsonpatch.CreateMergePatch(podJSON, updatedPodJSON)
131+
132+
}
133+
134+
func NewRevertBoostLabelsPatch() client.Patch {
135+
return &revertBoostLabelsPatch{}
136+
}
137+
138+
type revertBoostLabelsPatch struct {
139+
}
140+
141+
func (p *revertBoostLabelsPatch) Type() types.PatchType {
142+
return types.MergePatchType
143+
}
144+
145+
func (p *revertBoostLabelsPatch) Data(obj client.Object) ([]byte, error) {
146+
pod, ok := obj.(*corev1.Pod)
147+
if !ok {
148+
return nil, errors.New("revertBoostLabelsPatch applies only on *corev1.Pod objects")
149+
}
150+
return buildPodPatch(pod, revertBoostLabels)
151+
}
152+
153+
func NewRevertBootsResourcesPatch() client.Patch {
154+
return &revertBoostResourcesPatch{}
155+
}
156+
157+
type revertBoostResourcesPatch struct {
158+
}
159+
160+
func (p *revertBoostResourcesPatch) Type() types.PatchType {
161+
return types.MergePatchType
162+
}
163+
164+
func (p *revertBoostResourcesPatch) Data(obj client.Object) ([]byte, error) {
165+
pod, ok := obj.(*corev1.Pod)
166+
if !ok {
167+
return nil, errors.New("revertBoostResourcesPatch applies only on *corev1.Pod objects")
168+
}
169+
patchData, err := buildPodPatch(pod, revertBoostResources)
170+
if err != nil {
171+
return []byte(EmptyPatchString), nil
172+
}
173+
return patchData, nil
174+
}

0 commit comments

Comments
 (0)