Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@
package org.keycloak.operator.controllers;

import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerState;
import io.fabric8.kubernetes.api.model.ContainerStateWaiting;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
import io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.readiness.Readiness;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.quarkus.logging.Log;

import org.keycloak.common.util.CollectionUtil;
Expand Down Expand Up @@ -487,6 +492,7 @@ public void updateStatus(KeycloakStatusAggregator status) {
} else {
status.apply(b -> b.withInstances(existingDeployment.getStatus().getReadyReplicas()));
if (Optional.ofNullable(existingDeployment.getStatus().getReadyReplicas()).orElse(0) < keycloakCR.getSpec().getInstances()) {
checkForPodErrors(status);
status.addNotReadyMessage("Waiting for more replicas");
}
}
Expand All @@ -503,6 +509,33 @@ public void updateStatus(KeycloakStatusAggregator status) {
distConfigurator.validateOptions(status);
}

private void checkForPodErrors(KeycloakStatusAggregator status) {
client.pods().inNamespace(existingDeployment.getMetadata().getNamespace())
.withLabel("controller-revision-hash", existingDeployment.getStatus().getUpdateRevision())
.withLabels(getInstanceLabels())
.list().getItems().stream()
.filter(p -> !Readiness.isPodReady(p)
&& Optional.ofNullable(p.getStatus()).map(PodStatus::getContainerStatuses).isPresent())
.sorted((p1, p2) -> p1.getMetadata().getName().compareTo(p2.getMetadata().getName()))
.forEachOrdered(p -> {
Optional.of(p.getStatus()).map(s -> s.getContainerStatuses()).stream().flatMap(List::stream)
.filter(cs -> !Boolean.TRUE.equals(cs.getReady()))
.sorted((cs1, cs2) -> cs1.getName().compareTo(cs2.getName())).forEachOrdered(cs -> {
if (Optional.ofNullable(cs.getState()).map(ContainerState::getWaiting)
.map(ContainerStateWaiting::getReason).map(String::toLowerCase)
.filter(s -> s.contains("err") || s.equals("crashloopbackoff")).isPresent()) {
Log.infof("Found unhealthy container on pod %s/%s: %s",
p.getMetadata().getNamespace(), p.getMetadata().getName(),
Serialization.asYaml(cs));
status.addErrorMessage(
String.format("Waiting for %s/%s due to %s: %s", p.getMetadata().getNamespace(),
p.getMetadata().getName(), cs.getState().getWaiting().getReason(),
cs.getState().getWaiting().getMessage()));
}
});
});
}

public Set<String> getConfigSecretsNames() {
Set<String> ret = new HashSet<>(serverConfigSecretsNames);
ret.addAll(distConfigurator.getSecretNames());
Expand Down Expand Up @@ -539,7 +572,7 @@ public void migrateDeployment(StatefulSet previousDeployment, StatefulSet reconc
&& previousDeployment.getStatus().getReplicas() > 1) {
// TODO Check if migration is really needed (e.g. based on actual KC version); https://github.com/keycloak/keycloak/issues/10441
Log.info("Detected changed Keycloak image, assuming Keycloak upgrade. Scaling down the deployment to one instance to perform a safe database migration");
Log.infof("original image: %s; new image: %s");
Log.infof("original image: %s; new image: %s", previousContainer.getImage(), reconciledContainer.getImage());

reconciledContainer.setImage(previousContainer.getImage());
reconciledDeployment.getSpec().setReplicas(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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.HostnameSpecBuilder;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;

import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -555,6 +556,31 @@ public void testCustomImageWithImagePullSecrets() {
}
}

@Test
public void testInvalidCustomImageHasErrorMessage() {

try {
var kc = getDefaultKeycloakDeployment();
kc.getSpec().setImage("does-not-exist");

deployKeycloak(k8sclient, kc, false);

var crSelector = k8sclient.resource(kc);

Awaitility.await().atMost(3, MINUTES).pollDelay(1, SECONDS).ignoreExceptions().untilAsserted(() -> {
Keycloak current = crSelector.get();
CRAssert.assertKeycloakStatusCondition(current, KeycloakStatusCondition.READY, false);
CRAssert.assertKeycloakStatusCondition(current, KeycloakStatusCondition.HAS_ERRORS, true,
String.format("Waiting for %s/%s-0 due to ErrImage", k8sclient.getNamespace(),
kc.getMetadata().getName()));
});

} catch (Exception e) {
savePodLogs();
throw e;
}
}

@Test
public void testHttpRelativePathWithPlainValue() {
try {
Expand Down