Skip to content

Commit 0da9e7e

Browse files
authored
feat: spec updates handled and reflected by boost manager (#89)
1 parent 4ece136 commit 0da9e7e

8 files changed

+304
-52
lines changed

Diff for: internal/boost/manager.go

+20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"sync"
2222
"time"
2323

24+
autoscaling "github.com/google/kube-startup-cpu-boost/api/v1alpha1"
2425
corev1 "k8s.io/api/core/v1"
2526
"k8s.io/apimachinery/pkg/types"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -46,6 +47,8 @@ type Manager interface {
4647
AddStartupCPUBoost(ctx context.Context, boost StartupCPUBoost) error
4748
// RemoveStartupCPUBoost removes a startup-cpu-boost from a manager
4849
RemoveStartupCPUBoost(ctx context.Context, namespace, name string)
50+
// UpdateStartupCPUBoost updates a startup-cpu-boost in a manager
51+
UpdateStartupCPUBoost(ctx context.Context, spec *autoscaling.StartupCPUBoost) error
4952
// StartupCPUBoost returns a startup-cpu-boost with a given name and namespace
5053
StartupCPUBoostForPod(ctx context.Context, pod *corev1.Pod) (StartupCPUBoost, bool)
5154
// StartupCPUBoostForPod returns a startup-cpu-boost that matches a given pod
@@ -141,6 +144,23 @@ func (m *managerImpl) RemoveStartupCPUBoost(ctx context.Context, namespace, name
141144
log.Info("boost deleted successfully")
142145
}
143146

147+
func (m *managerImpl) UpdateStartupCPUBoost(ctx context.Context, spec *autoscaling.StartupCPUBoost) error {
148+
m.Lock()
149+
defer m.Unlock()
150+
log := m.log.WithValues("boost", spec.ObjectMeta.Name, "namespace", spec.ObjectMeta.Namespace)
151+
log.V(5).Info("handling boost update")
152+
boost, ok := m.getStartupCPUBoost(spec.ObjectMeta.Namespace, spec.ObjectMeta.Name)
153+
if !ok {
154+
log.V(5).Info("boost object not found")
155+
return nil
156+
}
157+
if err := boost.UpdateFromSpec(ctx, spec); err != nil {
158+
return err
159+
}
160+
log.Info("boost updated successfully")
161+
return nil
162+
}
163+
144164
// StartupCPUBoost returns a startup-cpu-boost with a given name and namespace
145165
// if registered in a manager.
146166
func (m *managerImpl) StartupCPUBoost(namespace string, name string) (StartupCPUBoost, bool) {

Diff for: internal/boost/manager_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
autoscaling "github.com/google/kube-startup-cpu-boost/api/v1alpha1"
2222
cpuboost "github.com/google/kube-startup-cpu-boost/internal/boost"
23+
"github.com/google/kube-startup-cpu-boost/internal/boost/duration"
2324
"github.com/google/kube-startup-cpu-boost/internal/metrics"
2425
"github.com/google/kube-startup-cpu-boost/internal/mock"
2526
. "github.com/onsi/ginkgo/v2"
@@ -107,6 +108,44 @@ var _ = Describe("Manager", func() {
107108
})
108109
})
109110
})
111+
Describe("updates startup-cpu-boost from spec", func() {
112+
var (
113+
boost cpuboost.StartupCPUBoost
114+
err error
115+
spec *autoscaling.StartupCPUBoost
116+
updatedSpec *autoscaling.StartupCPUBoost
117+
)
118+
BeforeEach(func() {
119+
spec = specTemplate.DeepCopy()
120+
updatedSpec = spec.DeepCopy()
121+
updatedSpec.Spec.DurationPolicy.Fixed = &autoscaling.FixedDurationPolicy{
122+
Unit: autoscaling.FixedDurationPolicyUnitMin,
123+
Value: 1000,
124+
}
125+
})
126+
JustBeforeEach(func() {
127+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
128+
Expect(err).ToNot(HaveOccurred())
129+
})
130+
When("startup-cpu-boost is registered", func() {
131+
JustBeforeEach(func() {
132+
err = manager.AddStartupCPUBoost(context.TODO(), boost)
133+
Expect(err).ToNot(HaveOccurred())
134+
err = manager.UpdateStartupCPUBoost(context.TODO(), updatedSpec)
135+
Expect(err).ToNot(HaveOccurred())
136+
})
137+
It("updates the startup-cpu-boost", func() {
138+
boost, ok := manager.StartupCPUBoost(updatedSpec.Namespace, updatedSpec.Name)
139+
Expect(ok).To(BeTrue())
140+
durationPolicies := boost.DurationPolicies()
141+
durationPolicy, ok := durationPolicies[duration.FixedDurationPolicyName]
142+
Expect(ok).To(BeTrue())
143+
Expect(durationPolicy).To(BeAssignableToTypeOf(&duration.FixedDurationPolicy{}))
144+
fixedDurationPolicy := durationPolicy.(*duration.FixedDurationPolicy)
145+
Expect(fixedDurationPolicy.Duration()).To(Equal(1000 * time.Minute))
146+
})
147+
})
148+
})
110149
Describe("retrieves startup-cpu-boost for a POD", func() {
111150
var (
112151
pod *corev1.Pod

Diff for: internal/boost/startupcpuboost.go

+22
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type StartupCPUBoost interface {
6060
Matches(pod *corev1.Pod) bool
6161
// Stats returns the StartupCPUBoost usage statistics
6262
Stats() StartupCPUBoostStats
63+
// UpdateFromSpec updates the StartupCPUBoost from the API spec
64+
UpdateFromSpec(ctx context.Context, boost *autoscaling.StartupCPUBoost) error
6365
}
6466

6567
const (
@@ -226,6 +228,26 @@ func (b *StartupCPUBoostImpl) Stats() StartupCPUBoostStats {
226228
return b.stats
227229
}
228230

231+
// UpdateFromSpec updates the StartupCPUBoost from the API spec
232+
func (b *StartupCPUBoostImpl) UpdateFromSpec(ctx context.Context, boost *autoscaling.StartupCPUBoost) error {
233+
b.Lock()
234+
defer b.Unlock()
235+
log := b.loggerFromContext(ctx)
236+
log.V(5).Info("handling boost update from API spec")
237+
selector, err := metav1.LabelSelectorAsSelector(&boost.Selector)
238+
if err != nil {
239+
return err
240+
}
241+
resourcePolicies, err := mapResourcePolicy(boost.Spec.ResourcePolicy)
242+
if err != nil {
243+
return err
244+
}
245+
b.selector = selector
246+
b.resourcePolicies = resourcePolicies
247+
b.durationPolicies = mapDurationPolicy(boost.Spec.DurationPolicy)
248+
return nil
249+
}
250+
229251
// loggerFromContext provides Logger from a current context with configured
230252
// values common for startup-cpu-boost like name or namespace
231253
func (b *StartupCPUBoostImpl) loggerFromContext(ctx context.Context) logr.Logger {

Diff for: internal/boost/startupcpuboost_test.go

+109
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,113 @@ var _ = Describe("StartupCPUBoost", func() {
299299
})
300300
})
301301
})
302+
Describe("Updates boost from the spec", func() {
303+
var (
304+
updatedSpec *autoscaling.StartupCPUBoost
305+
)
306+
BeforeEach(func() {
307+
spec.Selector = metav1.LabelSelector{
308+
MatchLabels: map[string]string{
309+
"app": "test",
310+
},
311+
}
312+
spec.Spec.DurationPolicy.Fixed = &autoscaling.FixedDurationPolicy{
313+
Unit: autoscaling.FixedDurationPolicyUnitMin,
314+
Value: 2,
315+
}
316+
spec.Spec.DurationPolicy.PodCondition = &autoscaling.PodConditionDurationPolicy{
317+
Status: corev1.ConditionTrue,
318+
Type: corev1.PodReady,
319+
}
320+
updatedSpec = spec.DeepCopy()
321+
})
322+
JustBeforeEach(func() {
323+
boost, err = cpuboost.NewStartupCPUBoost(nil, spec)
324+
Expect(err).ShouldNot(HaveOccurred())
325+
err = boost.UpdateFromSpec(context.TODO(), updatedSpec)
326+
})
327+
When("selector is changed", func() {
328+
var (
329+
podToSelect *corev1.Pod
330+
)
331+
BeforeEach(func() {
332+
updatedSpec.Selector = metav1.LabelSelector{
333+
MatchLabels: map[string]string{
334+
"app": "newApp",
335+
},
336+
}
337+
podToSelect = &corev1.Pod{
338+
ObjectMeta: metav1.ObjectMeta{
339+
Name: "test-pod",
340+
Namespace: specTemplate.Namespace,
341+
Labels: map[string]string{
342+
"app": "newApp",
343+
}}}
344+
})
345+
It("matches pod with new selector", func() {
346+
Expect(boost.Matches(podToSelect)).To(BeTrue())
347+
})
348+
})
349+
When("duration policy is changed", func() {
350+
var (
351+
durationPolicies map[string]duration.Policy
352+
)
353+
BeforeEach(func() {
354+
updatedSpec.Spec.DurationPolicy.Fixed = &autoscaling.FixedDurationPolicy{
355+
Unit: autoscaling.FixedDurationPolicyUnitMin,
356+
Value: 1000,
357+
}
358+
updatedSpec.Spec.DurationPolicy.PodCondition = &autoscaling.PodConditionDurationPolicy{
359+
Type: corev1.PodInitialized,
360+
Status: corev1.ConditionTrue,
361+
}
362+
})
363+
JustBeforeEach(func() {
364+
durationPolicies = boost.DurationPolicies()
365+
})
366+
It("has valid fixed duration policy", func() {
367+
durationPolicy := durationPolicies[duration.FixedDurationPolicyName]
368+
Expect(durationPolicy).To(BeAssignableToTypeOf(&duration.FixedDurationPolicy{}))
369+
fixedDurationPolicy := durationPolicy.(*duration.FixedDurationPolicy)
370+
Expect(fixedDurationPolicy.Duration()).To(Equal(1000 * time.Minute))
371+
})
372+
It("has valid pod condition policy", func() {
373+
durationPolicy := durationPolicies[duration.PodConditionPolicyName]
374+
Expect(durationPolicy).To(BeAssignableToTypeOf(&duration.PodConditionPolicy{}))
375+
podConditionDurationPolicy := durationPolicy.(*duration.PodConditionPolicy)
376+
Expect(podConditionDurationPolicy.Condition()).To(Equal(corev1.PodInitialized))
377+
Expect(podConditionDurationPolicy.Status()).To(Equal(corev1.ConditionTrue))
378+
})
379+
})
380+
When("resource policy is changed", func() {
381+
var (
382+
resourcePolicy resource.ContainerPolicy
383+
resourcePolicyFound bool
384+
)
385+
BeforeEach(func() {
386+
updatedSpec.Spec.ResourcePolicy = autoscaling.ResourcePolicy{
387+
ContainerPolicies: []autoscaling.ContainerPolicy{
388+
{
389+
ContainerName: "test",
390+
PercentageIncrease: &autoscaling.PercentageIncrease{
391+
Value: 1000,
392+
},
393+
},
394+
},
395+
}
396+
397+
})
398+
JustBeforeEach(func() {
399+
resourcePolicy, resourcePolicyFound = boost.ResourcePolicy("test")
400+
})
401+
It("finds resource policy", func() {
402+
Expect(resourcePolicyFound).To(BeTrue())
403+
})
404+
It("has valid resource policy", func() {
405+
Expect(resourcePolicy).To(BeAssignableToTypeOf(&resource.PercentageContainerPolicy{}))
406+
percentagePolicy := resourcePolicy.(*resource.PercentageContainerPolicy)
407+
Expect(percentagePolicy.Percentage()).To(Equal(int64(1000)))
408+
})
409+
})
410+
})
302411
})

Diff for: internal/controller/boost_controller.go

+4
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func (r *StartupCPUBoostReconciler) Update(e event.UpdateEvent) bool {
150150
}
151151
log := r.Log.WithValues("name", boostObj.Name, "namespace", boostObj.Namespace)
152152
log.V(5).Info("handling boost update event")
153+
ctx := ctrl.LoggerInto(context.Background(), log)
154+
if err := r.Manager.UpdateStartupCPUBoost(ctx, boostObj); err != nil {
155+
log.Error(err, "boost update error")
156+
}
153157
return true
154158
}
155159

Diff for: internal/controller/boost_controller_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/apimachinery/pkg/types"
3131
ctrl "sigs.k8s.io/controller-runtime"
3232
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/event"
3334
)
3435

3536
var _ = Describe("BoostController", func() {
@@ -141,4 +142,29 @@ var _ = Describe("BoostController", func() {
141142
})
142143
})
143144
})
145+
Describe("receives update event", func() {
146+
var (
147+
updateEvent event.UpdateEvent
148+
mgrMockCall *gomock.Call
149+
)
150+
BeforeEach(func() {
151+
updateEvent = event.UpdateEvent{
152+
ObjectNew: &autoscaling.StartupCPUBoost{
153+
ObjectMeta: metav1.ObjectMeta{
154+
Name: "boost-001",
155+
Namespace: "demo",
156+
},
157+
},
158+
}
159+
mgrMockCall = mockManager.EXPECT().UpdateStartupCPUBoost(
160+
gomock.Any(), gomock.Eq(updateEvent.ObjectNew))
161+
})
162+
JustBeforeEach(func() {
163+
ok := boostCtrl.Update(updateEvent)
164+
Expect(ok).To(BeTrue())
165+
})
166+
It("calls manager with valid update", func() {
167+
mgrMockCall.Times(1)
168+
})
169+
})
144170
})

0 commit comments

Comments
 (0)