Skip to content

Commit cc0f4cd

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 f412f5f commit cc0f4cd

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;
@@ -396,12 +397,36 @@ default boolean parseResourceVersionsForEventFilteringAndCaching() {
396397
* either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for
397398
* adding finalizers, managing observed generation, patching resources and status.
398399
*
399-
* @return {@code true} by default
400+
* @return {@code true} if Server-Side Apply (SSA) should be used when patching the primary
401+
* resources, {@code false} otherwise
400402
* @since 5.0.0
401403
* @see ConfigurationServiceOverrider#withUseSSAToPatchPrimaryResource(boolean)
402404
*/
403405
default boolean useSSAToPatchPrimaryResource() {
404406
return true;
405407
}
406408

409+
/**
410+
* <p>
411+
* Determines whether resources retrieved from caches such as via calls to
412+
* {@link Context#getSecondaryResource(Class)} should be defensively cloned first.
413+
* </p>
414+
*
415+
* <p>
416+
* Defensive cloning to prevent problematic cache modifications (modifying the resource would
417+
* otherwise modify the stored copy in the cache) was transparently done in previous JOSDK
418+
* versions. This might have performance consequences and, with the more prevalent use of
419+
* Server-Side Apply, where you should create a new copy of your resource with only modified
420+
* fields, such modifications of these resources are less likely to occur.
421+
* </p>
422+
*
423+
* @return {@code true} if resources should be defensively cloned before returning them from
424+
* caches, {@code false} otherwise
425+
*
426+
* @since 5.0.0
427+
*/
428+
default boolean cloneSecondaryResourcesWhenGettingFromCache() {
429+
return false;
430+
}
431+
407432
}

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
private DependentResourceFactory dependentResourceFactory;
4446

4547
ConfigurationServiceOverrider(ConfigurationService original) {
@@ -180,29 +182,42 @@ public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean va
180182
return this;
181183
}
182184

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

198+
private <T> T overriddenValueOrDefault(T value,
199+
Function<ConfigurationService, T> defaultValue) {
200+
return value != null ? value : defaultValue.apply(original);
201+
}
202+
190203
@Override
191204
public boolean checkCRDAndValidateLocalModel() {
192-
return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel();
205+
return overriddenValueOrDefault(checkCR,
206+
ConfigurationService::checkCRDAndValidateLocalModel);
193207
}
194208

195209
@Override
210+
@SuppressWarnings("rawtypes")
196211
public DependentResourceFactory dependentResourceFactory() {
197-
return dependentResourceFactory != null ? dependentResourceFactory
198-
: DependentResourceFactory.DEFAULT;
212+
return overriddenValueOrDefault(dependentResourceFactory,
213+
ConfigurationService::dependentResourceFactory);
199214
}
200215

201216
@Override
202217
public int concurrentReconciliationThreads() {
203218
return Utils.ensureValid(
204-
concurrentReconciliationThreads != null ? concurrentReconciliationThreads
205-
: original.concurrentReconciliationThreads(),
219+
overriddenValueOrDefault(concurrentReconciliationThreads,
220+
ConfigurationService::concurrentReconciliationThreads),
206221
"maximum reconciliation threads",
207222
minimumMaxValueFor(minConcurrentReconciliationThreads),
208223
original.concurrentReconciliationThreads());
@@ -211,8 +226,8 @@ public int concurrentReconciliationThreads() {
211226
@Override
212227
public int concurrentWorkflowExecutorThreads() {
213228
return Utils.ensureValid(
214-
concurrentWorkflowExecutorThreads != null ? concurrentWorkflowExecutorThreads
215-
: original.concurrentWorkflowExecutorThreads(),
229+
overriddenValueOrDefault(concurrentWorkflowExecutorThreads,
230+
ConfigurationService::concurrentWorkflowExecutorThreads),
216231
"maximum workflow execution threads",
217232
minimumMaxValueFor(minConcurrentWorkflowExecutorThreads),
218233
original.concurrentWorkflowExecutorThreads());
@@ -224,8 +239,8 @@ public int concurrentWorkflowExecutorThreads() {
224239
@Deprecated(forRemoval = true)
225240
@Override
226241
public int minConcurrentReconciliationThreads() {
227-
return minConcurrentReconciliationThreads != null ? minConcurrentReconciliationThreads
228-
: original.minConcurrentReconciliationThreads();
242+
return overriddenValueOrDefault(minConcurrentReconciliationThreads,
243+
ConfigurationService::minConcurrentReconciliationThreads);
229244
}
230245

231246
/**
@@ -234,30 +249,29 @@ public int minConcurrentReconciliationThreads() {
234249
@Override
235250
@Deprecated(forRemoval = true)
236251
public int minConcurrentWorkflowExecutorThreads() {
237-
return minConcurrentWorkflowExecutorThreads != null ? minConcurrentWorkflowExecutorThreads
238-
: original.minConcurrentWorkflowExecutorThreads();
252+
return overriddenValueOrDefault(minConcurrentWorkflowExecutorThreads,
253+
ConfigurationService::minConcurrentWorkflowExecutorThreads);
239254
}
240255

241256
@Override
242257
public Metrics getMetrics() {
243-
return metrics != null ? metrics : original.getMetrics();
258+
return overriddenValueOrDefault(metrics, ConfigurationService::getMetrics);
244259
}
245260

246261
@Override
247262
public boolean closeClientOnStop() {
248-
return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop();
263+
return overriddenValueOrDefault(closeClientOnStop, ConfigurationService::closeClientOnStop);
249264
}
250265

251266
@Override
252267
public ExecutorService getExecutorService() {
253-
return executorService != null ? executorService
254-
: super.getExecutorService();
268+
return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService);
255269
}
256270

257271
@Override
258272
public ExecutorService getWorkflowExecutorService() {
259-
return workflowExecutorService != null ? workflowExecutorService
260-
: super.getWorkflowExecutorService();
273+
return overriddenValueOrDefault(workflowExecutorService,
274+
ConfigurationService::getWorkflowExecutorService);
261275
}
262276

263277
@Override
@@ -274,54 +288,55 @@ public Optional<InformerStoppedHandler> getInformerStoppedHandler() {
274288

275289
@Override
276290
public boolean stopOnInformerErrorDuringStartup() {
277-
return stopOnInformerErrorDuringStartup != null ? stopOnInformerErrorDuringStartup
278-
: super.stopOnInformerErrorDuringStartup();
291+
return overriddenValueOrDefault(stopOnInformerErrorDuringStartup,
292+
ConfigurationService::stopOnInformerErrorDuringStartup);
279293
}
280294

281295
@Override
282296
public Duration cacheSyncTimeout() {
283-
return cacheSyncTimeout != null ? cacheSyncTimeout : super.cacheSyncTimeout();
297+
return overriddenValueOrDefault(cacheSyncTimeout, ConfigurationService::cacheSyncTimeout);
284298
}
285299

286300
@Override
287301
public ResourceClassResolver getResourceClassResolver() {
288-
return resourceClassResolver != null ? resourceClassResolver
289-
: super.getResourceClassResolver();
302+
return overriddenValueOrDefault(resourceClassResolver,
303+
ConfigurationService::getResourceClassResolver);
290304
}
291305

292306
@Override
293307
public boolean ssaBasedCreateUpdateMatchForDependentResources() {
294-
return ssaBasedCreateUpdateMatchForDependentResources != null
295-
? ssaBasedCreateUpdateMatchForDependentResources
296-
: super.ssaBasedCreateUpdateMatchForDependentResources();
308+
return overriddenValueOrDefault(ssaBasedCreateUpdateMatchForDependentResources,
309+
ConfigurationService::ssaBasedCreateUpdateMatchForDependentResources);
297310
}
298311

299312
@Override
300313
public Set<Class<? extends HasMetadata>> defaultNonSSAResource() {
301-
return defaultNonSSAResource != null ? defaultNonSSAResource
302-
: super.defaultNonSSAResource();
314+
return overriddenValueOrDefault(defaultNonSSAResource,
315+
ConfigurationService::defaultNonSSAResource);
303316
}
304317

305318
@Override
306319
public boolean previousAnnotationForDependentResourcesEventFiltering() {
307-
return previousAnnotationForDependentResources != null
308-
? previousAnnotationForDependentResources
309-
: super.previousAnnotationForDependentResourcesEventFiltering();
320+
return overriddenValueOrDefault(previousAnnotationForDependentResources,
321+
ConfigurationService::previousAnnotationForDependentResourcesEventFiltering);
310322
}
311323

312324
@Override
313325
public boolean parseResourceVersionsForEventFilteringAndCaching() {
314-
return parseResourceVersions != null
315-
? parseResourceVersions
316-
: super.parseResourceVersionsForEventFilteringAndCaching();
326+
return overriddenValueOrDefault(parseResourceVersions,
327+
ConfigurationService::parseResourceVersionsForEventFilteringAndCaching);
317328
}
318329

319330
@Override
320331
public boolean useSSAToPatchPrimaryResource() {
321-
return useSSAToPatchPrimaryResource != null
322-
? useSSAToPatchPrimaryResource
323-
: super.useSSAToPatchPrimaryResource();
332+
return overriddenValueOrDefault(useSSAToPatchPrimaryResource,
333+
ConfigurationService::useSSAToPatchPrimaryResource);
334+
}
324335

336+
@Override
337+
public boolean cloneSecondaryResourcesWhenGettingFromCache() {
338+
return overriddenValueOrDefault(cloneSecondaryResourcesWhenGettingFromCache,
339+
ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache);
325340
}
326341
};
327342
}

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)