fix: improving the local apiserver test cleanup (#42701)

closes: #42693

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2025-09-17 13:01:13 -04:00 committed by GitHub
parent aa8321b837
commit 373257a5d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -41,6 +41,7 @@ import io.fabric8.kubernetes.client.utils.Serialization;
import io.fabric8.openshift.api.model.monitoring.v1.ServiceMonitor;
import io.javaoperatorsdk.operator.Operator;
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
import io.javaoperatorsdk.operator.api.config.InformerStoppedHandler;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.quarkiverse.operatorsdk.runtime.QuarkusConfigurationService;
import io.quarkus.logging.Log;
@ -96,6 +97,7 @@ import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.util.TypeLiteral;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.keycloak.operator.Utils.isOpenShift;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
@ -113,7 +115,36 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
public static final String TEST_RESULTS_DIR = "target/operator-test-results/";
public static final String POD_LOGS_DIR = TEST_RESULTS_DIR + "pod-logs/";
public enum OperatorDeployment {local_apiserver,local,remote}
private static final class BaseOperatorTestConfigurationService extends BaseConfigurationService {
private final KubernetesClient client;
private boolean stopped;
private BaseOperatorTestConfigurationService(KubernetesClient client) {
this.client = client;
}
@Override
public KubernetesClient getKubernetesClient() {
return client;
}
@Override
public Optional<InformerStoppedHandler> getInformerStoppedHandler() {
return Optional.of((informer, ex) -> {
if (ex != null && informer.hasSynced()) {
if (!stopped) {
log.error("Fatal error in informer: {}.", informer, ex);
stopped = true;
}
} else {
super.getInformerStoppedHandler().ifPresent(handler -> handler.onStop(informer, ex));
}
});
}
}
public enum OperatorDeployment {local_apiserver,local,remote}
protected static OperatorDeployment operatorDeployment;
protected static QuarkusConfigurationService configuration;
@ -123,6 +154,7 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
protected static String kubernetesIp;
protected static String customImage;
private static Operator operator;
private static BaseOperatorTestConfigurationService config;
protected static boolean isOpenShift;
private static ApiServerHelper kubeApi;
@ -143,8 +175,7 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
kubeApi = new ApiServerHelper();
}
createK8sClient();
createNamespace();
createK8sClientForRandomNamespace();
kubernetesIp = ConfigProvider.getConfig().getOptionalValue(OPERATOR_KUBERNETES_IP, String.class).orElseGet(() -> {
try {
return new URL(k8sclient.getConfiguration().getMasterUrl()).getHost();
@ -180,13 +211,16 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
+ "------- Mode: " + operatorDeployment.name());
}
private static void createK8sClient() {
private static void createK8sClientForRandomNamespace() {
namespace = getNewRandomNamespaceName();
Log.infof("Creating new K8s Client for namespace %s", namespace);
if (operatorDeployment == OperatorDeployment.local_apiserver) {
k8sclient = kubeApi.createClient(namespace);
} else {
k8sclient = new KubernetesClientBuilder().withConfig(new ConfigBuilder(Config.autoConfigure(null)).withNamespace(namespace).build()).build();
}
Log.info("Creating Namespace " + namespace);
k8sclient.resource(new NamespaceBuilder().withNewMetadata().addToLabels("app","keycloak-test").withName(namespace).endMetadata().build()).create();
}
private static void createRBACresourcesAndOperatorDeployment() throws FileNotFoundException {
@ -223,12 +257,8 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
private static void createOperator() {
// create the operator to use the current client / namespace and injected dependent resources
// to be replaced later with full cdi construction or test mechanics from quarkus operator sdk
operator = new Operator(new BaseConfigurationService() {
@Override
public KubernetesClient getKubernetesClient() {
return k8sclient;
}
});
config = new BaseOperatorTestConfigurationService(k8sclient);
operator = new Operator(config);
Log.info("Registering reconcilers for operator : " + operator + " [" + operatorDeployment + "]");
Instance<Reconciler<? extends HasMetadata>> reconcilers = CDI.current().select(new TypeLiteral<>() {});
@ -240,11 +270,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
operator.start();
}
private static void createNamespace() {
Log.info("Creating Namespace " + namespace);
k8sclient.resource(new NamespaceBuilder().withNewMetadata().addToLabels("app","keycloak-test").withName(namespace).endMetadata().build()).create();
}
public static String getNewRandomNamespaceName() {
return "keycloak-test-" + UUID.randomUUID();
}
@ -305,9 +330,11 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
if (operatorDeployment == OperatorDeployment.local_apiserver) {
// by default garbage collection is not supported by envtest
// so might as well do a namespace per test
k8sclient.namespaces().withName(namespace).delete();
String oldNamespace = namespace;
stopOperator();
createNamespace();
// create a new client bc operator has closed the old one
createK8sClientForRandomNamespace();
k8sclient.namespaces().withName(oldNamespace).delete();
createOperator();
deployDBSecret();
return;
@ -515,16 +542,14 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
private static void stopOperator() {
Log.info("Stopping Operator");
assertFalse(config.stopped, "An informer unexpected stopped, check log for an ERROR");
config.stopped = true;
operator.stop();
// Avoid issues with Event Informers between tests
Log.info("Removing Controllers and application scoped DRs from CDI");
Stream.of(KeycloakController.class, KeycloakRealmImportController.class, KeycloakUpdateJobDependentResource.class)
.forEach(c -> CDI.current().destroy(CDI.current().select(c).get()));
Log.info("Creating new K8s Client");
// create a new client bc operator has closed the old one
createK8sClient();
}
public static String getCurrentNamespace() {