Skip to content

Commit b1e5066

Browse files
authored
feat: Allow configuration of informer pagination through list limit (#1976)
1 parent 86b676b commit b1e5066

File tree

10 files changed

+71
-22
lines changed

10 files changed

+71
-22
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconcile
157157
Constants.NO_VALUE_SET),
158158
null,
159159
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
160-
this);
160+
this,
161+
null);
161162

162163
ResourceEventFilter<P> answer = deprecatedEventFilter(annotation);
163164
config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough());

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
4040
private ItemStore<R> itemStore;
4141
private String name;
4242
private String fieldManager;
43+
private Long informerListLimit;
4344

4445
private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
4546
this.finalizer = original.getFinalizerName();
@@ -56,6 +57,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
5657
this.rateLimiter = original.getRateLimiter();
5758
this.name = original.getName();
5859
this.fieldManager = original.fieldManager();
60+
this.informerListLimit = original.getInformerListLimit().orElse(null);
5961
}
6062

6163
public ControllerConfigurationOverrider<R> withFinalizer(String finalizer) {
@@ -176,6 +178,20 @@ public ControllerConfigurationOverrider<R> withFieldManager(
176178
return this;
177179
}
178180

181+
182+
/**
183+
* Sets a max page size limit when starting the informer. This will result in pagination while
184+
* populating the cache. This means that longer lists will take multiple requests to fetch. See
185+
* {@link io.fabric8.kubernetes.client.dsl.Informable#withLimit(Long)} for more details.
186+
*
187+
* @param informerListLimit null (the default) results in no pagination
188+
*/
189+
public ControllerConfigurationOverrider<R> withInformerListLimit(
190+
Long informerListLimit) {
191+
this.informerListLimit = informerListLimit;
192+
return this;
193+
}
194+
179195
public ControllerConfigurationOverrider<R> replacingNamedDependentResourceConfig(String name,
180196
Object dependentResourceConfig) {
181197

@@ -199,7 +215,7 @@ public ControllerConfiguration<R> build() {
199215
reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter,
200216
original.getDependentResources(),
201217
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
202-
original.getConfigurationService());
218+
original.getConfigurationService(), informerListLimit);
203219
overridden.setEventFilter(customResourcePredicate);
204220
return overridden;
205221
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ public class DefaultResourceConfiguration<R extends HasMetadata>
2121
private final String labelSelector;
2222
private final Set<String> namespaces;
2323
private final ItemStore<R> itemStore;
24+
private final Long informerListLimit;
2425

2526
protected DefaultResourceConfiguration(Class<R> resourceClass,
2627
Set<String> namespaces, String labelSelector, OnAddFilter<? super R> onAddFilter,
2728
OnUpdateFilter<? super R> onUpdateFilter, GenericFilter<? super R> genericFilter,
28-
ItemStore<R> itemStore) {
29+
ItemStore<R> itemStore, Long informerListLimit) {
2930
this.resourceClass = resourceClass;
3031
this.resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass);
3132
this.onAddFilter = onAddFilter;
@@ -35,6 +36,7 @@ protected DefaultResourceConfiguration(Class<R> resourceClass,
3536
this.namespaces = ResourceConfiguration.ensureValidNamespaces(namespaces);
3637
this.labelSelector = ResourceConfiguration.ensureValidLabelSelector(labelSelector);
3738
this.itemStore = itemStore;
39+
this.informerListLimit = informerListLimit;
3840
}
3941

4042
@Override
@@ -76,4 +78,9 @@ public Optional<GenericFilter<? super R>> genericFilter() {
7678
public Optional<ItemStore<R>> getItemStore() {
7779
return Optional.ofNullable(itemStore);
7880
}
81+
82+
@Override
83+
public Optional<Long> getInformerListLimit() {
84+
return Optional.ofNullable(informerListLimit);
85+
}
7986
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, ControllerConfigu
4646
other.getDependentResources(), other.getNamespaces(),
4747
other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(),
4848
other.getItemStore().orElse(null), other.fieldManager(),
49-
other.getConfigurationService());
49+
other.getConfigurationService(),
50+
other.getInformerListLimit().orElse(null));
5051
}
5152

5253
public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
@@ -75,11 +76,11 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, String name,
7576
Set<String> namespaces, String finalizer, String labelSelector,
7677
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
7778
String fieldManager,
78-
ConfigurationService configurationService) {
79+
ConfigurationService configurationService, Long informerListLimit) {
7980
this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
8081
maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter,
8182
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
82-
configurationService);
83+
configurationService, informerListLimit);
8384
setDependentResources(dependentResources);
8485
}
8586

@@ -91,9 +92,9 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
9192
Set<String> namespaces, String finalizer, String labelSelector,
9293
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
9394
String fieldManager,
94-
ConfigurationService configurationService) {
95+
ConfigurationService configurationService, Long informerListLimit) {
9596
super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
96-
itemStore);
97+
itemStore, informerListLimit);
9798
this.configurationService = configurationService;
9899
this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName);
99100
this.generationAware = generationAware;
@@ -112,7 +113,7 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
112113
Class<? extends Reconciler> reconcilerClas, ConfigurationService configurationService) {
113114
this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
114115
null, null, null, null, null,
115-
null, null, null, null, null, configurationService);
116+
null, null, null, null, null, configurationService, null);
116117
}
117118

118119
@Override

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java

+8
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,12 @@ default Set<String> getEffectiveNamespaces(ConfigurationService configurationSer
145145
default Optional<ItemStore<R>> getItemStore() {
146146
return Optional.empty();
147147
}
148+
149+
/**
150+
* The maximum amount of items to return for a single list call when starting an informer. If this
151+
* is a not null it will result in paginating for the initial load of the informer cache.
152+
*/
153+
default Optional<Long> getInformerListLimit() {
154+
return Optional.empty();
155+
}
148156
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ protected DefaultInformerConfiguration(String labelSelector,
4040
OnUpdateFilter<? super R> onUpdateFilter,
4141
OnDeleteFilter<? super R> onDeleteFilter,
4242
GenericFilter<? super R> genericFilter,
43-
ItemStore<R> itemStore) {
43+
ItemStore<R> itemStore, Long informerListLimit) {
4444
super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
45-
itemStore);
45+
itemStore, informerListLimit);
4646
this.followControllerNamespaceChanges = followControllerNamespaceChanges;
4747

4848
this.primaryToSecondaryMapper = primaryToSecondaryMapper;
@@ -119,6 +119,7 @@ class InformerConfigurationBuilder<R extends HasMetadata> {
119119
private GenericFilter<? super R> genericFilter;
120120
private boolean inheritControllerNamespacesOnChange = false;
121121
private ItemStore<R> itemStore;
122+
private Long informerListLimit;
122123

123124
private InformerConfigurationBuilder(Class<R> resourceClass) {
124125
this.resourceClass = resourceClass;
@@ -226,12 +227,24 @@ public InformerConfigurationBuilder<R> withItemStore(ItemStore<R> itemStore) {
226227
return this;
227228
}
228229

230+
/**
231+
* Sets a max page size limit when starting the informer. This will result in pagination while
232+
* populating the cache. This means that longer lists will take multiple requests to fetch. See
233+
* {@link io.fabric8.kubernetes.client.dsl.Informable#withLimit(Long)} for more details.
234+
*
235+
* @param informerListLimit null (the default) results in no pagination
236+
*/
237+
public InformerConfigurationBuilder<R> withInformerListLimit(Long informerListLimit) {
238+
this.informerListLimit = informerListLimit;
239+
return this;
240+
}
241+
229242
public InformerConfiguration<R> build() {
230243
return new DefaultInformerConfiguration<>(labelSelector, resourceClass,
231244
primaryToSecondaryMapper,
232245
secondaryToPrimaryMapper,
233246
namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter,
234-
onDeleteFilter, genericFilter, itemStore);
247+
onDeleteFilter, genericFilter, itemStore, informerListLimit);
235248
}
236249
}
237250

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ private InformerWrapper<T> createEventSourceForNamespace(String namespace) {
128128
private InformerWrapper<T> createEventSource(
129129
FilterWatchListDeletable<T, KubernetesResourceList<T>, Resource<T>> filteredBySelectorClient,
130130
ResourceEventHandler<T> eventHandler, String namespaceIdentifier) {
131-
var informer = filteredBySelectorClient.runnableInformer(0);
131+
var informer = configuration.getInformerListLimit().map(filteredBySelectorClient::withLimit)
132+
.orElse(filteredBySelectorClient).runnableInformer(0);
132133
configuration.getItemStore().ifPresent(informer::itemStore);
133134
var source = new InformerWrapper<>(informer, configurationService, namespaceIdentifier);
134135
source.addEventHandler(eventHandler);

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java

+8-7
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@
1212
import io.fabric8.kubernetes.api.model.authorization.v1.SubjectRulesReviewStatus;
1313
import io.fabric8.kubernetes.client.KubernetesClient;
1414
import io.fabric8.kubernetes.client.V1ApiextensionAPIGroupDSL;
15-
import io.fabric8.kubernetes.client.dsl.AnyNamespaceOperation;
16-
import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL;
17-
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
18-
import io.fabric8.kubernetes.client.dsl.MixedOperation;
19-
import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
20-
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
21-
import io.fabric8.kubernetes.client.dsl.Resource;
15+
import io.fabric8.kubernetes.client.dsl.*;
2216
import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectorBuilder;
2317
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
2418
import io.fabric8.kubernetes.client.informers.cache.Indexer;
@@ -76,8 +70,15 @@ public static <T extends HasMetadata> KubernetesClient client(Class<T> clazz,
7670
}
7771
doAnswer(invocation -> null).when(informer).stop();
7872
Indexer mockIndexer = mock(Indexer.class);
73+
7974
when(informer.getIndexer()).thenReturn(mockIndexer);
75+
8076
when(filterable.runnableInformer(anyLong())).thenReturn(informer);
77+
78+
Informable<T> informable = mock(Informable.class);
79+
when(filterable.withLimit(anyLong())).thenReturn(informable);
80+
when(informable.runnableInformer(anyLong())).thenReturn(informer);
81+
8182
when(client.resources(clazz)).thenReturn(resources);
8283
when(client.leaderElector())
8384
.thenReturn(new LeaderElectorBuilder(client, Executors.newSingleThreadExecutor()));

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public ControllerConfig(String finalizer, boolean generationAware,
145145
null,
146146
null,
147147
null,
148-
null, null, null, finalizer, null, null, null, new BaseConfigurationService());
148+
null, null, null, finalizer, null, null, null, new BaseConfigurationService(), null);
149149
setEventFilter(eventFilter);
150150
}
151151
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ public TestConfiguration(boolean generationAware, OnAddFilter<TestCustomResource
198198
null,
199199
null,
200200
FINALIZER,
201-
null, null, null, new BaseConfigurationService());
201+
null, null, null, new BaseConfigurationService(),
202+
null);
202203
}
203204
}
204205
}

0 commit comments

Comments
 (0)