diff --git a/docs/guides/images/server/infinispan_info.png b/docs/guides/images/server/infinispan_info.png new file mode 100644 index 00000000000..cf34b84c848 Binary files /dev/null and b/docs/guides/images/server/infinispan_info.png differ diff --git a/docs/guides/server/caching.adoc b/docs/guides/server/caching.adoc index 52fe5d195dd..a7c04ee793c 100644 --- a/docs/guides/server/caching.adoc +++ b/docs/guides/server/caching.adoc @@ -408,6 +408,46 @@ m|cache-embedded-network-external-address |=== +== Verify cluster and network health + +This section provides methods to verify that your {project_name} cluster has formed correctly and that network communication between instances is functioning as expected. +It is crucial to perform these checks after deployment to ensure high availability and data consistency. + +To verify if the cluster is formed properly, check one of these locations: + +* Admin UI ++ +Access the {project_name} Web UI, typically available at `++https:///admin/master/console/#/master/providers++`. +Under the *Provider Info* section, locate the *connectionsInfinispan* entry. +Click on *Show more* to expand its details. +You should find information about the cluster status and the health of individual caches. ++ +image:server/infinispan_info.png[Infinispan Cluster Information in Web UI] + +* Logs ++ +Infinispan logs a cluster view every time a new instance joins or leaves the cluster. +Search for log entries with the ID `ISPN000094`. ++ +A healthy cluster view will show all expected nodes. +For example: ++ +[source,text] +---- +ISPN000094: Received new cluster view for channel ISPN: [node1-26186|1] (2) [node1-26186, node2-37007] +---- ++ +This log entry indicates that the cluster named "ISPN" currently has 2 nodes: `node1-26186` and `node2-37007`. +The `(2)` confirms the total number of nodes in the cluster. + +* Metrics ++ +{project_name} exposes Infinispan metrics via a Prometheus endpoint, which can be visualized in tools like Grafana. +The metric `vendor_cluster_size` shows the current number of instances in the cluster. +You should verify that this metric matches the expected number of running instances configured in your cluster. ++ +Refer to <@links.observability id="metrics-for-troubleshooting-clustering-and-network" anchor="_cluster_size"/> for more information. + == Exposing metrics from caches Metrics from caches are automatically exposed when the metrics are enabled. diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index 6ff30a16060..2eee9559669 100755 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -17,16 +17,23 @@ package org.keycloak.connections.infinispan; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.commons.util.Version; import org.infinispan.configuration.cache.Configuration; import org.infinispan.eviction.EvictionStrategy; +import org.infinispan.health.CacheHealth; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; import org.jboss.logging.Logger; @@ -52,6 +59,7 @@ import org.keycloak.provider.InvalidationHandler.ObjectType; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderEventListener; +import org.keycloak.provider.ServerInfoAwareProviderFactory; import org.keycloak.spi.infinispan.CacheEmbeddedConfigProvider; import org.keycloak.spi.infinispan.CacheRemoteConfigProvider; import org.keycloak.spi.infinispan.impl.embedded.CacheConfigurator; @@ -73,7 +81,7 @@ import static org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderF /** * @author Stian Thorgersen */ -public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory, ProviderEventListener { +public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory, ProviderEventListener, ServerInfoAwareProviderFactory { private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock(); private static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class); @@ -278,4 +286,32 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon KeycloakModelUtils.runJobInTransaction(pme.getFactory(), this::registerSystemWideListeners); } } + + @Override + public Map getOperationalInfo() { + Map info = new LinkedHashMap<>(); + info.put("product", Version.getBrandName()); + info.put("version", Version.getBrandVersion()); + if (InfinispanUtils.isRemoteInfinispan()) { + addRemoteOperationalInfo(info); + } else { + addEmbeddedOperationalInfo(info); + } + return info; + } + + private void addEmbeddedOperationalInfo(Map info) { + var cacheManagerInfo = cacheManager.getCacheManagerInfo(); + info.put("clusterSize", Integer.toString(cacheManagerInfo.getClusterSize())); + var cacheNames = Arrays.stream(InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES) + .sorted() + .collect(Collectors.toCollection(LinkedHashSet::new)); + for (CacheHealth health : cacheManager.getHealth().getCacheHealth(cacheNames)) { + info.put(health.getCacheName() + ":Cache", health.getStatus().toString()); + } + } + + private void addRemoteOperationalInfo(Map info) { + info.put("connectionCount", Integer.toString(remoteCacheManager.getConnectionCount())); + } }