Skip to content

Commit 33558d0

Browse files
im-kongePaulRMellor
authored andcommitted
Server Side Apply phase 1 - ConfigMap, Ingress, Service, ServiceAccount, PVC (strimzi#11693)
Signed-off-by: Lukas Kral <lukywill16@gmail.com> Signed-off-by: Lukáš Král <53821852+im-konge@users.noreply.github.com> Co-authored-by: PaulRMellor <47596553+PaulRMellor@users.noreply.github.com>
1 parent 8d2db3b commit 33558d0

33 files changed

+1112
-181
lines changed

.azure/templates/jobs/system-tests/feature_gates_regression_jobs.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
display_name: 'feature-gates-regression-bundle I. - kafka + oauth'
66
profile: 'azp_kafka_oauth'
77
cluster_operator_install_type: 'yaml'
8-
strimzi_feature_gates: '+DummyFeatureGate'
8+
strimzi_feature_gates: '+ServerSideApplyPhase1'
99
timeout: 360
1010
releaseVersion: '${{ parameters.releaseVersion }}'
1111
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -16,7 +16,7 @@ jobs:
1616
display_name: 'feature-gates-regression-bundle II. - security'
1717
profile: 'azp_security'
1818
cluster_operator_install_type: 'yaml'
19-
strimzi_feature_gates: '+DummyFeatureGate'
19+
strimzi_feature_gates: '+ServerSideApplyPhase1'
2020
timeout: 360
2121
releaseVersion: '${{ parameters.releaseVersion }}'
2222
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -27,7 +27,7 @@ jobs:
2727
display_name: 'feature-gates-regression-bundle III. - dynconfig + tracing + watcher'
2828
profile: 'azp_dynconfig_listeners_tracing_watcher'
2929
cluster_operator_install_type: 'yaml'
30-
strimzi_feature_gates: '+DummyFeatureGate'
30+
strimzi_feature_gates: '+ServerSideApplyPhase1'
3131
timeout: 360
3232
releaseVersion: '${{ parameters.releaseVersion }}'
3333
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -38,7 +38,7 @@ jobs:
3838
display_name: 'feature-gates-regression-bundle IV. - operators'
3939
profile: 'azp_operators'
4040
cluster_operator_install_type: 'yaml'
41-
strimzi_feature_gates: '+DummyFeatureGate'
41+
strimzi_feature_gates: '+ServerSideApplyPhase1'
4242
timeout: 360
4343
releaseVersion: '${{ parameters.releaseVersion }}'
4444
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -49,7 +49,7 @@ jobs:
4949
display_name: 'feature-gates-regression-bundle V. - rollingupdate'
5050
profile: 'azp_rolling_update_bridge'
5151
cluster_operator_install_type: 'yaml'
52-
strimzi_feature_gates: '+DummyFeatureGate'
52+
strimzi_feature_gates: '+ServerSideApplyPhase1'
5353
timeout: 360
5454
releaseVersion: '${{ parameters.releaseVersion }}'
5555
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -60,7 +60,7 @@ jobs:
6060
display_name: 'feature-gates-regression-bundle VI. - connect + mirrormaker'
6161
profile: 'azp_connect_mirrormaker'
6262
cluster_operator_install_type: 'yaml'
63-
strimzi_feature_gates: '+DummyFeatureGate'
63+
strimzi_feature_gates: '+ServerSideApplyPhase1'
6464
timeout: 360
6565
releaseVersion: '${{ parameters.releaseVersion }}'
6666
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -71,7 +71,7 @@ jobs:
7171
display_name: 'feature-gates-regression-bundle VII. - logging + specific'
7272
profile: 'azp_logging_specific'
7373
cluster_operator_install_type: 'yaml'
74-
strimzi_feature_gates: '+DummyFeatureGate'
74+
strimzi_feature_gates: '+ServerSideApplyPhase1'
7575
timeout: 360
7676
releaseVersion: '${{ parameters.releaseVersion }}'
7777
kafkaVersion: '${{ parameters.kafkaVersion }}'
@@ -82,7 +82,7 @@ jobs:
8282
display_name: 'feature-gates-regression-bundle VIII. - remaining system tests'
8383
profile: 'azp_remaining'
8484
cluster_operator_install_type: 'yaml'
85-
strimzi_feature_gates: '+DummyFeatureGate'
85+
strimzi_feature_gates: '+ServerSideApplyPhase1'
8686
timeout: 360
8787
releaseVersion: '${{ parameters.releaseVersion }}'
8888
kafkaVersion: '${{ parameters.kafkaVersion }}'

.azure/templates/jobs/system-tests/feature_gates_regression_namespace_rbac_jobs.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
cluster_operator_install_type: 'yaml'
99
timeout: 360
1010
strimzi_rbac_scope: NAMESPACE
11-
strimzi_feature_gates: '+DummyFeatureGate'
11+
strimzi_feature_gates: '+ServerSideApplyPhase1'
1212
releaseVersion: '${{ parameters.releaseVersion }}'
1313
kafkaVersion: '${{ parameters.kafkaVersion }}'
1414

@@ -21,7 +21,7 @@ jobs:
2121
cluster_operator_install_type: 'yaml'
2222
timeout: 360
2323
strimzi_rbac_scope: NAMESPACE
24-
strimzi_feature_gates: '+DummyFeatureGate'
24+
strimzi_feature_gates: '+ServerSideApplyPhase1'
2525
releaseVersion: '${{ parameters.releaseVersion }}'
2626
kafkaVersion: '${{ parameters.kafkaVersion }}'
2727

@@ -34,7 +34,7 @@ jobs:
3434
cluster_operator_install_type: 'yaml'
3535
timeout: 360
3636
strimzi_rbac_scope: NAMESPACE
37-
strimzi_feature_gates: '+DummyFeatureGate'
37+
strimzi_feature_gates: '+ServerSideApplyPhase1'
3838
releaseVersion: '${{ parameters.releaseVersion }}'
3939
kafkaVersion: '${{ parameters.kafkaVersion }}'
4040

@@ -47,7 +47,7 @@ jobs:
4747
cluster_operator_install_type: 'yaml'
4848
timeout: 360
4949
strimzi_rbac_scope: NAMESPACE
50-
strimzi_feature_gates: '+DummyFeatureGate'
50+
strimzi_feature_gates: '+ServerSideApplyPhase1'
5151
releaseVersion: '${{ parameters.releaseVersion }}'
5252
kafkaVersion: '${{ parameters.kafkaVersion }}'
5353

@@ -60,7 +60,7 @@ jobs:
6060
cluster_operator_install_type: 'yaml'
6161
timeout: 360
6262
strimzi_rbac_scope: NAMESPACE
63-
strimzi_feature_gates: '+DummyFeatureGate'
63+
strimzi_feature_gates: '+ServerSideApplyPhase1'
6464
releaseVersion: '${{ parameters.releaseVersion }}'
6565
kafkaVersion: '${{ parameters.kafkaVersion }}'
6666

@@ -73,7 +73,7 @@ jobs:
7373
cluster_operator_install_type: 'yaml'
7474
timeout: 360
7575
strimzi_rbac_scope: NAMESPACE
76-
strimzi_feature_gates: '+DummyFeatureGate'
76+
strimzi_feature_gates: '+ServerSideApplyPhase1'
7777
releaseVersion: '${{ parameters.releaseVersion }}'
7878
kafkaVersion: '${{ parameters.kafkaVersion }}'
7979

@@ -86,6 +86,6 @@ jobs:
8686
cluster_operator_install_type: 'yaml'
8787
timeout: 360
8888
strimzi_rbac_scope: NAMESPACE
89-
strimzi_feature_gates: '+DummyFeatureGate'
89+
strimzi_feature_gates: '+ServerSideApplyPhase1'
9090
releaseVersion: '${{ parameters.releaseVersion }}'
9191
kafkaVersion: '${{ parameters.kafkaVersion }}'

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Add monitoring of custom resources using [kubernetes-state-metrics (KSM)](https://github.com/kubernetes/kube-state-metrics) (see [Strimzi proposal 087](https://github.com/strimzi/proposals/blob/main/087-monitoring-of-custom-resources.md))
99
* Ignore users (their ACLs, Quotas and SCRAM-SHA-512 credentials) managed by some other tools based on a configurable pattern in User Operator
1010
* Added support for Strimzi Metrics Reporter to Kafka Connect, Mirror Maker 2 and Kafka Bridge.
11+
* Add new feature gate `ServerSideApplyPhase1` (disabled by default) that adds support for Server Side Apply for `ConfigMap`, `Ingress`, `PVC`, `Service`, and `ServiceAccount` according to [Strimzi Proposal #105](https://github.com/strimzi/proposals/blob/main/105-server-side-apply-implementation-fg-timelines.md).
1112

1213
### Major changes, deprecations and removals
1314

cluster-operator/src/main/java/io/strimzi/operator/cluster/Main.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,15 @@ private static Future<PlatformFeaturesAvailability> createPlatformFeaturesAvaila
136136
*
137137
* @return Future which completes when all Cluster Operator verticles are started and running
138138
*/
139-
static CompositeFuture deployClusterOperatorVerticles(Vertx vertx, KubernetesClient client, MetricsProvider metricsProvider, PlatformFeaturesAvailability pfa, ClusterOperatorConfig config, ShutdownHook shutdownHook) {
139+
static CompositeFuture deployClusterOperatorVerticles(Vertx vertx, KubernetesClient client, MetricsProvider metricsProvider,
140+
PlatformFeaturesAvailability pfa, ClusterOperatorConfig config, ShutdownHook shutdownHook) {
140141
ResourceOperatorSupplier resourceOperatorSupplier = new ResourceOperatorSupplier(
141142
vertx,
142143
client,
143144
metricsProvider,
144145
pfa,
145-
config.getOperatorName()
146+
config.getOperatorName(),
147+
config.featureGates()
146148
);
147149

148150
// Initialize the PodSecurityProvider factory to provide the user configured provider

cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/resource/ResourceOperatorSupplier.java

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import io.strimzi.operator.common.AdminClientProvider;
5050
import io.strimzi.operator.common.DefaultAdminClientProvider;
5151
import io.strimzi.operator.common.MetricsProvider;
52+
import io.strimzi.operator.common.featuregates.FeatureGates;
5253
import io.vertx.core.Vertx;
5354

5455
/**
@@ -230,15 +231,17 @@ public class ResourceOperatorSupplier {
230231
* @param metricsProvider Metrics provider
231232
* @param pfa Platform Availability Features
232233
* @param operatorName Name of this operator instance
234+
* @param featureGates Feature Gates configuration of operator
233235
*/
234-
public ResourceOperatorSupplier(Vertx vertx, KubernetesClient client, MetricsProvider metricsProvider, PlatformFeaturesAvailability pfa, String operatorName) {
236+
public ResourceOperatorSupplier(Vertx vertx, KubernetesClient client, MetricsProvider metricsProvider, PlatformFeaturesAvailability pfa, String operatorName, FeatureGates featureGates) {
235237
this(vertx,
236-
client,
237-
new DefaultAdminClientProvider(),
238-
new DefaultKafkaAgentClientProvider(),
239-
metricsProvider,
240-
pfa,
241-
new KubernetesRestartEventPublisher(client, operatorName)
238+
client,
239+
new DefaultAdminClientProvider(),
240+
new DefaultKafkaAgentClientProvider(),
241+
metricsProvider,
242+
pfa,
243+
new KubernetesRestartEventPublisher(client, operatorName),
244+
featureGates
242245
);
243246
}
244247

@@ -275,21 +278,40 @@ private ResourceOperatorSupplier(Vertx vertx,
275278
MetricsProvider metricsProvider,
276279
PlatformFeaturesAvailability pfa,
277280
KubernetesRestartEventPublisher restartEventPublisher) {
278-
this(new ServiceOperator(vertx, client),
281+
this(vertx,
282+
client,
283+
adminClientProvider,
284+
kafkaAgentClientProvider,
285+
metricsProvider,
286+
pfa,
287+
restartEventPublisher,
288+
new FeatureGates("")
289+
);
290+
}
291+
292+
private ResourceOperatorSupplier(Vertx vertx,
293+
KubernetesClient client,
294+
AdminClientProvider adminClientProvider,
295+
KafkaAgentClientProvider kafkaAgentClientProvider,
296+
MetricsProvider metricsProvider,
297+
PlatformFeaturesAvailability pfa,
298+
KubernetesRestartEventPublisher restartEventPublisher,
299+
FeatureGates featureGates) {
300+
this(new ServiceOperator(vertx, client, featureGates.serverSideApplyPhase1Enabled()),
279301
pfa.hasRoutes() ? new RouteOperator(vertx, client.adapt(OpenShiftClient.class)) : null,
280302
pfa.hasImages() ? new ImageStreamOperator(vertx, client.adapt(OpenShiftClient.class)) : null,
281-
new ConfigMapOperator(vertx, client),
303+
new ConfigMapOperator(vertx, client, featureGates.serverSideApplyPhase1Enabled()),
282304
new SecretOperator(vertx, client),
283-
new PvcOperator(vertx, client),
305+
new PvcOperator(vertx, client, featureGates.serverSideApplyPhase1Enabled()),
284306
new DeploymentOperator(vertx, client),
285-
new ServiceAccountOperator(vertx, client),
307+
new ServiceAccountOperator(vertx, client, featureGates.serverSideApplyPhase1Enabled()),
286308
new RoleBindingOperator(vertx, client),
287309
new RoleOperator(vertx, client),
288310
new ClusterRoleBindingOperator(vertx, client),
289311
new NetworkPolicyOperator(vertx, client),
290312
new PodDisruptionBudgetOperator(vertx, client),
291313
new PodOperator(vertx, client),
292-
new IngressOperator(vertx, client),
314+
new IngressOperator(vertx, client, featureGates.serverSideApplyPhase1Enabled()),
293315
pfa.hasBuilds() ? new BuildConfigOperator(vertx, client.adapt(OpenShiftClient.class)) : null,
294316
pfa.hasBuilds() ? new BuildOperator(vertx, client.adapt(OpenShiftClient.class)) : null,
295317
new CrdOperator<>(vertx, client, Kafka.class, KafkaList.class, Kafka.RESOURCE_KIND),

cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/resource/kubernetes/AbstractNamespacedResourceOperator.java

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
1010
import io.fabric8.kubernetes.api.model.LabelSelector;
1111
import io.fabric8.kubernetes.client.KubernetesClient;
12+
import io.fabric8.kubernetes.client.KubernetesClientException;
1213
import io.fabric8.kubernetes.client.Watcher;
1314
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
1415
import io.fabric8.kubernetes.client.dsl.Informable;
@@ -49,15 +50,29 @@ public abstract class AbstractNamespacedResourceOperator<C extends KubernetesCli
4950
R extends Resource<T>>
5051
extends AbstractResourceOperator<C, T, L, R> {
5152
private static final ReconciliationLogger LOGGER = ReconciliationLogger.create(AbstractNamespacedResourceOperator.class);
53+
private final boolean useServerSideApply;
5254

5355
/**
5456
* Constructor.
5557
* @param vertx The vertx instance.
5658
* @param client The kubernetes client.
57-
* @param resourceKind The mind of Kubernetes resource (used for logging).
59+
* @param resourceKind The kind of Kubernetes resource (used for logging).
5860
*/
5961
public AbstractNamespacedResourceOperator(Vertx vertx, C client, String resourceKind) {
6062
super(vertx, client, resourceKind);
63+
this.useServerSideApply = false;
64+
}
65+
66+
/**
67+
* Constructor.
68+
* @param vertx The vertx instance.
69+
* @param client The kubernetes client.
70+
* @param resourceKind The kind of Kubernetes resource (used for logging).
71+
* @param useServerSideApply Determines if Server Side Apply should be used.
72+
*/
73+
public AbstractNamespacedResourceOperator(Vertx vertx, C client, String resourceKind, boolean useServerSideApply) {
74+
super(vertx, client, resourceKind);
75+
this.useServerSideApply = useServerSideApply;
6176
}
6277

6378
protected abstract MixedOperation<T, L, R> operation();
@@ -96,22 +111,25 @@ public Future<ReconcileResult<T>> reconcile(Reconciliation reconciliation, Strin
96111

97112
return getAsync(namespace, name)
98113
.compose(current -> {
99-
if (desired != null) {
114+
if (desired == null) {
100115
if (current == null) {
101-
LOGGER.debugCr(reconciliation, "{} {}/{} does not exist, creating it", resourceKind, namespace, name);
102-
return internalCreate(reconciliation, namespace, name, desired);
116+
LOGGER.debugCr(reconciliation, "{} {}/{} does not exist, noop", resourceKind, namespace, name);
117+
return Future.succeededFuture(ReconcileResult.noop(null));
103118
} else {
104-
LOGGER.debugCr(reconciliation, "{} {}/{} already exists, updating it", resourceKind, namespace, name);
105-
return internalUpdate(reconciliation, namespace, name, current, desired);
106-
}
107-
} else {
108-
if (current != null) {
109119
// Deletion is desired
110120
LOGGER.debugCr(reconciliation, "{} {}/{} exist, deleting it", resourceKind, namespace, name);
111121
return internalDelete(reconciliation, namespace, name);
122+
}
123+
} else {
124+
if (useServerSideApply) {
125+
LOGGER.debugCr(reconciliation, "{} {}/{} is desired, patching it", resourceKind, namespace, name);
126+
return internalServerSideApply(reconciliation, namespace, name, desired);
127+
} else if (current == null) {
128+
LOGGER.debugCr(reconciliation, "{} {}/{} does not exist, creating it", resourceKind, namespace, name);
129+
return internalCreate(reconciliation, namespace, name, desired);
112130
} else {
113-
LOGGER.debugCr(reconciliation, "{} {}/{} does not exist, noop", resourceKind, namespace, name);
114-
return Future.succeededFuture(ReconcileResult.noop(null));
131+
LOGGER.debugCr(reconciliation, "{} {}/{} already exists, updating it", resourceKind, namespace, name);
132+
return internalUpdate(reconciliation, namespace, name, current, desired);
115133
}
116134
}
117135
});
@@ -245,6 +263,56 @@ protected Future<ReconcileResult<T>> internalUpdate(Reconciliation reconciliatio
245263
}
246264
}
247265

266+
/**
267+
* Patches the resource with the given namespace and name to match the given desired resource
268+
* and completes the given future accordingly.
269+
* The patch is done using Server-Side Apply, which means that the server will handle the changes
270+
* to the resource.
271+
* First patch attempt is done without force, however if there is a conflict, operator will try again using force.
272+
*/
273+
protected Future<ReconcileResult<T>> internalServerSideApply(Reconciliation reconciliation, String namespace, String name, T desired) {
274+
R resourceOp = operation().inNamespace(namespace).withName(name);
275+
276+
try {
277+
T result;
278+
279+
try {
280+
LOGGER.debugCr(reconciliation, "{} {}/{} is being patched using Server Side Apply", resourceKind, namespace, name);
281+
result = resourceOp.patch(serverSideApplyPatchContext(false), desired);
282+
} catch (KubernetesClientException e) {
283+
// in case that error code is 409, we have conflict with other operator when using SSA
284+
// so we will use force
285+
if (e.getCode() == 409) {
286+
LOGGER.warnCr(reconciliation, "{} {}/{} failed to patch because of conflict: {}, applying force", resourceKind, namespace, name, e.getMessage());
287+
result = resourceOp.patch(serverSideApplyPatchContext(true), desired);
288+
} else {
289+
// otherwise throw the exception
290+
throw e;
291+
}
292+
}
293+
294+
LOGGER.debugCr(reconciliation, "{} {}/{} has been patched", resourceKind, namespace, name);
295+
return Future.succeededFuture(ReconcileResult.patchedWithServerSideApply(result));
296+
} catch (Exception e) {
297+
LOGGER.debugCr(reconciliation, "Caught exception while patching {} {} in namespace {}", resourceKind, name, namespace, e);
298+
return Future.failedFuture(e);
299+
}
300+
}
301+
302+
/**
303+
* Creates {@link PatchContext} with SSA type for the Cluster Operator.
304+
*
305+
* @param useForce boolean parameter determining if the force should be used or not.
306+
* @return {@link PatchContext} with SSA type for the Cluster Operator.
307+
*/
308+
private static PatchContext serverSideApplyPatchContext(boolean useForce) {
309+
return new PatchContext.Builder()
310+
.withFieldManager("strimzi-kafka-operator")
311+
.withForce(useForce)
312+
.withPatchType(PatchType.SERVER_SIDE_APPLY)
313+
.build();
314+
}
315+
248316
/**
249317
* Method for patching or replacing a resource. By default, is using JSON-type patch. Overriding this method can be
250318
* used to use replace instead of patch or different patch strategies.

cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/resource/kubernetes/ConfigMapOperator.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ public class ConfigMapOperator extends AbstractNamespacedResourceOperator<Kubern
2828
/**
2929
* Constructor
3030
*
31-
* @param vertx The Vertx instance
32-
* @param client The Kubernetes client
31+
* @param vertx The Vertx instance
32+
* @param client The Kubernetes client
33+
* @param useServerSideApply Determines if Server Side Apply should be used
3334
*/
34-
public ConfigMapOperator(Vertx vertx, KubernetesClient client) {
35-
super(vertx, client, "ConfigMap");
35+
public ConfigMapOperator(Vertx vertx, KubernetesClient client, boolean useServerSideApply) {
36+
super(vertx, client, "ConfigMap", useServerSideApply);
3637
}
3738

3839
@Override

0 commit comments

Comments
 (0)