diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index 88f6b41197..9f25b47f49 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -157,7 +157,8 @@ protected

ControllerConfiguration

configFor(Reconcile Constants.NO_VALUE_SET), null, Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager, - this); + this, + null); ResourceEventFilter

answer = deprecatedEventFilter(annotation); config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough()); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java index 6d13ec2750..88a28480bd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java @@ -40,6 +40,7 @@ public class ControllerConfigurationOverrider { private ItemStore itemStore; private String name; private String fieldManager; + private Long informerListLimit; private ControllerConfigurationOverrider(ControllerConfiguration original) { this.finalizer = original.getFinalizerName(); @@ -56,6 +57,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { this.rateLimiter = original.getRateLimiter(); this.name = original.getName(); this.fieldManager = original.fieldManager(); + this.informerListLimit = original.getInformerListLimit().orElse(null); } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -176,6 +178,20 @@ public ControllerConfigurationOverrider withFieldManager( return this; } + + /** + * Sets a max page size limit when starting the informer. This will result in pagination while + * populating the cache. This means that longer lists will take multiple requests to fetch. See + * {@link io.fabric8.kubernetes.client.dsl.Informable#withLimit(Long)} for more details. + * + * @param informerListLimit null (the default) results in no pagination + */ + public ControllerConfigurationOverrider withInformerListLimit( + Long informerListLimit) { + this.informerListLimit = informerListLimit; + return this; + } + public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name, Object dependentResourceConfig) { @@ -199,7 +215,7 @@ public ControllerConfiguration build() { reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter, original.getDependentResources(), namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager, - original.getConfigurationService()); + original.getConfigurationService(), informerListLimit); overridden.setEventFilter(customResourcePredicate); return overridden; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java index 821553b2c8..87d8a6cd80 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java @@ -21,11 +21,12 @@ public class DefaultResourceConfiguration private final String labelSelector; private final Set namespaces; private final ItemStore itemStore; + private final Long informerListLimit; protected DefaultResourceConfiguration(Class resourceClass, Set namespaces, String labelSelector, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, GenericFilter genericFilter, - ItemStore itemStore) { + ItemStore itemStore, Long informerListLimit) { this.resourceClass = resourceClass; this.resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass); this.onAddFilter = onAddFilter; @@ -35,6 +36,7 @@ protected DefaultResourceConfiguration(Class resourceClass, this.namespaces = ResourceConfiguration.ensureValidNamespaces(namespaces); this.labelSelector = ResourceConfiguration.ensureValidLabelSelector(labelSelector); this.itemStore = itemStore; + this.informerListLimit = informerListLimit; } @Override @@ -76,4 +78,9 @@ public Optional> genericFilter() { public Optional> getItemStore() { return Optional.ofNullable(itemStore); } + + @Override + public Optional getInformerListLimit() { + return Optional.ofNullable(informerListLimit); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java index fd48a3ec6b..307e75080f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java @@ -46,7 +46,8 @@ public ResolvedControllerConfiguration(Class

resourceClass, ControllerConfigu other.getDependentResources(), other.getNamespaces(), other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(), other.getItemStore().orElse(null), other.fieldManager(), - other.getConfigurationService()); + other.getConfigurationService(), + other.getInformerListLimit().orElse(null)); } public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) { @@ -75,11 +76,11 @@ public ResolvedControllerConfiguration(Class

resourceClass, String name, Set namespaces, String finalizer, String labelSelector, Map configurations, ItemStore

itemStore, String fieldManager, - ConfigurationService configurationService) { + ConfigurationService configurationService, Long informerListLimit) { this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter, maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter, namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager, - configurationService); + configurationService, informerListLimit); setDependentResources(dependentResources); } @@ -91,9 +92,9 @@ protected ResolvedControllerConfiguration(Class

resourceClass, String name, Set namespaces, String finalizer, String labelSelector, Map configurations, ItemStore

itemStore, String fieldManager, - ConfigurationService configurationService) { + ConfigurationService configurationService, Long informerListLimit) { super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter, - itemStore); + itemStore, informerListLimit); this.configurationService = configurationService; this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName); this.generationAware = generationAware; @@ -112,7 +113,7 @@ protected ResolvedControllerConfiguration(Class

resourceClass, String name, Class reconcilerClas, ConfigurationService configurationService) { this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null, null, null, null, null, null, - null, null, null, null, null, configurationService); + null, null, null, null, null, configurationService, null); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java index 34b05a4573..d94504f50f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java @@ -145,4 +145,12 @@ default Set getEffectiveNamespaces(ConfigurationService configurationSer default Optional> getItemStore() { return Optional.empty(); } + + /** + * The maximum amount of items to return for a single list call when starting an informer. If this + * is a not null it will result in paginating for the initial load of the informer cache. + */ + default Optional getInformerListLimit() { + return Optional.empty(); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java index 3ca680cf7f..1f40677ee7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java @@ -40,9 +40,9 @@ protected DefaultInformerConfiguration(String labelSelector, OnUpdateFilter onUpdateFilter, OnDeleteFilter onDeleteFilter, GenericFilter genericFilter, - ItemStore itemStore) { + ItemStore itemStore, Long informerListLimit) { super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter, - itemStore); + itemStore, informerListLimit); this.followControllerNamespaceChanges = followControllerNamespaceChanges; this.primaryToSecondaryMapper = primaryToSecondaryMapper; @@ -119,6 +119,7 @@ class InformerConfigurationBuilder { private GenericFilter genericFilter; private boolean inheritControllerNamespacesOnChange = false; private ItemStore itemStore; + private Long informerListLimit; private InformerConfigurationBuilder(Class resourceClass) { this.resourceClass = resourceClass; @@ -226,12 +227,24 @@ public InformerConfigurationBuilder withItemStore(ItemStore itemStore) { return this; } + /** + * Sets a max page size limit when starting the informer. This will result in pagination while + * populating the cache. This means that longer lists will take multiple requests to fetch. See + * {@link io.fabric8.kubernetes.client.dsl.Informable#withLimit(Long)} for more details. + * + * @param informerListLimit null (the default) results in no pagination + */ + public InformerConfigurationBuilder withInformerListLimit(Long informerListLimit) { + this.informerListLimit = informerListLimit; + return this; + } + public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, primaryToSecondaryMapper, secondaryToPrimaryMapper, namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter, - onDeleteFilter, genericFilter, itemStore); + onDeleteFilter, genericFilter, itemStore, informerListLimit); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java index a1b58b8c66..88f1e51942 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java @@ -128,7 +128,8 @@ private InformerWrapper createEventSourceForNamespace(String namespace) { private InformerWrapper createEventSource( FilterWatchListDeletable, Resource> filteredBySelectorClient, ResourceEventHandler eventHandler, String namespaceIdentifier) { - var informer = filteredBySelectorClient.runnableInformer(0); + var informer = configuration.getInformerListLimit().map(filteredBySelectorClient::withLimit) + .orElse(filteredBySelectorClient).runnableInformer(0); configuration.getItemStore().ifPresent(informer::itemStore); var source = new InformerWrapper<>(informer, configurationService, namespaceIdentifier); source.addEventHandler(eventHandler); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java index 78e1b0c789..eebf7a72d4 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java @@ -12,13 +12,7 @@ import io.fabric8.kubernetes.api.model.authorization.v1.SubjectRulesReviewStatus; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.V1ApiextensionAPIGroupDSL; -import io.fabric8.kubernetes.client.dsl.AnyNamespaceOperation; -import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL; -import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; -import io.fabric8.kubernetes.client.dsl.MixedOperation; -import io.fabric8.kubernetes.client.dsl.NamespaceableResource; -import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; -import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.*; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectorBuilder; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.cache.Indexer; @@ -76,8 +70,15 @@ public static KubernetesClient client(Class clazz, } doAnswer(invocation -> null).when(informer).stop(); Indexer mockIndexer = mock(Indexer.class); + when(informer.getIndexer()).thenReturn(mockIndexer); + when(filterable.runnableInformer(anyLong())).thenReturn(informer); + + Informable informable = mock(Informable.class); + when(filterable.withLimit(anyLong())).thenReturn(informable); + when(informable.runnableInformer(anyLong())).thenReturn(informer); + when(client.resources(clazz)).thenReturn(resources); when(client.leaderElector()) .thenReturn(new LeaderElectorBuilder(client, Executors.newSingleThreadExecutor())); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java index 003b623b5d..c8ec839b59 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java @@ -145,7 +145,7 @@ public ControllerConfig(String finalizer, boolean generationAware, null, null, null, - null, null, null, finalizer, null, null, null, new BaseConfigurationService()); + null, null, null, finalizer, null, null, null, new BaseConfigurationService(), null); setEventFilter(eventFilter); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index 6ee3d19d6c..87ec3f9eba 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -198,7 +198,8 @@ public TestConfiguration(boolean generationAware, OnAddFilter