Skip to content

Commit cdbb21d

Browse files
csvirimetacosm
authored andcommitted
feat: feature flag to not clone secondary resource on access (#2364)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 8f123b3 commit cdbb21d

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;
@@ -40,6 +41,7 @@ public class ConfigurationServiceOverrider {
4041
private Boolean previousAnnotationForDependentResources;
4142
private Boolean parseResourceVersions;
4243
private Boolean useSSAToPatchPrimaryResource;
44+
private Boolean cloneSecondaryResourcesWhenGettingFromCache;
4345
@SuppressWarnings("rawtypes")
4446
private DependentResourceFactory dependentResourceFactory;
4547

@@ -182,30 +184,43 @@ public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean va
182184
return this;
183185
}
184186

187+
public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromCache(
188+
boolean value) {
189+
this.cloneSecondaryResourcesWhenGettingFromCache = value;
190+
return this;
191+
}
192+
185193
public ConfigurationService build() {
186194
return new BaseConfigurationService(original.getVersion(), cloner, client) {
187195
@Override
188196
public Set<String> getKnownReconcilerNames() {
189197
return original.getKnownReconcilerNames();
190198
}
191199

200+
private <T> T overriddenValueOrDefault(T value,
201+
Function<ConfigurationService, T> defaultValue) {
202+
return value != null ? value : defaultValue.apply(original);
203+
}
204+
192205
@Override
193206
public boolean checkCRDAndValidateLocalModel() {
194-
return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel();
207+
return overriddenValueOrDefault(checkCR,
208+
ConfigurationService::checkCRDAndValidateLocalModel);
195209
}
196210

197211
@SuppressWarnings("rawtypes")
198212
@Override
213+
@SuppressWarnings("rawtypes")
199214
public DependentResourceFactory dependentResourceFactory() {
200-
return dependentResourceFactory != null ? dependentResourceFactory
201-
: DependentResourceFactory.DEFAULT;
215+
return overriddenValueOrDefault(dependentResourceFactory,
216+
ConfigurationService::dependentResourceFactory);
202217
}
203218

204219
@Override
205220
public int concurrentReconciliationThreads() {
206221
return Utils.ensureValid(
207-
concurrentReconciliationThreads != null ? concurrentReconciliationThreads
208-
: original.concurrentReconciliationThreads(),
222+
overriddenValueOrDefault(concurrentReconciliationThreads,
223+
ConfigurationService::concurrentReconciliationThreads),
209224
"maximum reconciliation threads",
210225
minimumMaxValueFor(minConcurrentReconciliationThreads),
211226
original.concurrentReconciliationThreads());
@@ -214,8 +229,8 @@ public int concurrentReconciliationThreads() {
214229
@Override
215230
public int concurrentWorkflowExecutorThreads() {
216231
return Utils.ensureValid(
217-
concurrentWorkflowExecutorThreads != null ? concurrentWorkflowExecutorThreads
218-
: original.concurrentWorkflowExecutorThreads(),
232+
overriddenValueOrDefault(concurrentWorkflowExecutorThreads,
233+
ConfigurationService::concurrentWorkflowExecutorThreads),
219234
"maximum workflow execution threads",
220235
minimumMaxValueFor(minConcurrentWorkflowExecutorThreads),
221236
original.concurrentWorkflowExecutorThreads());
@@ -227,8 +242,8 @@ public int concurrentWorkflowExecutorThreads() {
227242
@Deprecated(forRemoval = true)
228243
@Override
229244
public int minConcurrentReconciliationThreads() {
230-
return minConcurrentReconciliationThreads != null ? minConcurrentReconciliationThreads
231-
: original.minConcurrentReconciliationThreads();
245+
return overriddenValueOrDefault(minConcurrentReconciliationThreads,
246+
ConfigurationService::minConcurrentReconciliationThreads);
232247
}
233248

234249
/**
@@ -237,30 +252,29 @@ public int minConcurrentReconciliationThreads() {
237252
@Override
238253
@Deprecated(forRemoval = true)
239254
public int minConcurrentWorkflowExecutorThreads() {
240-
return minConcurrentWorkflowExecutorThreads != null ? minConcurrentWorkflowExecutorThreads
241-
: original.minConcurrentWorkflowExecutorThreads();
255+
return overriddenValueOrDefault(minConcurrentWorkflowExecutorThreads,
256+
ConfigurationService::minConcurrentWorkflowExecutorThreads);
242257
}
243258

244259
@Override
245260
public Metrics getMetrics() {
246-
return metrics != null ? metrics : original.getMetrics();
261+
return overriddenValueOrDefault(metrics, ConfigurationService::getMetrics);
247262
}
248263

249264
@Override
250265
public boolean closeClientOnStop() {
251-
return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop();
266+
return overriddenValueOrDefault(closeClientOnStop, ConfigurationService::closeClientOnStop);
252267
}
253268

254269
@Override
255270
public ExecutorService getExecutorService() {
256-
return executorService != null ? executorService
257-
: super.getExecutorService();
271+
return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService);
258272
}
259273

260274
@Override
261275
public ExecutorService getWorkflowExecutorService() {
262-
return workflowExecutorService != null ? workflowExecutorService
263-
: super.getWorkflowExecutorService();
276+
return overriddenValueOrDefault(workflowExecutorService,
277+
ConfigurationService::getWorkflowExecutorService);
264278
}
265279

266280
@Override
@@ -277,54 +291,55 @@ public Optional<InformerStoppedHandler> getInformerStoppedHandler() {
277291

278292
@Override
279293
public boolean stopOnInformerErrorDuringStartup() {
280-
return stopOnInformerErrorDuringStartup != null ? stopOnInformerErrorDuringStartup
281-
: super.stopOnInformerErrorDuringStartup();
294+
return overriddenValueOrDefault(stopOnInformerErrorDuringStartup,
295+
ConfigurationService::stopOnInformerErrorDuringStartup);
282296
}
283297

284298
@Override
285299
public Duration cacheSyncTimeout() {
286-
return cacheSyncTimeout != null ? cacheSyncTimeout : super.cacheSyncTimeout();
300+
return overriddenValueOrDefault(cacheSyncTimeout, ConfigurationService::cacheSyncTimeout);
287301
}
288302

289303
@Override
290304
public ResourceClassResolver getResourceClassResolver() {
291-
return resourceClassResolver != null ? resourceClassResolver
292-
: super.getResourceClassResolver();
305+
return overriddenValueOrDefault(resourceClassResolver,
306+
ConfigurationService::getResourceClassResolver);
293307
}
294308

295309
@Override
296310
public boolean ssaBasedCreateUpdateMatchForDependentResources() {
297-
return ssaBasedCreateUpdateMatchForDependentResources != null
298-
? ssaBasedCreateUpdateMatchForDependentResources
299-
: super.ssaBasedCreateUpdateMatchForDependentResources();
311+
return overriddenValueOrDefault(ssaBasedCreateUpdateMatchForDependentResources,
312+
ConfigurationService::ssaBasedCreateUpdateMatchForDependentResources);
300313
}
301314

302315
@Override
303316
public Set<Class<? extends HasMetadata>> defaultNonSSAResources() {
304-
return defaultNonSSAResource != null ? defaultNonSSAResource
305-
: super.defaultNonSSAResources();
317+
return overriddenValueOrDefault(defaultNonSSAResource,
318+
ConfigurationService::defaultNonSSAResources);
306319
}
307320

308321
@Override
309322
public boolean previousAnnotationForDependentResourcesEventFiltering() {
310-
return previousAnnotationForDependentResources != null
311-
? previousAnnotationForDependentResources
312-
: super.previousAnnotationForDependentResourcesEventFiltering();
323+
return overriddenValueOrDefault(previousAnnotationForDependentResources,
324+
ConfigurationService::previousAnnotationForDependentResourcesEventFiltering);
313325
}
314326

315327
@Override
316328
public boolean parseResourceVersionsForEventFilteringAndCaching() {
317-
return parseResourceVersions != null
318-
? parseResourceVersions
319-
: super.parseResourceVersionsForEventFilteringAndCaching();
329+
return overriddenValueOrDefault(parseResourceVersions,
330+
ConfigurationService::parseResourceVersionsForEventFilteringAndCaching);
320331
}
321332

322333
@Override
323334
public boolean useSSAToPatchPrimaryResource() {
324-
return useSSAToPatchPrimaryResource != null
325-
? useSSAToPatchPrimaryResource
326-
: super.useSSAToPatchPrimaryResource();
335+
return overriddenValueOrDefault(useSSAToPatchPrimaryResource,
336+
ConfigurationService::useSSAToPatchPrimaryResource);
337+
}
327338

339+
@Override
340+
public boolean cloneSecondaryResourcesWhenGettingFromCache() {
341+
return overriddenValueOrDefault(cloneSecondaryResourcesWhenGettingFromCache,
342+
ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache);
328343
}
329344
};
330345
}

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)