Skip to content

Commit 439a7b5

Browse files
committed
CNTRLPLANE-1616: add event-ttl config observer
This updates the API to latest main and adds the logic to set the event-ttl accoringly to the newly introduced API field. Note to the reviewer, this has been largely AI generated by Cursor. Signed-off-by: Thomas Jungblut <tjungblu@redhat.com>
1 parent adc82f6 commit 439a7b5

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package apiserver
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openshift/api/features"
7+
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation"
8+
"github.com/openshift/library-go/pkg/operator/configobserver"
9+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
10+
"github.com/openshift/library-go/pkg/operator/events"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
)
13+
14+
var eventTTLPath = []string{"apiServerArguments", "event-ttl"}
15+
16+
// NewObserveEventTTL returns a config observation function that observes
17+
// the EventTTLMinutes field from the KubeAPIServer operator CRD
18+
func NewObserveEventTTL(featureGateAccessor featuregates.FeatureGateAccess) configobserver.ObserveConfigFunc {
19+
return (&eventTTLObserver{
20+
featureGateAccessor: featureGateAccessor,
21+
}).ObserveEventTTL
22+
}
23+
24+
type eventTTLObserver struct {
25+
featureGateAccessor featuregates.FeatureGateAccess
26+
}
27+
28+
// ObserveEventTTL reads the eventTTLMinutes from the KubeAPIServer operator CRD
29+
func (o *eventTTLObserver) ObserveEventTTL(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, errs []error) {
30+
defer func() {
31+
// Prune the observed config to only include the event-ttl path
32+
ret = configobserver.Pruned(ret, eventTTLPath)
33+
}()
34+
35+
if !o.featureGateAccessor.AreInitialFeatureGatesObserved() {
36+
// if we haven't observed featuregates yet, return the existing
37+
return existingConfig, nil
38+
}
39+
40+
featureGates, err := o.featureGateAccessor.CurrentFeatureGates()
41+
if err != nil {
42+
return existingConfig, []error{err}
43+
}
44+
45+
if !featureGates.Enabled(features.FeatureEventTTL) {
46+
// Feature disabled: return no opinion so any previously observed value is removed.
47+
// Pruning in defer will ensure only the relevant path is considered.
48+
return map[string]interface{}{}, nil
49+
}
50+
51+
kubeAPIServer, err := genericListers.(configobservation.Listers).KubeAPIServerOperatorLister().Get("cluster")
52+
if err != nil {
53+
return existingConfig, []error{err}
54+
}
55+
56+
// Determine the event TTL value to use
57+
var eventTTLValue string
58+
if kubeAPIServer.Spec.EventTTLMinutes > 0 {
59+
observedConfig := map[string]interface{}{}
60+
// Use the specified value, convert minutes to duration string (e.g., "180m" for 180 minutes)
61+
eventTTLValue = fmt.Sprintf("%dm", kubeAPIServer.Spec.EventTTLMinutes)
62+
if err := unstructured.SetNestedStringSlice(observedConfig, []string{eventTTLValue}, eventTTLPath...); err != nil {
63+
return existingConfig, []error{err}
64+
}
65+
return observedConfig, nil
66+
}
67+
68+
// Use default value from the defaultconfig.yaml when EventTTLMinutes is 0 or not set
69+
return map[string]interface{}{}, nil
70+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package apiserver
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/go-cmp/cmp"
8+
configv1 "github.com/openshift/api/config/v1"
9+
"github.com/openshift/api/features"
10+
operatorv1 "github.com/openshift/api/operator/v1"
11+
operatorlistersv1 "github.com/openshift/client-go/operator/listers/operator/v1"
12+
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation"
13+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
14+
"github.com/openshift/library-go/pkg/operator/events"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/client-go/tools/cache"
17+
clocktesting "k8s.io/utils/clock/testing"
18+
)
19+
20+
func TestObserveEventTTL(t *testing.T) {
21+
scenarios := []struct {
22+
name string
23+
existingKubeAPIConfig map[string]interface{}
24+
expectedKubeAPIConfig map[string]interface{}
25+
eventTTLMinutes int32
26+
featureOn bool
27+
}{
28+
{
29+
name: "feature gate disabled",
30+
existingKubeAPIConfig: map[string]interface{}{},
31+
expectedKubeAPIConfig: map[string]interface{}{},
32+
eventTTLMinutes: 120,
33+
featureOn: false,
34+
},
35+
{
36+
name: "feature gate disabled clears existing event-ttl",
37+
existingKubeAPIConfig: map[string]interface{}{
38+
"apiServerArguments": map[string]interface{}{
39+
"event-ttl": []interface{}{"120m"},
40+
},
41+
},
42+
expectedKubeAPIConfig: map[string]interface{}{},
43+
eventTTLMinutes: 0,
44+
featureOn: false,
45+
},
46+
{
47+
name: "feature gate enabled, no event TTL set - use default from defaultconfig.yaml",
48+
existingKubeAPIConfig: map[string]interface{}{},
49+
expectedKubeAPIConfig: map[string]interface{}{},
50+
eventTTLMinutes: 0,
51+
featureOn: true,
52+
},
53+
{
54+
name: "feature gate enabled, event TTL set to 60 minutes",
55+
existingKubeAPIConfig: map[string]interface{}{},
56+
expectedKubeAPIConfig: map[string]interface{}{
57+
"apiServerArguments": map[string]interface{}{
58+
"event-ttl": []interface{}{"60m"},
59+
},
60+
},
61+
eventTTLMinutes: 60,
62+
featureOn: true,
63+
},
64+
{
65+
name: "feature gate enabled, event TTL set to 180 minutes (maximum)",
66+
existingKubeAPIConfig: map[string]interface{}{},
67+
expectedKubeAPIConfig: map[string]interface{}{
68+
"apiServerArguments": map[string]interface{}{
69+
"event-ttl": []interface{}{"180m"},
70+
},
71+
},
72+
eventTTLMinutes: 180,
73+
featureOn: true,
74+
},
75+
{
76+
name: "feature gate enabled, event TTL set to 5 minutes (minimum)",
77+
existingKubeAPIConfig: map[string]interface{}{},
78+
expectedKubeAPIConfig: map[string]interface{}{
79+
"apiServerArguments": map[string]interface{}{
80+
"event-ttl": []interface{}{"5m"},
81+
},
82+
},
83+
eventTTLMinutes: 5,
84+
featureOn: true,
85+
},
86+
{
87+
name: "feature gate enabled, update existing config",
88+
existingKubeAPIConfig: map[string]interface{}{
89+
"apiServerArguments": map[string]interface{}{
90+
"event-ttl": []interface{}{"120m"},
91+
},
92+
},
93+
expectedKubeAPIConfig: map[string]interface{}{
94+
"apiServerArguments": map[string]interface{}{
95+
"event-ttl": []interface{}{"90m"},
96+
},
97+
},
98+
eventTTLMinutes: 90,
99+
featureOn: true,
100+
},
101+
{
102+
name: "feature gate enabled, no change needed",
103+
existingKubeAPIConfig: map[string]interface{}{
104+
"apiServerArguments": map[string]interface{}{
105+
"event-ttl": []interface{}{"120m"},
106+
},
107+
},
108+
expectedKubeAPIConfig: map[string]interface{}{
109+
"apiServerArguments": map[string]interface{}{
110+
"event-ttl": []interface{}{"120m"},
111+
},
112+
},
113+
eventTTLMinutes: 120,
114+
featureOn: true,
115+
},
116+
{
117+
name: "feature gate enabled, set default event-ttl when set to 0",
118+
existingKubeAPIConfig: map[string]interface{}{
119+
"apiServerArguments": map[string]interface{}{
120+
"event-ttl": []interface{}{"120m"},
121+
},
122+
},
123+
expectedKubeAPIConfig: map[string]interface{}{},
124+
eventTTLMinutes: 0,
125+
featureOn: true,
126+
},
127+
{
128+
name: "feature gate enabled, no change needed when already at default, returning empty",
129+
existingKubeAPIConfig: map[string]interface{}{
130+
"apiServerArguments": map[string]interface{}{
131+
"event-ttl": []interface{}{"3h"},
132+
},
133+
},
134+
expectedKubeAPIConfig: map[string]interface{}{},
135+
eventTTLMinutes: 0,
136+
featureOn: true,
137+
},
138+
}
139+
140+
for _, scenario := range scenarios {
141+
t.Run(scenario.name, func(t *testing.T) {
142+
// test data
143+
eventRecorder := events.NewInMemoryRecorder("", clocktesting.NewFakePassiveClock(time.Now()))
144+
kubeAPIServerIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
145+
146+
// Add KubeAPIServer resource
147+
_ = kubeAPIServerIndexer.Add(&operatorv1.KubeAPIServer{
148+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
149+
Spec: operatorv1.KubeAPIServerSpec{
150+
EventTTLMinutes: scenario.eventTTLMinutes,
151+
},
152+
})
153+
154+
listers := configobservation.Listers{
155+
KubeAPIServerOperatorLister_: operatorlistersv1.NewKubeAPIServerLister(kubeAPIServerIndexer),
156+
}
157+
158+
// Set up feature gate accessor
159+
var fg featuregates.FeatureGateAccess
160+
if scenario.featureOn {
161+
fg = featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{features.FeatureEventTTL}, []configv1.FeatureGateName{})
162+
} else {
163+
fg = featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{}, []configv1.FeatureGateName{features.FeatureEventTTL})
164+
}
165+
166+
observer := NewObserveEventTTL(fg)
167+
observedKubeAPIConfig, errs := observer(listers, eventRecorder, scenario.existingKubeAPIConfig)
168+
169+
if len(errs) > 0 {
170+
t.Fatalf("unexpected errors: %v", errs)
171+
}
172+
if diff := cmp.Diff(scenario.expectedKubeAPIConfig, observedKubeAPIConfig); diff != "" {
173+
t.Fatalf("unexpected configuration, diff = %s", diff)
174+
}
175+
})
176+
}
177+
}

pkg/operator/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func NewConfigObserver(operatorClient v1helpers.StaticPodOperatorClient, kubeInf
9797
ResourceSync: resourceSyncer,
9898
PreRunCachesSynced: append(preRunCacheSynced,
9999
operatorClient.Informer().HasSynced,
100+
operatorInformer.Operator().V1().KubeAPIServers().Informer().HasSynced,
100101

101102
kubeInformersForNamespaces.InformersFor("openshift-etcd").Core().V1().ConfigMaps().Informer().HasSynced,
102103
kubeInformersForNamespaces.InformersFor(operatorclient.TargetNamespace).Core().V1().Secrets().Informer().HasSynced,
@@ -125,6 +126,7 @@ func NewConfigObserver(operatorClient v1helpers.StaticPodOperatorClient, kubeInf
125126
apiserver.ObserveSendRetryAfterWhileNotReadyOnce,
126127
apiserver.ObserveGoawayChance,
127128
apiserver.ObserveAdmissionPlugins,
129+
apiserver.NewObserveEventTTL(featureGateAccessor),
128130
libgoapiserver.ObserveTLSSecurityProfile,
129131
auth.ObserveAuthMetadata,
130132
auth.ObserveServiceAccountIssuer,

0 commit comments

Comments
 (0)