[KEYCLOAK-13639] - Improvements to metrics and health status

This commit is contained in:
Pedro Igor 2020-11-12 19:28:01 -03:00
parent 21c7af1c53
commit 42b9141326
7 changed files with 108 additions and 46 deletions

View file

@ -37,6 +37,10 @@ import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -66,7 +70,9 @@ import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.vertx.http.deployment.FilterBuildItem; import io.quarkus.vertx.http.deployment.FilterBuildItem;
import org.keycloak.services.NotFoundHandler;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.health.KeycloakMetricsHandler;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.transaction.JBossJtaTransactionManagerLookup; import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
import org.keycloak.util.Environment; import org.keycloak.util.Environment;
@ -75,6 +81,8 @@ class KeycloakProcessor {
private static final Logger logger = Logger.getLogger(KeycloakProcessor.class); private static final Logger logger = Logger.getLogger(KeycloakProcessor.class);
private static final String DEFAULT_HEALTH_ENDPOINT = "/health";
@BuildStep @BuildStep
FeatureBuildItem getFeature() { FeatureBuildItem getFeature() {
return new FeatureBuildItem("keycloak"); return new FeatureBuildItem("keycloak");
@ -195,13 +203,41 @@ class KeycloakProcessor {
indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-services")); indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-services"));
} }
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep @BuildStep
void initializeFilter(BuildProducer<FilterBuildItem> routes, KeycloakRecorder recorder) { void initializeFilter(BuildProducer<FilterBuildItem> filters) {
Optional<Boolean> metricsEnabled = Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")); filters.produce(new FilterBuildItem(new QuarkusRequestFilter(),FilterBuildItem.AUTHORIZATION - 10));
}
routes.produce(new FilterBuildItem(recorder.createFilter(metricsEnabled.orElse(false)), /**
FilterBuildItem.AUTHORIZATION - 10)); * <p>Initialize metrics and health endpoints.
*
* <p>The only reason for manually registering these endpoints is that by default they run as blocking hence
* running in a different thread than the worker thread started by {@link QuarkusRequestFilter}.
* See https://github.com/quarkusio/quarkus/issues/12990.
*
* <p>By doing this, custom health checks such as {@link org.keycloak.services.health.KeycloakReadyHealthCheck} is
* executed within an active {@link org.keycloak.models.KeycloakSession}, making possible to use it when calculating the
* status.
*
* @param routes
*/
@BuildStep
void initializeMetrics(BuildProducer<RouteBuildItem> routes) {
Handler<RoutingContext> healthHandler;
Handler<RoutingContext> metricsHandler;
if (isMetricsEnabled()) {
healthHandler = new SmallRyeHealthHandler();
metricsHandler = new KeycloakMetricsHandler();
} else {
healthHandler = new NotFoundHandler();
metricsHandler = new NotFoundHandler();
}
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT, healthHandler));
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/live"), healthHandler));
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/ready"), healthHandler));
routes.produce(new RouteBuildItem(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT, metricsHandler));
} }
@BuildStep(onlyIf = IsDevelopment.class) @BuildStep(onlyIf = IsDevelopment.class)
@ -310,4 +346,8 @@ class KeycloakProcessor {
throw new RuntimeException("No valid ConfigProvider found"); throw new RuntimeException("No valid ConfigProvider found");
} }
} }
private boolean isMetricsEnabled() {
return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")).orElse(false);
}
} }

View file

@ -40,15 +40,8 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
// we don't really care about the result because any exception thrown should be handled by the parent class // we don't really care about the result because any exception thrown should be handled by the parent class
}; };
private Predicate<RoutingContext> enabledEndpoints;
@Override @Override
public void handle(RoutingContext context) { public void handle(RoutingContext context) {
if (!enabledEndpoints.test(context)) {
context.fail(404);
return;
}
// our code should always be run as blocking until we don't provide a better support for running non-blocking code // our code should always be run as blocking until we don't provide a better support for running non-blocking code
// in the event loop // in the event loop
context.vertx().executeBlocking(promise -> { context.vertx().executeBlocking(promise -> {
@ -102,8 +95,4 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
} }
}; };
} }
public void setEnabledEndpoints(Predicate<RoutingContext> disabledEndpoints) {
this.enabledEndpoints = disabledEndpoints;
}
} }

View file

@ -26,8 +26,6 @@ import java.util.function.Predicate;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import io.smallrye.config.ConfigValue; import io.smallrye.config.ConfigValue;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.QuarkusKeycloakSessionFactory; import org.keycloak.QuarkusKeycloakSessionFactory;
import org.keycloak.cli.ShowConfigCommand; import org.keycloak.cli.ShowConfigCommand;
@ -45,7 +43,6 @@ import org.keycloak.provider.Spi;
import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.annotations.Recorder;
import liquibase.logging.LogFactory; import liquibase.logging.LogFactory;
import liquibase.servicelocator.ServiceLocator; import liquibase.servicelocator.ServiceLocator;
import org.keycloak.provider.quarkus.QuarkusRequestFilter;
import org.keycloak.util.Environment; import org.keycloak.util.Environment;
@Recorder @Recorder
@ -213,23 +210,4 @@ public class KeycloakRecorder {
} }
}); });
} }
public Handler<RoutingContext> createFilter(boolean metricsEnabled) {
QuarkusRequestFilter handler = new QuarkusRequestFilter();
handler.setEnabledEndpoints(new Predicate<RoutingContext>() {
@Override
public boolean test(RoutingContext context) {
if (context.request().uri().startsWith("/metrics") ||
context.request().uri().startsWith("/health")) {
return metricsEnabled;
}
return true;
}
});
return handler;
}
} }

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
public class NotFoundHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext event) {
event.fail(404);
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.health;
import io.quarkus.smallrye.metrics.runtime.SmallRyeMetricsHandler;
public class KeycloakMetricsHandler extends SmallRyeMetricsHandler {
public static final String DEFAULT_METRICS_ENDPOINT = "/metrics";
public KeycloakMetricsHandler() {
setMetricsPath(DEFAULT_METRICS_ENDPOINT);
}
}

View file

@ -9,6 +9,9 @@ quarkus.http.root-path=/
quarkus.application.name=Keycloak quarkus.application.name=Keycloak
quarkus.banner.enabled=false quarkus.banner.enabled=false
# Disable the default data source health check by Agroal extension, since we provide our own (default is true) # Disable health checks from extensions, since we provide our own (default is true)
quarkus.datasource.health.enabled=false quarkus.health.extensions.enabled=false
# Default transaction timeout
quarkus.transaction-manager.default-transaction-timeout=300

View file

@ -88,9 +88,6 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override @Override
public void commit() { public void commit() {
try { try {
if (Status.STATUS_NO_TRANSACTION == tm.getStatus()) {
return;
}
logger.debug("JtaTransactionWrapper commit"); logger.debug("JtaTransactionWrapper commit");
tm.commit(); tm.commit();
} catch (Exception e) { } catch (Exception e) {
@ -103,9 +100,6 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override @Override
public void rollback() { public void rollback() {
try { try {
if (Status.STATUS_NO_TRANSACTION == tm.getStatus()) {
return;
}
logger.debug("JtaTransactionWrapper rollback"); logger.debug("JtaTransactionWrapper rollback");
tm.rollback(); tm.rollback();
} catch (Exception e) { } catch (Exception e) {