Skip to content

Commit c89ad92

Browse files
committed
feat: feature flag to not clone secondary resource on access (#2364)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 496d1b3 commit c89ad92

File tree

4 files changed

+84
-43
lines changed

4 files changed

+84
-43
lines changed

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
2121
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
2222
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
23+
import io.javaoperatorsdk.operator.api.reconciler.Context;
2324
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
2425
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
2526
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@@ -456,12 +457,36 @@ default boolean parseResourceVersionsForEventFilteringAndCaching() {
456457
* either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for
457458
* adding finalizers, managing observed generation, patching resources and status.
458459
*
459-
* @return {@code true} by default
460+
* @return {@code true} if Server-Side Apply (SSA) should be used when patching the primary
461+
* resources, {@code false} otherwise
460462
* @since 5.0.0
461463
* @see ConfigurationServiceOverrider#withUseSSAToPatchPrimaryResource(boolean)
462464
*/
463465
default boolean useSSAToPatchPrimaryResource() {
464466
return true;
465467
}
466468

469+
/**
470+
* <p>
471+
* Determines whether resources retrieved from caches such as via calls to
472+
* {@link Context#getSecondaryResource(Class)} should be defensively cloned first.
473+
* </p>
474+
*
475+
* <p>
476+
* Defensive cloning to prevent problematic cache modifications (modifying the resource would
477+
* otherwise modify the stored copy in the cache) was transparently done in previous JOSDK
478+
* versions. This might have performance consequences and, with the more prevalent use of
479+
* Server-Side Apply, where you should create a new copy of your resource with only modified
480+
* fields, such modifications of these resources are less likely to occur.
481+
* </p>
482+
*
483+
* @return {@code true} if resources should be defensively cloned before returning them from
484+
* caches, {@code false} otherwise
485+
*
486+
* @since 5.0.0
487+
*/
488+
default boolean cloneSecondaryResourcesWhenGettingFromCache() {
489+
return false;
490+
}
491+
467492
}

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

+51-36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Set;
66
import java.util.concurrent.ExecutorService;
77
import java.util.function.Consumer;
8+
import java.util.function.Function;
89

910
import org.slf4j.Logger;
1011
import org.slf4j.LoggerFactory;
@@ -41,6 +42,7 @@ public class ConfigurationServiceOverrider {
4142
private Boolean previousAnnotationForDependentResources;
4243
private Boolean parseResourceVersions;
4344
private Boolean useSSAToPatchPrimaryResource;
45+
private Boolean cloneSecondaryResourcesWhenGettingFromCache;
4446
@SuppressWarnings("rawtypes")
4547
private DependentResourceFactory dependentResourceFactory;
4648

@@ -203,30 +205,43 @@ public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean va
203205
return this;
204206
}
205207

208+
public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromCache(
209+
boolean value) {
210+
this.cloneSecondaryResourcesWhenGettingFromCache = value;
211+
return this;
212+
}
213+
206214
public ConfigurationService build() {
207215
return new BaseConfigurationService(original.getVersion(), cloner, client) {
208216
@Override
209217
public Set<String> getKnownReconcilerNames() {
210218
return original.getKnownReconcilerNames();
211219
}
212220

221+
private <T> T overriddenValueOrDefault(T value,
222+
Function<ConfigurationService, T> defaultValue) {
223+
return value != null ? value : defaultValue.apply(original);
224+
}
225+
213226
@Override
214227
public boolean checkCRDAndValidateLocalModel() {
215-
return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel();
228+
return overriddenValueOrDefault(checkCR,
229+
ConfigurationService::checkCRDAndValidateLocalModel);
216230
}
217231

218232
@SuppressWarnings("rawtypes")
219233
@Override
234+
@SuppressWarnings("rawtypes")
220235
public DependentResourceFactory dependentResourceFactory() {
221-
return dependentResourceFactory != null ? dependentResourceFactory
222-
: DependentResourceFactory.DEFAULT;
236+
return overriddenValueOrDefault(dependentResourceFactory,
237+
ConfigurationService::dependentResourceFactory);
223238
}
224239

225240
@Override
226241
public int concurrentReconciliationThreads() {
227242
return Utils.ensureValid(
228-
concurrentReconciliationThreads != null ? concurrentReconciliationThreads
229-
: original.concurrentReconciliationThreads(),
243+
overriddenValueOrDefault(concurrentReconciliationThreads,
244+
ConfigurationService::concurrentReconciliationThreads),
230245
"maximum reconciliation threads",
231246
minimumMaxValueFor(minConcurrentReconciliationThreads),
232247
original.concurrentReconciliationThreads());
@@ -235,8 +250,8 @@ public int concurrentReconciliationThreads() {
235250
@Override
236251
public int concurrentWorkflowExecutorThreads() {
237252
return Utils.ensureValid(
238-
concurrentWorkflowExecutorThreads != null ? concurrentWorkflowExecutorThreads
239-
: original.concurrentWorkflowExecutorThreads(),
253+
overriddenValueOrDefault(concurrentWorkflowExecutorThreads,
254+
ConfigurationService::concurrentWorkflowExecutorThreads),
240255
"maximum workflow execution threads",
241256
minimumMaxValueFor(minConcurrentWorkflowExecutorThreads),
242257
original.concurrentWorkflowExecutorThreads());
@@ -248,8 +263,8 @@ public int concurrentWorkflowExecutorThreads() {
248263
@Deprecated(forRemoval = true)
249264
@Override
250265
public int minConcurrentReconciliationThreads() {
251-
return minConcurrentReconciliationThreads != null ? minConcurrentReconciliationThreads
252-
: original.minConcurrentReconciliationThreads();
266+
return overriddenValueOrDefault(minConcurrentReconciliationThreads,
267+
ConfigurationService::minConcurrentReconciliationThreads);
253268
}
254269

255270
/**
@@ -258,30 +273,29 @@ public int minConcurrentReconciliationThreads() {
258273
@Override
259274
@Deprecated(forRemoval = true)
260275
public int minConcurrentWorkflowExecutorThreads() {
261-
return minConcurrentWorkflowExecutorThreads != null ? minConcurrentWorkflowExecutorThreads
262-
: original.minConcurrentWorkflowExecutorThreads();
276+
return overriddenValueOrDefault(minConcurrentWorkflowExecutorThreads,
277+
ConfigurationService::minConcurrentWorkflowExecutorThreads);
263278
}
264279

265280
@Override
266281
public Metrics getMetrics() {
267-
return metrics != null ? metrics : original.getMetrics();
282+
return overriddenValueOrDefault(metrics, ConfigurationService::getMetrics);
268283
}
269284

270285
@Override
271286
public boolean closeClientOnStop() {
272-
return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop();
287+
return overriddenValueOrDefault(closeClientOnStop, ConfigurationService::closeClientOnStop);
273288
}
274289

275290
@Override
276291
public ExecutorService getExecutorService() {
277-
return executorService != null ? executorService
278-
: super.getExecutorService();
292+
return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService);
279293
}
280294

281295
@Override
282296
public ExecutorService getWorkflowExecutorService() {
283-
return workflowExecutorService != null ? workflowExecutorService
284-
: super.getWorkflowExecutorService();
297+
return overriddenValueOrDefault(workflowExecutorService,
298+
ConfigurationService::getWorkflowExecutorService);
285299
}
286300

287301
@Override
@@ -298,54 +312,55 @@ public Optional<InformerStoppedHandler> getInformerStoppedHandler() {
298312

299313
@Override
300314
public boolean stopOnInformerErrorDuringStartup() {
301-
return stopOnInformerErrorDuringStartup != null ? stopOnInformerErrorDuringStartup
302-
: super.stopOnInformerErrorDuringStartup();
315+
return overriddenValueOrDefault(stopOnInformerErrorDuringStartup,
316+
ConfigurationService::stopOnInformerErrorDuringStartup);
303317
}
304318

305319
@Override
306320
public Duration cacheSyncTimeout() {
307-
return cacheSyncTimeout != null ? cacheSyncTimeout : super.cacheSyncTimeout();
321+
return overriddenValueOrDefault(cacheSyncTimeout, ConfigurationService::cacheSyncTimeout);
308322
}
309323

310324
@Override
311325
public ResourceClassResolver getResourceClassResolver() {
312-
return resourceClassResolver != null ? resourceClassResolver
313-
: super.getResourceClassResolver();
326+
return overriddenValueOrDefault(resourceClassResolver,
327+
ConfigurationService::getResourceClassResolver);
314328
}
315329

316330
@Override
317331
public boolean ssaBasedCreateUpdateMatchForDependentResources() {
318-
return ssaBasedCreateUpdateMatchForDependentResources != null
319-
? ssaBasedCreateUpdateMatchForDependentResources
320-
: super.ssaBasedCreateUpdateMatchForDependentResources();
332+
return overriddenValueOrDefault(ssaBasedCreateUpdateMatchForDependentResources,
333+
ConfigurationService::ssaBasedCreateUpdateMatchForDependentResources);
321334
}
322335

323336
@Override
324337
public Set<Class<? extends HasMetadata>> defaultNonSSAResources() {
325-
return defaultNonSSAResource != null ? defaultNonSSAResource
326-
: super.defaultNonSSAResources();
338+
return overriddenValueOrDefault(defaultNonSSAResource,
339+
ConfigurationService::defaultNonSSAResources);
327340
}
328341

329342
@Override
330343
public boolean previousAnnotationForDependentResourcesEventFiltering() {
331-
return previousAnnotationForDependentResources != null
332-
? previousAnnotationForDependentResources
333-
: super.previousAnnotationForDependentResourcesEventFiltering();
344+
return overriddenValueOrDefault(previousAnnotationForDependentResources,
345+
ConfigurationService::previousAnnotationForDependentResourcesEventFiltering);
334346
}
335347

336348
@Override
337349
public boolean parseResourceVersionsForEventFilteringAndCaching() {
338-
return parseResourceVersions != null
339-
? parseResourceVersions
340-
: super.parseResourceVersionsForEventFilteringAndCaching();
350+
return overriddenValueOrDefault(parseResourceVersions,
351+
ConfigurationService::parseResourceVersionsForEventFilteringAndCaching);
341352
}
342353

343354
@Override
344355
public boolean useSSAToPatchPrimaryResource() {
345-
return useSSAToPatchPrimaryResource != null
346-
? useSSAToPatchPrimaryResource
347-
: super.useSSAToPatchPrimaryResource();
356+
return overriddenValueOrDefault(useSSAToPatchPrimaryResource,
357+
ConfigurationService::useSSAToPatchPrimaryResource);
358+
}
348359

360+
@Override
361+
public boolean cloneSecondaryResourcesWhenGettingFromCache() {
362+
return overriddenValueOrDefault(cloneSecondaryResourcesWhenGettingFromCache,
363+
ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache);
349364
}
350365
};
351366
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ public Stream<T> list(String namespace, Predicate<T> predicate) {
170170
public Optional<T> get(ResourceID resourceID) {
171171
return getSource(resourceID.getNamespace().orElse(WATCH_ALL_NAMESPACES))
172172
.flatMap(source -> source.get(resourceID))
173-
.map(r -> configurationService.getResourceCloner().clone(r));
173+
.map(r -> configurationService.cloneSecondaryResourcesWhenGettingFromCache()
174+
? configurationService.getResourceCloner().clone(r)
175+
: r);
174176
}
175177

176178
@Override

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import io.fabric8.kubernetes.api.model.apps.Deployment;
1414
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
1515
import io.fabric8.kubernetes.client.KubernetesClient;
16-
import io.fabric8.kubernetes.client.dsl.Replaceable;
1716
import io.javaoperatorsdk.operator.ReconcilerUtils;
1817
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
1918
import io.javaoperatorsdk.operator.api.reconciler.*;
@@ -91,7 +90,7 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
9190
desiredHtmlConfigMap.getMetadata().getName(),
9291
ns);
9392
kubernetesClient.configMaps().inNamespace(ns).resource(desiredHtmlConfigMap)
94-
.createOr(Replaceable::update);
93+
.serverSideApply();
9594
}
9695

9796
var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null);
@@ -101,7 +100,7 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
101100
desiredDeployment.getMetadata().getName(),
102101
ns);
103102
kubernetesClient.apps().deployments().inNamespace(ns).resource(desiredDeployment)
104-
.createOr(Replaceable::update);
103+
.serverSideApply();
105104
}
106105

107106
var existingService = context.getSecondaryResource(Service.class).orElse(null);
@@ -111,14 +110,14 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
111110
desiredDeployment.getMetadata().getName(),
112111
ns);
113112
kubernetesClient.services().inNamespace(ns).resource(desiredService)
114-
.createOr(Replaceable::update);
113+
.serverSideApply();
115114
}
116115

117116
var existingIngress = context.getSecondaryResource(Ingress.class);
118117
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
119118
var desiredIngress = makeDesiredIngress(webPage);
120119
if (existingIngress.isEmpty() || !match(desiredIngress, existingIngress.get())) {
121-
kubernetesClient.resource(desiredIngress).inNamespace(ns).createOr(Replaceable::update);
120+
kubernetesClient.resource(desiredIngress).inNamespace(ns).serverSideApply();
122121
}
123122
} else
124123
existingIngress.ifPresent(

0 commit comments

Comments
 (0)