Add support for forwarding cache eviction for custom caches
Allow to specify cache.pattern in the plugin configuration, which
will add additional patterns to the regex to test if a cache eviction
should be forwarded. This will allow caches created by other plugins
to be handled in addition to the default core caches.
For example:
[cache]
synchronize = true
pattern = ^my_cache.*
pattern = other_cache
Note that evictions for core caches are always forwarded. Specifying
cache.pattern only adds extra matches; it doesn't override forwarding
of eviction for the core caches.
Change-Id: Ia415d53a3c08d744324e88a6f7115a761f94c1f6
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index 6c56a8c..4ae47fe 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -28,6 +28,9 @@
import com.google.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,6 +58,7 @@
// cache section
static final String CACHE_SECTION = "cache";
+ static final String PATTERN_KEY = "pattern";
// event section
static final String EVENT_SECTION = "event";
@@ -242,15 +246,21 @@
public static class Cache extends Forwarding {
private final int threadPoolSize;
+ private final List<String> patterns;
private Cache(Config cfg) {
super(cfg, CACHE_SECTION);
threadPoolSize = getInt(cfg, CACHE_SECTION, THREAD_POOL_SIZE_KEY, DEFAULT_THREAD_POOL_SIZE);
+ patterns = Arrays.asList(cfg.getStringList(CACHE_SECTION, null, PATTERN_KEY));
}
public int threadPoolSize() {
return threadPoolSize;
}
+
+ public List<String> patterns() {
+ return Collections.unmodifiableList(patterns);
+ }
}
public static class Event extends Forwarding {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
index 43e3642..f8d71f3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
@@ -14,9 +14,12 @@
package com.ericsson.gerrit.plugins.highavailability.cache;
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@@ -34,8 +37,11 @@
private final Pattern pattern;
- public CachePatternMatcher() {
- this.pattern = Pattern.compile(Joiner.on("|").join(DEFAULT_PATTERNS));
+ @Inject
+ public CachePatternMatcher(Configuration cfg) {
+ List<String> patterns = new ArrayList<>(DEFAULT_PATTERNS);
+ patterns.addAll(cfg.cache().patterns());
+ this.pattern = Pattern.compile(Joiner.on("|").join(patterns));
}
public boolean matches(String cacheName) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java
index 6b047d3..94c3d30 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java
@@ -15,7 +15,7 @@
package com.ericsson.gerrit.plugins.highavailability.cache;
public final class Constants {
-
+ public static final String GERRIT = "gerrit";
public static final String PROJECT_LIST = "project_list";
public static final String ACCOUNTS = "accounts";
public static final String GROUPS = "groups";
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
index b388b0b..253ff4a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
@@ -19,6 +19,7 @@
import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -37,7 +38,6 @@
class CacheRestApiServlet extends HttpServlet {
private static final int CACHENAME_INDEX = 1;
private static final long serialVersionUID = -1L;
- private static final String GERRIT = "gerrit";
private static final Logger logger = LoggerFactory.getLogger(CacheRestApiServlet.class);
private final DynamicMap<Cache<?, ?>> cacheMap;
@@ -57,10 +57,17 @@
String cacheName = params.get(CACHENAME_INDEX);
String json = req.getReader().readLine();
Object key = GsonParser.fromJson(cacheName, json);
- Cache<?, ?> cache = cacheMap.get(GERRIT, cacheName);
- Context.setForwardedEvent(true);
- evictCache(cache, cacheName, key);
- rsp.setStatus(SC_NO_CONTENT);
+ CacheParameters cacheKey = getCacheParameters(cacheName);
+ Cache<?, ?> cache = cacheMap.get(cacheKey.pluginName, cacheKey.cacheName);
+ if (cache == null) {
+ String msg = String.format("cache %s not found", cacheName);
+ logger.error("Failed to process eviction request: " + msg);
+ sendError(rsp, SC_BAD_REQUEST, msg);
+ } else {
+ Context.setForwardedEvent(true);
+ evictCache(cache, cacheKey.cacheName, key);
+ rsp.setStatus(SC_NO_CONTENT);
+ }
} catch (IOException e) {
logger.error("Failed to process eviction request: " + e.getMessage(), e);
sendError(rsp, SC_BAD_REQUEST, e.getMessage());
@@ -69,6 +76,26 @@
}
}
+ @VisibleForTesting
+ public static class CacheParameters {
+ public final String pluginName;
+ public final String cacheName;
+
+ public CacheParameters(String pluginName, String cacheName) {
+ this.pluginName = pluginName;
+ this.cacheName = cacheName;
+ }
+ }
+
+ @VisibleForTesting
+ public static CacheParameters getCacheParameters(String cache) {
+ int dot = cache.indexOf(".");
+ if (dot > 0) {
+ return new CacheParameters(cache.substring(0, dot), cache.substring(dot + 1));
+ }
+ return new CacheParameters(Constants.GERRIT, cache);
+ }
+
private static void sendError(HttpServletResponse rsp, int statusCode, String message) {
try {
rsp.sendError(statusCode, message);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
index 23f42ab..8a177de 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
@@ -44,7 +44,11 @@
key = gson.fromJson(Strings.nullToEmpty(json), Object.class);
break;
default:
- key = gson.fromJson(Strings.nullToEmpty(json).trim(), String.class);
+ try {
+ key = gson.fromJson(Strings.nullToEmpty(json).trim(), String.class);
+ } catch (Exception e) {
+ key = gson.fromJson(Strings.nullToEmpty(json), Object.class);
+ }
}
return key;
}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 8ec9432..1ab2858 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -62,6 +62,13 @@
: Maximum number of threads used to send cache evictions to the target instance.
Defaults to 1.
+cache.pattern
+: Pattern to match names of custom caches for which evictions should be
+ forwarded (in addition to the core caches that are always forwarded). May be
+ specified more than once to add multiple patterns.
+ Defaults to an empty list, meaning only evictions of the core caches are
+ forwarded.
+
event.synchronize
: Whether to synchronize stream events.
Defaults to true.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index 4ffce0a..cf9e29f 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -29,6 +29,7 @@
import static com.ericsson.gerrit.plugins.highavailability.Configuration.MAIN_SECTION;
import static com.ericsson.gerrit.plugins.highavailability.Configuration.MAX_TRIES_KEY;
import static com.ericsson.gerrit.plugins.highavailability.Configuration.PASSWORD_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.PATTERN_KEY;
import static com.ericsson.gerrit.plugins.highavailability.Configuration.PEER_INFO_SECTION;
import static com.ericsson.gerrit.plugins.highavailability.Configuration.RETRY_INTERVAL_KEY;
import static com.ericsson.gerrit.plugins.highavailability.Configuration.SHARED_DIRECTORY_KEY;
@@ -73,6 +74,7 @@
private static final String RELATIVE_SHARED_DIRECTORY = "relative/dir";
private static final Path SITE_PATH = Paths.get("/site_path");
private static final String ERROR_MESSAGE = "some error message";
+ private static final String[] CUSTOM_CACHE_PATTERNS = {"^my_cache.*", "other"};
@Mock private PluginConfigFactory cfgFactoryMock;
@Mock private Config configMock;
@@ -85,6 +87,8 @@
when(cfgFactoryMock.getGlobalPluginConfig(pluginName)).thenReturn(configMock);
when(configMock.getString(MAIN_SECTION, null, SHARED_DIRECTORY_KEY))
.thenReturn(SHARED_DIRECTORY);
+ when(configMock.getStringList(CACHE_SECTION, null, PATTERN_KEY))
+ .thenReturn(CUSTOM_CACHE_PATTERNS);
site = new SitePaths(SITE_PATH);
}
@@ -333,8 +337,15 @@
@Test
public void testGetCachePatterns() throws Exception {
initializeConfiguration();
- CachePatternMatcher matcher = new CachePatternMatcher();
- for (String cache : ImmutableList.of("accounts_byemail", "ldap_groups", "project_list")) {
+ CachePatternMatcher matcher = new CachePatternMatcher(configuration);
+ for (String cache :
+ ImmutableList.of(
+ "accounts_byemail",
+ "ldap_groups",
+ "project_list",
+ "my_cache_a",
+ "my_cache_b",
+ "other")) {
assertThat(matcher.matches(cache)).isTrue();
}
for (String cache : ImmutableList.of("ldap_groups_by_include", "foo")) {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
index d57c0e1..84ac4db 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
@@ -14,6 +14,7 @@
package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+import static com.google.common.truth.Truth.assertThat;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static org.mockito.Mockito.doThrow;
@@ -22,6 +23,7 @@
import static org.mockito.Mockito.when;
import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.CacheRestApiServlet.CacheParameters;
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.registration.DynamicMap;
import java.io.BufferedReader;
@@ -78,14 +80,15 @@
}
@Test
- public void evictDefault() throws Exception {
- configureMocksFor(Constants.PROJECTS);
+ public void evictPluginCache() throws Exception {
+ configureMocksFor("my-plugin", "my-cache");
verifyResponseIsOK();
}
- private void verifyResponseIsOK() throws Exception {
- servlet.doPost(request, response);
- verify(response).setStatus(SC_NO_CONTENT);
+ @Test
+ public void evictDefault() throws Exception {
+ configureMocksFor(Constants.PROJECTS);
+ verifyResponseIsOK();
}
@Test
@@ -106,10 +109,34 @@
verify(response).sendError(SC_BAD_REQUEST, errorMessage);
}
+ @Test
+ public void cacheParameters() throws Exception {
+ CacheParameters key = CacheRestApiServlet.getCacheParameters("accounts_by_name");
+ assertThat(key.pluginName).isEqualTo(Constants.GERRIT);
+ assertThat(key.cacheName).isEqualTo("accounts_by_name");
+
+ key = CacheRestApiServlet.getCacheParameters("my_plugin.my_cache");
+ assertThat(key.pluginName).isEqualTo("my_plugin");
+ assertThat(key.cacheName).isEqualTo("my_cache");
+ }
+
+ private void verifyResponseIsOK() throws Exception {
+ servlet.doPost(request, response);
+ verify(response).setStatus(SC_NO_CONTENT);
+ }
+
+ private void configureMocksFor(String cacheName) throws Exception {
+ configureMocksFor(Constants.GERRIT, cacheName);
+ }
+
@SuppressWarnings("unchecked")
- private void configureMocksFor(String cacheName) throws IOException {
- when(cacheMap.get("gerrit", cacheName)).thenReturn(mock(Cache.class));
- when(request.getPathInfo()).thenReturn("/" + cacheName);
+ private void configureMocksFor(String pluginName, String cacheName) throws Exception {
+ when(cacheMap.get(pluginName, cacheName)).thenReturn(mock(Cache.class));
+ if (Constants.GERRIT.equals(pluginName)) {
+ when(request.getPathInfo()).thenReturn("/" + cacheName);
+ } else {
+ when(request.getPathInfo()).thenReturn("/" + pluginName + "." + cacheName);
+ }
when(request.getReader()).thenReturn(reader);
if (Constants.PROJECTS.equals(cacheName)) {