mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
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:
parent
ac890075e1
commit
5a4fe491bf
45
.github/workflows/operator-ci.yml
vendored
45
.github/workflows/operator-ci.yml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user