Skip to content

Commit 2318d4a

Browse files
authored
fix: delete CRD only after all tests in the class pass (#3279)
Signed-off-by: xstefank <xstefank122@gmail.com>
1 parent 2901f08 commit 2318d4a

File tree

2 files changed

+76
-23
lines changed

2 files changed

+76
-23
lines changed

operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ protected void before(ExtensionContext context) {
9696
final var crd = kubernetesClient.load(is);
9797
crd.createOrReplace();
9898
Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little
99-
LOGGER.debug("Applied CRD with name: {}", crd.get().get(0).getMetadata().getName());
99+
var crdList = crd.get();
100+
LOGGER.debug(
101+
"Applied CRD with name: {}",
102+
(crdList != null && !crdList.isEmpty() && crdList.get(0) != null)
103+
? crdList.get(0).getMetadata().getName()
104+
: crdFile.getName());
100105
} catch (InterruptedException ex) {
101106
LOGGER.error("Interrupted.", ex);
102107
Thread.currentThread().interrupt();

operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import java.util.HashSet;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.Objects;
3132
import java.util.Set;
33+
import java.util.concurrent.TimeUnit;
3234
import java.util.function.Consumer;
3335
import java.util.function.Function;
3436
import java.util.stream.Stream;
@@ -42,6 +44,7 @@
4244
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
4345
import io.fabric8.kubernetes.client.CustomResource;
4446
import io.fabric8.kubernetes.client.KubernetesClient;
47+
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
4548
import io.fabric8.kubernetes.client.LocalPortForward;
4649
import io.javaoperatorsdk.operator.Operator;
4750
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
@@ -58,6 +61,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension {
5861

5962
private static final Logger LOGGER = LoggerFactory.getLogger(LocallyRunOperatorExtension.class);
6063
private static final int CRD_DELETE_TIMEOUT = 5000;
64+
private static final int CRD_DELETE_WAIT_TIMEOUT = 60000;
6165
private static final Set<AppliedCRD> appliedCRDs = new HashSet<>();
6266
private static final boolean deleteCRDs =
6367
Boolean.parseBoolean(System.getProperty("testsuite.deleteCRDs", "true"));
@@ -349,6 +353,13 @@ protected void before(ExtensionContext context) {
349353
beforeStartHook.accept(this);
350354
}
351355

356+
// ExtensionContext.Store.CloseableResource registered in the class-level (parent) context
357+
// is invoked by JUnit when the class scope closes
358+
var classContext = oneNamespacePerClass ? context : context.getParent().orElse(context);
359+
classContext
360+
.getStore(ExtensionContext.Namespace.create(LocallyRunOperatorExtension.class))
361+
.computeIfAbsent(CrdCleanup.class, ignored -> new CrdCleanup());
362+
352363
LOGGER.debug("Starting the operator locally");
353364
this.operator.start();
354365
}
@@ -357,18 +368,12 @@ protected void before(ExtensionContext context) {
357368
protected void after(ExtensionContext context) {
358369
super.after(context);
359370

360-
var kubernetesClient = getInfrastructureKubernetesClient();
361-
362-
var iterator = appliedCRDs.iterator();
363-
while (iterator.hasNext()) {
364-
deleteCrd(iterator.next(), kubernetesClient);
365-
iterator.remove();
366-
}
371+
var infrastructureKubernetesClient = getInfrastructureKubernetesClient();
367372

368373
// if the client is used for infra client, we should not close it
369374
// either test or operator should close this client
370-
if (getKubernetesClient() != getInfrastructureKubernetesClient()) {
371-
kubernetesClient.close();
375+
if (getKubernetesClient() != infrastructureKubernetesClient) {
376+
infrastructureKubernetesClient.close();
372377
}
373378

374379
try {
@@ -387,12 +392,27 @@ protected void after(ExtensionContext context) {
387392
localPortForwards.clear();
388393
}
389394

390-
private void deleteCrd(AppliedCRD appliedCRD, KubernetesClient client) {
391-
if (!deleteCRDs) {
392-
LOGGER.debug("Skipping deleting CRD because of configuration: {}", appliedCRD);
393-
return;
395+
private static class CrdCleanup implements ExtensionContext.Store.CloseableResource {
396+
@Override
397+
public void close() {
398+
// Create a fresh client for cleanup since operator clients may already be closed.
399+
try (var client = new KubernetesClientBuilder().build()) {
400+
var iterator = appliedCRDs.iterator();
401+
while (iterator.hasNext()) {
402+
var appliedCRD = iterator.next();
403+
iterator.remove();
404+
if (!deleteCRDs) {
405+
LOGGER.debug("Skipping deleting CRD because of configuration: {}", appliedCRD);
406+
continue;
407+
}
408+
try {
409+
appliedCRD.delete(client);
410+
} catch (Exception e) {
411+
LOGGER.warn("Failed to delete CRD: {}. Continuing with remaining CRDs.", appliedCRD, e);
412+
}
413+
}
414+
}
394415
}
395-
appliedCRD.delete(client);
396416
}
397417

398418
private sealed interface AppliedCRD permits AppliedCRD.FileCRD, AppliedCRD.InstanceCRD {
@@ -409,8 +429,25 @@ record FileCRD(String crdString, String path) implements AppliedCRD {
409429
public void delete(KubernetesClient client) {
410430
try {
411431
LOGGER.debug("Deleting CRD: {}", crdString);
412-
final var crd = client.load(new ByteArrayInputStream(crdString.getBytes()));
413-
crd.withTimeoutInMillis(CRD_DELETE_TIMEOUT).delete();
432+
final var items = client.load(new ByteArrayInputStream(crdString.getBytes())).items();
433+
if (items == null || items.isEmpty() || items.get(0) == null) {
434+
LOGGER.warn("Could not determine CRD name from yaml: {}", path);
435+
return;
436+
}
437+
final var crdName = items.get(0).getMetadata().getName();
438+
client
439+
.apiextensions()
440+
.v1()
441+
.customResourceDefinitions()
442+
.withName(crdName)
443+
.withTimeoutInMillis(CRD_DELETE_TIMEOUT)
444+
.delete();
445+
client
446+
.apiextensions()
447+
.v1()
448+
.customResourceDefinitions()
449+
.withName(crdName)
450+
.waitUntilCondition(Objects::isNull, CRD_DELETE_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
414451
LOGGER.debug("Deleted CRD with path: {}", path);
415452
} catch (Exception ex) {
416453
LOGGER.warn(
@@ -423,17 +460,28 @@ record InstanceCRD(CustomResourceDefinition customResourceDefinition) implements
423460

424461
@Override
425462
public void delete(KubernetesClient client) {
426-
String type = customResourceDefinition.getMetadata().getName();
463+
String crdName = customResourceDefinition.getMetadata().getName();
427464
try {
428-
LOGGER.debug("Deleting CustomResourceDefinition instance CRD: {}", type);
429-
final var crd = client.resource(customResourceDefinition);
430-
crd.withTimeoutInMillis(CRD_DELETE_TIMEOUT).delete();
431-
LOGGER.debug("Deleted CustomResourceDefinition instance CRD: {}", type);
465+
LOGGER.debug("Deleting CustomResourceDefinition instance CRD: {}", crdName);
466+
client
467+
.apiextensions()
468+
.v1()
469+
.customResourceDefinitions()
470+
.withName(crdName)
471+
.withTimeoutInMillis(CRD_DELETE_TIMEOUT)
472+
.delete();
473+
client
474+
.apiextensions()
475+
.v1()
476+
.customResourceDefinitions()
477+
.withName(crdName)
478+
.waitUntilCondition(Objects::isNull, CRD_DELETE_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
479+
LOGGER.debug("Deleted CustomResourceDefinition instance CRD: {}", crdName);
432480
} catch (Exception ex) {
433481
LOGGER.warn(
434482
"Cannot delete CustomResourceDefinition instance CRD: {}. You might need to delete it"
435483
+ " manually.",
436-
type,
484+
crdName,
437485
ex);
438486
}
439487
}

0 commit comments

Comments
 (0)