Skip to content

Commit

Permalink
Publish information about Infinispan availability in lb-check if MULT…
Browse files Browse the repository at this point in the history
…I_SITE is enabled

Closes keycloak#25077

Signed-off-by: Michal Hajas <mhajas@redhat.com>
Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
Co-authored-by: Pedro Ruivo <pruivo@redhat.com>
Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
  • Loading branch information
3 people authored Nov 29, 2023
1 parent 29aec9c commit 2b2207a
Show file tree
Hide file tree
Showing 25 changed files with 457 additions and 65 deletions.
4 changes: 3 additions & 1 deletion common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public enum Feature {

UPDATE_EMAIL("Update Email Action", Type.PREVIEW),

JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT),
JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak server", Type.DEFAULT),

FIPS("FIPS 140-2 mode", Type.DISABLED_BY_DEFAULT),

Expand All @@ -97,6 +97,8 @@ public enum Feature {
DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT),

TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL),

MULTI_SITE("Multi-site support", Type.PREVIEW),
;

private final Type type;
Expand Down
3 changes: 2 additions & 1 deletion common/src/test/java/org/keycloak/common/ProfileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public void checkDefaults() {
Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ,
Profile.Feature.DYNAMIC_SCOPES,
Profile.Feature.DOCKER,
Profile.Feature.MULTI_SITE,
Profile.Feature.RECOVERY_CODES,
Profile.Feature.SCRIPTS,
Profile.Feature.TOKEN_EXCHANGE,
Expand All @@ -93,7 +94,7 @@ public void checkDefaults() {
disabledFeatures.add(Profile.Feature.KERBEROS);
}
assertEquals(profile.getDisabledFeatures(), disabledFeatures);
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP);
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.MULTI_SITE, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager, Re
}

@Override
public <K, V> Cache<K, V> getCache(String name) {
return cacheManager.getCache(name);
public <K, V> Cache<K, V> getCache(String name, boolean createIfAbsent) {
return cacheManager.getCache(name, createIfAbsent);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.keycloak.connections.infinispan;

import org.infinispan.Cache;
import org.infinispan.client.hotrod.ProtocolVersion;
import org.infinispan.commons.dataconversion.MediaType;
import org.infinispan.configuration.cache.CacheMode;
Expand All @@ -28,6 +29,7 @@
import org.infinispan.jboss.marshalling.core.JBossUserMarshaller;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
Expand Down Expand Up @@ -119,7 +121,7 @@ public void init(Config.Scope config) {
public void postInit(KeycloakSessionFactory factory) {
factory.register((ProviderEvent event) -> {
if (event instanceof PostMigrationEvent) {
KeycloakModelUtils.runJobInTransaction(factory, session -> { registerSystemWideListeners(session); });
KeycloakModelUtils.runJobInTransaction(factory, this::registerSystemWideListeners);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,43 @@ public interface InfinispanConnectionProvider extends Provider {
// Constant used as the prefix of the current node if "jboss.node.name" is not configured
String NODE_PREFIX = "node_";

<K, V> Cache<K, V> getCache(String name);
String[] ALL_CACHES_NAME = {
REALM_CACHE_NAME,
REALM_REVISIONS_CACHE_NAME,
USER_CACHE_NAME,
USER_REVISIONS_CACHE_NAME,
USER_SESSION_CACHE_NAME,
CLIENT_SESSION_CACHE_NAME,
OFFLINE_USER_SESSION_CACHE_NAME,
OFFLINE_CLIENT_SESSION_CACHE_NAME,
LOGIN_FAILURE_CACHE_NAME,
AUTHENTICATION_SESSIONS_CACHE_NAME,
WORK_CACHE_NAME,
AUTHORIZATION_CACHE_NAME,
AUTHORIZATION_REVISIONS_CACHE_NAME,
ACTION_TOKEN_CACHE,
KEYS_CACHE_NAME
};

/**
*
* Effectively the same as {@link InfinispanConnectionProvider#getCache(String, boolean)} with createIfAbsent set to {@code true}
*
*/
default <K, V> Cache<K, V> getCache(String name) {
return getCache(name, true);
}

/**
* Provides an instance if Infinispan cache by name
*
* @param name name of the requested cache
* @param createIfAbsent if true the connection provider will create the requested cache on method call if it does not exist
* @return return a cache instance
* @param <K> key type
* @param <V> value type
*/
<K, V> Cache<K, V> getCache(String name, boolean createIfAbsent);

/**
* Get remote cache of given name. Could just retrieve the remote cache from the remoteStore configured in given infinispan cache and/or
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2023 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.connections.infinispan;

import org.infinispan.Cache;
import org.infinispan.persistence.manager.PersistenceManager;
import org.jboss.logging.Logger;
import org.keycloak.health.LoadBalancerCheckProvider;

import java.util.Objects;

import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.ALL_CACHES_NAME;

public class InfinispanMultiSiteLoadBalancerCheckProvider implements LoadBalancerCheckProvider {
private static final Logger LOG = Logger.getLogger(InfinispanMultiSiteLoadBalancerCheckProvider.class);
private final InfinispanConnectionProvider connectionProvider;

public InfinispanMultiSiteLoadBalancerCheckProvider(InfinispanConnectionProvider connectionProvider) {
Objects.requireNonNull(connectionProvider, "connectionProvider");
this.connectionProvider = connectionProvider;
}

/**
* Non-blocking check if all caches and their persistence are available.
* <p />
* In a situation where any cache's remote cache is unreachable, this will report the "down" to the caller.
* When the remote cache is down, it assumes that it is down for all Keycloak nodes in this site, all incoming
* requests are likely to fail and that a loadbalancer should send traffic to the other site that might be healthy.
* <p />
* This code is non-blocking as the embedded Infinispan checks the connection to the remote store periodically
* in the background (default: every second).
* See {@link LoadBalancerCheckProvider#isDown()} to read more why this needs to be non-blocking.
*
* @return true if the component is down/unhealthy, false otherwise
*/
@Override
public boolean isDown() {
for (String cacheName : ALL_CACHES_NAME) {
// do not block in cache creation, as this method is required to be non-blocking
Cache<?,?> cache = connectionProvider.getCache(cacheName, false);

// check if cache is started
if (cache == null || !cache.getStatus().allowInvocations()) {
LOG.debugf("Cache '%s' is not started yet.", cacheName);
return true; // no need to check other caches
}

PersistenceManager persistenceManager = cache.getAdvancedCache()
.getComponentRegistry()
.getComponent(PersistenceManager.class);

if (persistenceManager != null && !persistenceManager.isAvailable()) {
LOG.debugf("PersistenceManager for cache '%s' is down.", cacheName);
return true; // no need to check other caches
}
LOG.debugf("Cache '%s' is up.", cacheName);
}

return false;
}

@Override
public void close() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2023 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.connections.infinispan;

import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.health.LoadBalancerCheckProvider;
import org.keycloak.health.LoadBalancerCheckProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;



public class InfinispanMultiSiteLoadBalancerCheckProviderFactory implements LoadBalancerCheckProviderFactory, EnvironmentDependentProviderFactory {

private LoadBalancerCheckProvider loadBalancerCheckProvider;
private static final LoadBalancerCheckProvider ALWAYS_HEALTHY = new LoadBalancerCheckProvider() {
@Override public boolean isDown() { return false; }
@Override public void close() {}
};
private static final Logger LOG = Logger.getLogger(InfinispanMultiSiteLoadBalancerCheckProviderFactory.class);

@Override
public LoadBalancerCheckProvider create(KeycloakSession session) {
if (loadBalancerCheckProvider == null) {
InfinispanConnectionProvider infinispanConnectionProvider = session.getProvider(InfinispanConnectionProvider.class);
if (infinispanConnectionProvider == null) {
LOG.warn("InfinispanConnectionProvider is not available. Load balancer check will be always healthy for Infinispan.");
loadBalancerCheckProvider = ALWAYS_HEALTHY;
} else {
loadBalancerCheckProvider = new InfinispanMultiSiteLoadBalancerCheckProvider(infinispanConnectionProvider);
}
}
return loadBalancerCheckProvider;
}

@Override
public void init(Config.Scope config) {

}

@Override
public void postInit(KeycloakSessionFactory factory) {

}

@Override
public void close() {

}

@Override
public String getId() {
return "infinispan-multisite";
}

@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.MULTI_SITE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.keycloak.connections.infinispan.InfinispanMultiSiteLoadBalancerCheckProviderFactory
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@LegacyStore
public class FeaturesDistTest {

private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3, admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, dpop, recovery-codes, scripts, token-exchange, update-email";
private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3, admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, dpop, multi-site, recovery-codes, scripts, token-exchange, update-email";

@Test
public void testEnableOnBuild(KeycloakDistribution dist) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.

HTTP/TLS:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.

Config:

Expand Down Expand Up @@ -142,4 +142,4 @@ Export:
--users-per-file <number>
Set the number of users per file. It is used only if 'users' is set to
'different_files'. Increasing this number leads to exponentially increasing
export times. Default: 50.
export times. Default: 50.
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.

Config:

Expand Down Expand Up @@ -142,4 +142,4 @@ Export:
--users-per-file <number>
Set the number of users per file. It is used only if 'users' is set to
'different_files'. Increasing this number leads to exponentially increasing
export times. Default: 50.
export times. Default: 50.
Loading

0 comments on commit 2b2207a

Please sign in to comment.