using jenvtest for local operator testing (#38947)

* fix: using jenvtest for testing

closes: #39020

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* Apply suggestions from code review

Co-authored-by: Martin Bartoš <mabartos@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>

* replacing local jobs and using a stronger hash

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* Clean up updateStatefulSet

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Scale down to 0

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Fix for `specReplicas == 0`

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Return `Math.min`

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>
Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>
Co-authored-by: Martin Bartoš <mabartos@redhat.com>
Co-authored-by: Václav Muzikář <vmuzikar@redhat.com>
This commit is contained in:
Steven Hawkins 2025-04-24 10:26:01 -04:00 committed by GitHub
parent ac890075e1
commit 5a4fe491bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 252 additions and 99 deletions

View File

@ -58,56 +58,19 @@ jobs:
upload-m2-repo: false
upload-dist: true
test-local:
name: Test local
test-local-apiserver:
name: Test local apiserver
runs-on: ubuntu-latest
needs: [build]
strategy:
matrix:
suite: [slow, fast]
steps:
- uses: actions/checkout@v4
- name: Set version
id: vars
run: echo "version_local=0.0.1-${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Setup Java
uses: ./.github/actions/java-setup
- name: Setup Minikube-Kubernetes
uses: manusa/actions-setup-minikube@v2.13.1
with:
minikube version: ${{ env.MINIKUBE_VERSION }}
kubernetes version: ${{ env.KUBERNETES_VERSION }}
github token: ${{ secrets.GITHUB_TOKEN }}
driver: docker
start args: --addons=ingress --memory=${{ env.MINIKUBE_MEMORY }} --cni cilium --cpus=max
- name: Download keycloak distribution
id: download-keycloak-dist
uses: actions/download-artifact@v4
with:
name: keycloak-dist
path: quarkus/container
- name: Build Keycloak Docker images
run: |
eval $(minikube -p minikube docker-env)
(cd quarkus/container && docker build --build-arg KEYCLOAK_DIST=$(ls keycloak-*.tar.gz) . -t keycloak:${{ env.version_local }})
(cd operator && ./scripts/build-testing-docker-images.sh ${{ env.version_local }} keycloak custom-keycloak)
- name: Test operator running locally
run: |
declare -A PARAMS
PARAMS["slow"]="-Dkc.quarkus.tests.groups=slow"
PARAMS["fast"]='-Dkc.quarkus.tests.groups=!slow'
./mvnw install -Poperator -pl :keycloak-operator -am \
-Dquarkus.kubernetes.image-pull-policy=IfNotPresent \
-Dkc.operator.keycloak.image=keycloak:${{ env.version_local }} \
-Dtest.operator.custom.image=custom-keycloak:${{ env.version_local }} \
-Dkc.operator.keycloak.image-pull-policy=Never ${PARAMS["${{ matrix.suite }}"]}
./mvnw install -Poperator -pl :keycloak-operator -am
test-remote:
name: Test remote
@ -253,7 +216,7 @@ jobs:
needs:
- conditional
- build
- test-local
- test-local-apiserver
- test-remote
- test-olm
runs-on: ubuntu-latest

View File

@ -7,7 +7,7 @@ Also see [Operator guides](https://www.keycloak.org/guides#operator)
## Activating the Module
When build from the project root directory, this module is only enabled if the installed JDK is 11 or newer.
When build from the project root directory, this module is only enabled if the installed JDK is 17 or newer.
## Building
@ -84,7 +84,9 @@ kubectl delete -k <previously-used-folder>
### Testing
Testing allows 2 methods specified in the property `test.operator.deployment` : `local` & `remote`.
Testing allows 3 methods specified in the property `test.operator.deployment` : `local_apiserver`, `local` & `remote`.
`local_apiserver` : the default, where resources will be deployed to a jenvtest controlled api server (not a full kube environment) and the operator will run locally - not all tests can run in this mode. This is the fastest mode of testing as no externally managed kube environment is needed. As long as your test does not need a running Keycloak instance, this level of testing should be applicable.
`local` : resources will be deployed to the local cluster and the operator will run out of the cluster

View File

@ -142,6 +142,11 @@
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-component</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>

View File

@ -0,0 +1,90 @@
package org.keycloak.operator.testsuite.apiserver;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.keycloak.operator.Utils;
import io.fabric8.kubeapitest.KubeAPIServer;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
public class ApiServerHelper {
private KubeAPIServer kubeApi;
public ApiServerHelper() {
kubeApi = new KubeAPIServer();
kubeApi.start();
}
public void stop() {
kubeApi.stop();
}
public KubernetesClient createClient(String namespace) {
Config config = Config.fromKubeconfig(kubeApi.getKubeConfigYaml());
config.setNamespace(namespace);
var result = new KubernetesClientBuilder().withConfig(config).build();
// fake statefulset controller - we don't have to worry about closing this
// that will happen automatically when the client is closed
result.apps().statefulSets().inAnyNamespace().inform(new ResourceEventHandler<StatefulSet>() {
@Override
public void onAdd(StatefulSet obj) {
updateStatefulSet(obj);
}
private void updateStatefulSet(StatefulSet obj) {
int specReplicas = obj.getSpec().getReplicas();
int statusReplicas = Optional.ofNullable(obj.getStatus().getReplicas()).orElse(0);
String revision = Utils.hash(List.of(obj.getSpec())).substring(1);
String updateRevision = obj.getStatus().getUpdateRevision();
if (statusReplicas != specReplicas
|| !Objects.equals(revision, updateRevision)
|| !Objects.equals(obj.getStatus().getCurrentRevision(), updateRevision)) {
// generate intermediate rolling events
int actualReplicas;
if (!Objects.equals(revision, updateRevision)) { // detected spec change, mimic rolling update
// this is not fully accurate as it's rather recreate than rolling update,
// but thanks to gradual scaling up it emits more events which is closer to the real behavior
actualReplicas = 0;
} else if (specReplicas == 0) { // probably recreate update requested, scaling down the deployment
actualReplicas = Math.max(0, statusReplicas - 1);
} else {
actualReplicas = Math.min(specReplicas, statusReplicas + 1); // otherwise, just scale up
}
obj = result.getKubernetesSerialization().clone(obj);
obj.getStatus().setReplicas(actualReplicas);
obj.getStatus().setReadyReplicas(actualReplicas);
obj.getStatus().setUpdateRevision(revision);
if (actualReplicas == specReplicas) {
obj.getStatus().setCurrentRevision(revision);
}
obj.getMetadata().setResourceVersion(null);
result.resource(obj).updateStatus();
}
}
@Override
public void onUpdate(StatefulSet oldObj, StatefulSet newObj) {
updateStatefulSet(newObj);
}
@Override
public void onDelete(StatefulSet obj, boolean deletedFinalStateUnknown) {
}
});
return result;
}
}

View File

@ -0,0 +1,15 @@
package org.keycloak.operator.testsuite.apiserver;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface DisabledIfApiServerTest {
}

View File

@ -50,6 +50,7 @@ import org.awaitility.Awaitility;
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger.Level;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
@ -62,6 +63,8 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.testsuite.apiserver.ApiServerHelper;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.K8sUtils;
import org.opentest4j.TestAbortedException;
@ -108,7 +111,7 @@ 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,remote}
public enum OperatorDeployment {local_apiserver,local,remote}
protected static OperatorDeployment operatorDeployment;
protected static QuarkusConfigurationService configuration;
@ -120,16 +123,26 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
private static Operator operator;
protected static boolean isOpenShift;
private static ApiServerHelper kubeApi;
@BeforeAll
public static void before() throws FileNotFoundException {
public static void before(TestInfo testInfo) throws FileNotFoundException {
configuration = CDI.current().select(QuarkusConfigurationService.class).get();
operatorDeployment = ConfigProvider.getConfig().getOptionalValue(OPERATOR_DEPLOYMENT_PROP, OperatorDeployment.class).orElse(OperatorDeployment.local);
operatorDeployment = ConfigProvider.getConfig().getOptionalValue(OPERATOR_DEPLOYMENT_PROP, OperatorDeployment.class).orElse(OperatorDeployment.local_apiserver);
if (testInfo.getTestClass().map(m -> m.getAnnotation(DisabledIfApiServerTest.class)).isPresent()) {
Assumptions.assumeFalse(operatorDeployment == OperatorDeployment.local_apiserver);
}
deploymentTarget = ConfigProvider.getConfig().getOptionalValue(QUARKUS_KUBERNETES_DEPLOYMENT_TARGET, String.class).orElse("kubernetes");
customImage = ConfigProvider.getConfig().getOptionalValue(OPERATOR_CUSTOM_IMAGE, String.class).orElse(null);
setDefaultAwaitilityTimings();
calculateNamespace();
if (operatorDeployment == OperatorDeployment.local_apiserver) {
kubeApi = new ApiServerHelper();
}
createK8sClient();
createNamespace();
kubernetesIp = ConfigProvider.getConfig().getOptionalValue(OPERATOR_KUBERNETES_IP, String.class).orElseGet(() -> {
try {
return new URL(k8sclient.getConfiguration().getMasterUrl()).getHost();
@ -139,30 +152,39 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
});
Log.info("Creating CRDs");
createCRDs(k8sclient);
createNamespace();
isOpenShift = isOpenShift(k8sclient);
if (operatorDeployment == OperatorDeployment.remote) {
createRBACresourcesAndOperatorDeployment();
} else {
createOperator();
registerReconcilers();
operator.start();
}
deployDB();
if (operatorDeployment == OperatorDeployment.local_apiserver) {
deployDBSecret();
} else {
deployDB();
}
}
@BeforeEach
public void beforeEach(TestInfo testInfo) {
String testClassName = testInfo.getTestClass().map(c -> c.getSimpleName() + ".").orElse("");
Log.info("\n------- STARTING: " + testClassName + testInfo.getDisplayName() + "\n"
+ "------- Namespace: " + namespace + "\n"
+ "------- Mode: " + ((operatorDeployment == OperatorDeployment.remote) ? "remote" : "local"));
if (testInfo.getTestMethod().map(m -> m.getAnnotation(DisabledIfApiServerTest.class)).isPresent()) {
Assumptions.assumeTrue(operatorDeployment != OperatorDeployment.local_apiserver);
}
String testClassName = testInfo.getTestClass().map(c -> c.getSimpleName() + ".").orElse("");
Log.info("\n------- STARTING: " + testClassName + testInfo.getDisplayName() + "\n"
+ "------- Namespace: " + namespace + "\n"
+ "------- Mode: " + operatorDeployment.name());
}
private static void createK8sClient() {
k8sclient = new KubernetesClientBuilder().withConfig(new ConfigBuilder(Config.autoConfigure(null)).withNamespace(namespace).build()).build();
namespace = getNewRandomNamespaceName();
if (operatorDeployment == OperatorDeployment.local_apiserver) {
k8sclient = kubeApi.createClient(namespace);
} else {
k8sclient = new KubernetesClientBuilder().withConfig(new ConfigBuilder(Config.autoConfigure(null)).withNamespace(namespace).build()).build();
}
}
private static void createRBACresourcesAndOperatorDeployment() throws FileNotFoundException {
@ -194,17 +216,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
Awaitility.await().pollInterval(100, TimeUnit.MILLISECONDS).untilAsserted(() -> client.resources(KeycloakRealmImport.class).list());
}
private static void registerReconcilers() {
Log.info("Registering reconcilers for operator : " + operator + " [" + operatorDeployment + "]");
Instance<Reconciler<? extends HasMetadata>> reconcilers = CDI.current().select(new TypeLiteral<>() {});
for (Reconciler<?> reconciler : reconcilers) {
Log.info("Register and apply : " + reconciler.getClass().getName());
operator.register(reconciler, overrider -> overrider.settingNamespace(namespace));
}
}
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
@ -214,6 +225,15 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
return k8sclient;
}
});
Log.info("Registering reconcilers for operator : " + operator + " [" + operatorDeployment + "]");
Instance<Reconciler<? extends HasMetadata>> reconcilers = CDI.current().select(new TypeLiteral<>() {});
for (Reconciler<?> reconciler : reconcilers) {
Log.info("Register and apply : " + reconciler.getClass().getName());
operator.register(reconciler, overrider -> overrider.settingNamespace(k8sclient.getNamespace()));
}
operator.start();
}
private static void createNamespace() {
@ -221,10 +241,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
k8sclient.resource(new NamespaceBuilder().withNewMetadata().addToLabels("app","keycloak-test").withName(namespace).endMetadata().build()).create();
}
private static void calculateNamespace() {
namespace = getNewRandomNamespaceName();
}
public static String getNewRandomNamespaceName() {
return "keycloak-test-" + UUID.randomUUID();
}
@ -239,7 +255,7 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
// Check DB has deployed and ready
Log.info("Checking Postgres is running");
Awaitility.await()
.untilAsserted(() -> assertThat(k8sclient.apps().statefulSets().inNamespace(namespace).withName(POSTGRESQL_NAME).get().getStatus().getReadyReplicas()).isEqualTo(1));
.untilAsserted(() -> assertThat(k8sclient.apps().statefulSets().withName(POSTGRESQL_NAME).get().getStatus().getReadyReplicas()).isEqualTo(1));
}
protected static void deployDBSecret() {
@ -249,17 +265,17 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
protected static void deleteDB() {
// Delete the Postgres StatefulSet
Log.infof("Waiting for postgres to be deleted");
k8sclient.apps().statefulSets().inNamespace(namespace).withName(POSTGRESQL_NAME).withTimeout(2, TimeUnit.MINUTES).delete();
k8sclient.apps().statefulSets().withName(POSTGRESQL_NAME).withTimeout(2, TimeUnit.MINUTES).delete();
}
// TODO improve this (preferably move to JOSDK)
protected void savePodLogs() {
Log.infof("Saving pod logs to %s", POD_LOGS_DIR);
for (var pod : k8sclient.pods().inNamespace(namespace).list().getItems()) {
for (var pod : k8sclient.pods().list().getItems()) {
try {
String podName = pod.getMetadata().getName();
Log.infof("Processing %s", podName);
String podLog = k8sclient.pods().inNamespace(namespace).withName(podName).getLog();
String podLog = k8sclient.pods().withName(podName).getLog();
File file = new File(POD_LOGS_DIR + String.format("%s-%s.txt", namespace, podName)); // using namespace for now, if more tests fail, the log might get overwritten
file.getAbsoluteFile().getParentFile().mkdirs();
try (var fw = new FileWriter(file, false)) {
@ -272,11 +288,26 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
}
private static void setDefaultAwaitilityTimings() {
Awaitility.setDefaultPollInterval(Duration.ofMillis(500));
Awaitility.setDefaultTimeout(Duration.ofSeconds(360));
if (operatorDeployment == OperatorDeployment.local_apiserver) {
Awaitility.setDefaultPollInterval(Duration.ofMillis(250));
Awaitility.setDefaultTimeout(Duration.ofSeconds(60));
} else {
Awaitility.setDefaultPollInterval(Duration.ofMillis(500));
Awaitility.setDefaultTimeout(Duration.ofSeconds(360));
}
}
public void cleanup() {
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();
stopOperator();
createNamespace();
createOperator();
deployDBSecret();
return;
}
Log.info("Deleting Keycloak CR");
// this can be simplified to just the root deletion after we pick up the fix
@ -289,7 +320,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
k8sclient
.apps()
.statefulSets()
.inNamespace(namespace)
.withLabels(Constants.DEFAULT_LABELS).informOnCondition(List::isEmpty).get(20, TimeUnit.SECONDS);
} catch (Exception e) {
throw KubernetesClientException.launderThrowable(e);
@ -321,7 +351,9 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
if (operatorDeployment == OperatorDeployment.remote) {
log(k8sclient.apps().deployments().withName("keycloak-operator"), Deployment::getStatus, false);
}
logFailed(k8sclient.apps().statefulSets().withName(POSTGRESQL_NAME), StatefulSet::getStatus);
if (operatorDeployment != OperatorDeployment.local_apiserver) {
logFailed(k8sclient.apps().statefulSets().withName(POSTGRESQL_NAME), StatefulSet::getStatus);
}
k8sclient.pods().withLabel("app", "keycloak-realm-import").list().getItems()
.forEach(pod -> log(k8sclient.pods().resource(pod), Pod::getStatus, false));
} finally {
@ -435,8 +467,32 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
@AfterAll
public static void after() throws FileNotFoundException {
if (operatorDeployment == OperatorDeployment.remote) {
cleanRBACresourcesAndOperatorDeployment();
}
if (operatorDeployment == OperatorDeployment.local) {
if (k8sclient != null) {
Log.info("Deleting namespace : " + namespace);
assertThat(k8sclient.namespaces().withName(namespace).delete()).isNotEmpty();
}
if (operator != null) {
stopOperator();
operator = null;
}
if (k8sclient != null) {
k8sclient.close();
k8sclient = null;
}
if (kubeApi != null) {
kubeApi.stop();
kubeApi = null;
}
}
private static void stopOperator() {
Log.info("Stopping Operator");
operator.stop();
@ -448,13 +504,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
Log.info("Creating new K8s Client");
// create a new client bc operator has closed the old one
createK8sClient();
} else {
cleanRBACresourcesAndOperatorDeployment();
}
Log.info("Deleting namespace : " + namespace);
assertThat(k8sclient.namespaces().withName(namespace).delete()).isNotEmpty();
k8sclient.close();
}
public static String getCurrentNamespace() {

View File

@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpecBuilder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -41,6 +42,7 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
@DisabledIfApiServerTest
@QuarkusTest
public class CacheTest extends BaseOperatorTest {

View File

@ -38,6 +38,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusCondition;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -52,6 +53,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.keycloak.operator.controllers.KeycloakDeploymentDependentResource.KC_TRACING_SERVICE_NAME;
@DisabledIfApiServerTest
@QuarkusTest
public class ClusteringTest extends BaseOperatorTest {

View File

@ -47,6 +47,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.unit.WatchedResourcesTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -76,6 +77,7 @@ import static org.keycloak.operator.testsuite.utils.K8sUtils.enableHttp;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
import static org.keycloak.operator.testsuite.utils.K8sUtils.waitForKeycloakToBeReady;
@DisabledIfApiServerTest
@Tag(BaseOperatorTest.SLOW)
@QuarkusTest
public class KeycloakDeploymentTest extends BaseOperatorTest {

View File

@ -34,6 +34,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpecBuilder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.K8sUtils;
import java.util.Map;
@ -48,6 +49,7 @@ import static org.keycloak.operator.testsuite.utils.K8sUtils.enableHttp;
@QuarkusTest
public class KeycloakIngressTest extends BaseOperatorTest {
@DisabledIfApiServerTest
@Test
public void testIngressOnHTTP() {
var kc = getTestKeycloakDeployment(false);
@ -76,6 +78,7 @@ public class KeycloakIngressTest extends BaseOperatorTest {
testIngressURLs(baseUrl);
}
@DisabledIfApiServerTest
@Test
public void testIngressOnHTTPSAndProxySettings() {
var kc = getTestKeycloakDeployment(false);

View File

@ -35,6 +35,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.NetworkPolicySpec;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -51,6 +52,7 @@ public class KeycloakNetworkPolicyTest extends BaseOperatorTest {
.get();
}
@DisabledIfApiServerTest
@Test
public void testHttpAndHttps() {
var kc = create();
@ -69,6 +71,7 @@ public class KeycloakNetworkPolicyTest extends BaseOperatorTest {
CRAssert.assertManagementInterfaceAccessibleViaService(k8sclient, kc, true, mngtPort);
}
@DisabledIfApiServerTest
@Test
public void testServiceConnectivity() {
var kc = create();
@ -128,6 +131,7 @@ public class KeycloakNetworkPolicyTest extends BaseOperatorTest {
}
}
@DisabledIfApiServerTest
@Test
public void testJGroupsConnectivity() {
var kc = create();

View File

@ -30,6 +30,7 @@ import io.quarkus.test.junit.QuarkusTest;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -54,6 +55,7 @@ public class PodTemplateTest extends BaseOperatorTest {
.withName("example-podtemplate");
}
@DisabledIfApiServerTest
@Test
public void testPodTemplateIsMerged() {
// Act

View File

@ -38,6 +38,7 @@ import org.keycloak.operator.controllers.KeycloakServiceDependentResource;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.crds.v2alpha1.realmimport.Placeholder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -58,6 +59,7 @@ import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
import static org.keycloak.operator.testsuite.utils.K8sUtils.inClusterCurl;
@DisabledIfApiServerTest
@QuarkusTest
public class RealmImportTest extends BaseOperatorTest {
@ -103,6 +105,7 @@ public class RealmImportTest extends BaseOperatorTest {
K8sUtils.set(k8sclient, getResourceFromFile("example-smtp-secret.yaml", Secret.class));
}
@DisabledIfApiServerTest
@Test
public void testWorkingRealmImport() {
// Arrange
@ -119,6 +122,7 @@ public class RealmImportTest extends BaseOperatorTest {
assertWorkingRealmImport(kc);
}
@DisabledIfApiServerTest
@Test
public void testWorkingRealmImportWithReplacement() {
// Arrange

View File

@ -24,6 +24,7 @@ import org.keycloak.operator.Constants;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpecBuilder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import java.util.Map;
import java.util.Objects;
@ -34,6 +35,7 @@ import static org.keycloak.operator.controllers.KeycloakDeploymentDependentResou
import static org.keycloak.operator.controllers.KeycloakDeploymentDependentResource.KC_TRACING_SERVICE_NAME;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
@DisabledIfApiServerTest
@QuarkusTest
public class TracingDeploymentTest extends BaseOperatorTest {

View File

@ -17,6 +17,14 @@
package org.keycloak.operator.testsuite.integration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.keycloak.operator.testsuite.utils.CRAssert.eventuallyRecreateUpdateStatus;
import static org.keycloak.operator.testsuite.utils.CRAssert.eventuallyRollingUpdateStatus;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@ -24,10 +32,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.fabric8.kubernetes.api.model.batch.v1.Job;
import io.fabric8.kubernetes.api.model.batch.v1.JobStatus;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
@ -38,17 +43,14 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UpdateSpec;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.update.UpdateStrategy;
import org.keycloak.operator.update.impl.AutoUpdateLogic;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.keycloak.operator.testsuite.utils.CRAssert.eventuallyRecreateUpdateStatus;
import static org.keycloak.operator.testsuite.utils.CRAssert.eventuallyRollingUpdateStatus;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import io.fabric8.kubernetes.api.model.batch.v1.Job;
import io.fabric8.kubernetes.api.model.batch.v1.JobStatus;
import io.quarkus.test.junit.QuarkusTest;
@Tag(BaseOperatorTest.SLOW)
@QuarkusTest
@ -57,6 +59,7 @@ public class UpdateTest extends BaseOperatorTest {
@ParameterizedTest(name = "testImageChange-{0}")
@EnumSource(UpdateStrategy.class)
public void testImageChange(UpdateStrategy updateStrategy) throws InterruptedException {
Assumptions.assumeTrue(operatorDeployment != OperatorDeployment.local_apiserver || updateStrategy != UpdateStrategy.AUTO);
var kc = createInitialDeployment(updateStrategy);
var updateCondition = assertUnknownUpdateTypeStatus(kc);
deployKeycloak(k8sclient, kc, true);
@ -82,6 +85,7 @@ public class UpdateTest extends BaseOperatorTest {
@ParameterizedTest(name = "testCacheMaxCount-{0}")
@EnumSource(UpdateStrategy.class)
public void testCacheMaxCount(UpdateStrategy updateStrategy) throws InterruptedException {
Assumptions.assumeTrue(operatorDeployment != OperatorDeployment.local_apiserver || updateStrategy != UpdateStrategy.AUTO);
var kc = createInitialDeployment(updateStrategy);
var updateCondition = assertUnknownUpdateTypeStatus(kc);
deployKeycloak(k8sclient, kc, true);
@ -108,6 +112,7 @@ public class UpdateTest extends BaseOperatorTest {
@EnumSource(UpdateStrategy.class)
@EnabledIfSystemProperty(named = OPERATOR_CUSTOM_IMAGE, matches = ".+")
public void testOptimizedImage(UpdateStrategy updateStrategy) throws InterruptedException {
Assumptions.assumeTrue(operatorDeployment != OperatorDeployment.local_apiserver || updateStrategy != UpdateStrategy.AUTO);
// In GHA, the custom image is an optimized image of the base image.
// We should be able to do a zero-downtime update with Auto strategy.
var kc = createInitialDeployment(updateStrategy);
@ -133,6 +138,7 @@ public class UpdateTest extends BaseOperatorTest {
}
}
@DisabledIfApiServerTest
@EnabledIfSystemProperty(named = OPERATOR_CUSTOM_IMAGE, matches = ".+")
@Test
public void testNoJobReuse() throws InterruptedException {

View File

@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.unit.WatchedResourcesTest;
import java.util.Base64;
@ -75,6 +76,7 @@ public class WatchedSecretsTest extends BaseOperatorTest {
});
}
@DisabledIfApiServerTest
@Test
public void testSecretChangesArePropagated() {
final String username = "HomerSimpson";

View File

@ -6,7 +6,7 @@ quarkus.operator-sdk.start-operator=false
quarkus.log.level=INFO
kc.operator.keycloak.pod-labels."test.label"=foobar
kc.operator.keycloak.pod-labels."testLabelWithExpression"=${OPERATOR_TEST_LABEL_EXPRESSION}
kc.operator.keycloak.pod-labels."testLabelWithExpression"=${OPERATOR_TEST_LABEL_EXPRESSION:default}
# allow the watching tests to complete more quickly
kc.operator.keycloak.poll-interval-seconds=10
# Update Pod timeout reduced to 1 min for testing