Skip to content

Commit ed8e901

Browse files
committed
wip
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 71da180 commit ed8e901

File tree

3 files changed

+170
-60
lines changed

3 files changed

+170
-60
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerUtils.java

Lines changed: 0 additions & 57 deletions
This file was deleted.

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.javaoperatorsdk.operator.api.reconciler;
22

3+
import java.lang.reflect.InvocationTargetException;
34
import java.time.LocalTime;
45
import java.time.temporal.ChronoUnit;
56
import java.util.function.UnaryOperator;
@@ -8,12 +9,17 @@
89
import org.slf4j.LoggerFactory;
910

1011
import io.fabric8.kubernetes.api.model.HasMetadata;
12+
import io.fabric8.kubernetes.api.model.ObjectMeta;
13+
import io.fabric8.kubernetes.client.KubernetesClient;
1114
import io.fabric8.kubernetes.client.KubernetesClientException;
1215
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
1316
import io.fabric8.kubernetes.client.dsl.base.PatchType;
1417
import io.javaoperatorsdk.operator.OperatorException;
1518
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1619

20+
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
21+
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
22+
1723
/**
1824
* Utility methods to patch the primary resource state and store it to the related cache, to make
1925
* sure that the latest version of the resource is present for the next reconciliation. The main use
@@ -229,4 +235,165 @@ private static <P extends HasMetadata> P pollLocalCache(
229235
throw new OperatorException(e);
230236
}
231237
}
238+
239+
/** Adds finalizer using JSON Patch. Retries conflicts and unprocessable content (HTTP 422) */
240+
@SuppressWarnings("unchecked")
241+
public static <P extends HasMetadata> P addFinalizer(
242+
Context<P> context, P resource, String finalizerName) {
243+
if (log.isDebugEnabled()) {
244+
log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resource));
245+
}
246+
int retryIndex = 0;
247+
while (true) {
248+
try {
249+
if (resource.hasFinalizer(finalizerName)) {
250+
return resource;
251+
}
252+
return context
253+
.getClient()
254+
.resource(resource)
255+
.edit(
256+
r -> {
257+
r.addFinalizer(finalizerName);
258+
return r;
259+
});
260+
} catch (KubernetesClientException e) {
261+
log.trace("Exception during patch for resource: {}", resource);
262+
retryIndex++;
263+
// only retry on conflict (409) and unprocessable content (422) which
264+
// can happen if JSON Patch is not a valid request since there was
265+
// a concurrent request which already removed another finalizer:
266+
// List element removal from a list is by index in JSON Patch
267+
// so if addressing a second finalizer but first is meanwhile removed
268+
// it is a wrong request.
269+
if (e.getCode() != 409 && e.getCode() != 422) {
270+
throw e;
271+
}
272+
if (retryIndex >= DEFAULT_MAX_RETRY) {
273+
throw new OperatorException(
274+
"Exceeded maximum ("
275+
+ DEFAULT_MAX_RETRY
276+
+ ") retry attempts to patch resource: "
277+
+ ResourceID.fromResource(resource));
278+
}
279+
log.debug(
280+
"Retrying patch for resource name: {}, namespace: {}; HTTP code: {}",
281+
resource.getMetadata().getName(),
282+
resource.getMetadata().getNamespace(),
283+
e.getCode());
284+
var operation = context.getClient().resources(resource.getClass());
285+
if (resource.getMetadata().getNamespace() != null) {
286+
resource =
287+
(P)
288+
operation
289+
.inNamespace(resource.getMetadata().getNamespace())
290+
.withName(resource.getMetadata().getName())
291+
.get();
292+
} else {
293+
resource = (P) operation.withName(resource.getMetadata().getName()).get();
294+
}
295+
}
296+
}
297+
}
298+
299+
/** Adds finalizer using Server-Side Apply. */
300+
public static <P extends HasMetadata> P addFinalizerWithSSA(
301+
Context<P> context, P originalResource, String finalizerName) {
302+
return addFinalizerWithSSA(
303+
context.getClient(),
304+
originalResource,
305+
finalizerName,
306+
context.getControllerConfiguration().fieldManager());
307+
}
308+
309+
/** Adds finalizer using Server-Side Apply. */
310+
public static <P extends HasMetadata> P addFinalizerWithSSA(
311+
KubernetesClient client, P originalResource, String finalizerName, String fieldManager) {
312+
log.debug(
313+
"Adding finalizer (using SSA) for resource: {} version: {}",
314+
getUID(originalResource),
315+
getVersion(originalResource));
316+
try {
317+
P resource = (P) originalResource.getClass().getConstructor().newInstance();
318+
ObjectMeta objectMeta = new ObjectMeta();
319+
objectMeta.setName(originalResource.getMetadata().getName());
320+
objectMeta.setNamespace(originalResource.getMetadata().getNamespace());
321+
resource.setMetadata(objectMeta);
322+
resource.addFinalizer(finalizerName);
323+
return client
324+
.resource(resource)
325+
.patch(
326+
new PatchContext.Builder()
327+
.withFieldManager(fieldManager)
328+
.withForce(true)
329+
.withPatchType(PatchType.SERVER_SIDE_APPLY)
330+
.build());
331+
} catch (InstantiationException
332+
| IllegalAccessException
333+
| InvocationTargetException
334+
| NoSuchMethodException e) {
335+
throw new RuntimeException(
336+
"Issue with creating custom resource instance with reflection."
337+
+ " Custom Resources must provide a no-arg constructor. Class: "
338+
+ originalResource.getClass().getName(),
339+
e);
340+
}
341+
}
342+
343+
// todo
344+
public static <P extends HasMetadata> P removeFinalizer() {
345+
return null;
346+
}
347+
348+
/**
349+
* Experimental. Patches finalizer. For retry uses informer cache to get the fresh resources.
350+
* Therefore makes less Kubernetes API Calls.
351+
*/
352+
public static <P extends HasMetadata> P addFinalizer(
353+
P resource, String finalizer, Context<P> context) {
354+
355+
if (resource.hasFinalizer(finalizer)) {
356+
log.debug("Skipping adding finalizer, since already present.");
357+
return resource;
358+
}
359+
360+
return updateAndCacheResource(
361+
resource,
362+
context,
363+
r -> r,
364+
r ->
365+
context
366+
.getClient()
367+
.resource(r)
368+
.edit(
369+
res -> {
370+
res.addFinalizer(finalizer);
371+
return res;
372+
}));
373+
}
374+
375+
/**
376+
* Experimental. Removes finalizer, for retry uses informer cache to get the fresh resources.
377+
* Therefore makes less Kubernetes API Calls.
378+
*/
379+
public static <P extends HasMetadata> P removeFinalizer(
380+
P resource, String finalizer, Context<P> context) {
381+
if (!resource.hasFinalizer(finalizer)) {
382+
log.debug("Skipping removing finalizer, since not present.");
383+
return resource;
384+
}
385+
return updateAndCacheResource(
386+
resource,
387+
context,
388+
r -> r,
389+
r ->
390+
context
391+
.getClient()
392+
.resource(r)
393+
.edit(
394+
res -> {
395+
res.removeFinalizer(finalizer);
396+
return res;
397+
}));
398+
}
232399
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/triggerallevent/onlyreconcile/TriggerReconcilerOnAllEventReconciler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import io.javaoperatorsdk.operator.api.reconciler.Context;
99
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
10-
import io.javaoperatorsdk.operator.api.reconciler.ControllerUtils;
10+
import io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils;
1111
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
1212
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1313

@@ -53,7 +53,7 @@ public UpdateControl<TriggerReconcilerOnAllEventCustomResource> reconcile(
5353

5454
if (!primary.isMarkedForDeletion() && getUseFinalizer() && !primary.hasFinalizer(FINALIZER)) {
5555
log.info("Adding finalizer");
56-
ControllerUtils.patchFinalizer(primary, FINALIZER, context);
56+
PrimaryUpdateAndCacheUtils.addFinalizer(primary, FINALIZER, context);
5757
return UpdateControl.noUpdate();
5858
}
5959

@@ -76,7 +76,7 @@ public UpdateControl<TriggerReconcilerOnAllEventCustomResource> reconcile(
7676
setEventOnMarkedForDeletion(true);
7777
if (getUseFinalizer() && primary.hasFinalizer(FINALIZER)) {
7878
log.info("Removing finalizer");
79-
ControllerUtils.removeFinalizer(primary, FINALIZER, context);
79+
PrimaryUpdateAndCacheUtils.removeFinalizer(primary, FINALIZER, context);
8080
}
8181
}
8282

0 commit comments

Comments
 (0)