Merge branch 'stable-2.16' into stable-3.0 * stable-2.16: Serve reindexing simulating a GET request for /meta ref caching Auto-reload the indexTs file for auto-reindexing Download plugins from archive-ci.gerritforge.com Pin haproxy to 1.8.30-buster and fix associated issues Fix issue with change indexing during the NoteDb online migration Change-Id: I0992d42770ff1e1c546015ff97bfb9f28aed824d
diff --git a/.bazelversion b/.bazelversion index fd2a018..7c69a55 100644 --- a/.bazelversion +++ b/.bazelversion
@@ -1 +1 @@ -3.1.0 +3.7.0
diff --git a/BUILD b/BUILD index 577b49d..c3f5a11 100644 --- a/BUILD +++ b/BUILD
@@ -41,7 +41,6 @@ visibility = ["//visibility:public"], exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ ":high-availability__plugin", - "@mockito//jar", "@wiremock//jar", ], )
diff --git a/README.md b/README.md index 8e7ef27..c8ac4b4 100644 --- a/README.md +++ b/README.md
@@ -1,13 +1,12 @@ # Gerrit high-availability plugin This plugin allows deploying a cluster of multiple Gerrit masters -on the same data-center sharing the same ReviewDb and Git repositories. +on the same data-center sharing the same Git repositories. Requirements for the Gerrit masters are: - Gerrit v2.14.20 or later - Externally mounted filesystem shared among the cluster -- ReviewDb on an external DataBase Server - Load-balancer (HAProxy or similar) ## License @@ -25,12 +24,12 @@ `gerrit-02.mycompany.com`, listening on the HTTP port 8080, with a shared volume mounted under `/shared`, see below the minimal configuration steps. -1. Install one Gerrit master on the first node (e.g. `gerrit-01.mycompany.com`) using an external - ReviewDb on a DB server and the repositories location under the shared volume (e.g. `/shared/git`). - Init the site in order to create the DB Schema and the initial repositories. +1. Install one Gerrit master on the first node (e.g. `gerrit-01.mycompany.com`) + with the repositories location under the shared volume (e.g. `/shared/git`). + Init the site in order to create the initial repositories. 2. Copy all the files of the first Gerrit master onto the second node (e.g. `gerrit-02.mycompany.com`) - so that it points to the same ReviewDb and the same repositories location. + so that it points to the same repositories location. 3. Install the high-availability plugin into the `$GERRIT_SITE/plugins` directory of both the Gerrit servers.
diff --git a/WORKSPACE b/WORKSPACE index 456e679..666572c 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -3,26 +3,15 @@ load("//:bazlets.bzl", "load_bazlets") load_bazlets( - commit = "6c39deb06f58bb62162ccb6865964f531739f512", + commit = "a029d8e41d6211c8b23052aa0a0c2c7649577e85", #local_path = "/home/<user>/projects/bazlets", ) -# Snapshot Plugin API -#load( -# "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl", -# "gerrit_api_maven_local", -#) - -# Load snapshot Plugin API -#gerrit_api_maven_local() - -# Release Plugin API load( "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl", "gerrit_api", ) -# Load release Plugin API gerrit_api() load("//:external_plugin_deps.bzl", "external_plugin_deps")
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl index 12bf919..0f8b43b 100644 --- a/external_plugin_deps.bzl +++ b/external_plugin_deps.bzl
@@ -3,39 +3,8 @@ def external_plugin_deps(): maven_jar( name = "wiremock", - artifact = "com.github.tomakehurst:wiremock-standalone:2.26.3", - sha1 = "245c6efae2cbcb4e4f3457caf3d1c030cbaf2eb5", - ) - - maven_jar( - name = "mockito", - artifact = "org.mockito:mockito-core:2.28.2", - sha1 = "91110215a8cb9b77a46e045ee758f77d79167cc0", - deps = [ - "@byte-buddy//jar", - "@byte-buddy-agent//jar", - "@objenesis//jar", - ], - ) - - BYTE_BUDDY_VERSION = "1.9.10" - - maven_jar( - name = "byte-buddy", - artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION, - sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840", - ) - - maven_jar( - name = "byte-buddy-agent", - artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION, - sha1 = "9674aba5ee793e54b864952b001166848da0f26b", - ) - - maven_jar( - name = "objenesis", - artifact = "org.objenesis:objenesis:2.6", - sha1 = "639033469776fd37c08358c6b92a4761feb2af4b", + artifact = "com.github.tomakehurst:wiremock-standalone:2.27.2", + sha1 = "327647a19b2319af2526b9c33a5733a2241723e0", ) maven_jar(
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 99cc106..093628e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -45,6 +45,9 @@ public class Configuration { private static final FluentLogger log = FluentLogger.forEnclosingClass(); + public static final int DEFAULT_NUM_STRIPED_LOCKS = 10; + public static final int DEFAULT_TIMEOUT_MS = 5000; + // common parameter to peerInfo section static final String PEER_INFO_SECTION = "peerInfo"; @@ -55,7 +58,6 @@ static final int DEFAULT_INDEX_RETRY_INTERVAL = 30000; static final int DEFAULT_THREAD_POOL_SIZE = 4; static final String NUM_STRIPED_LOCKS = "numStripedLocks"; - static final int DEFAULT_NUM_STRIPED_LOCKS = 10; private final Main main; private final AutoReindex autoReindex; @@ -334,6 +336,9 @@ } public static class Http { + public static final int DEFAULT_MAX_TRIES = 360; + public static final int DEFAULT_RETRY_INTERVAL = 10000; + static final String HTTP_SECTION = "http"; static final String USER_KEY = "user"; static final String PASSWORD_KEY = "password"; @@ -342,10 +347,6 @@ static final String MAX_TRIES_KEY = "maxTries"; static final String RETRY_INTERVAL_KEY = "retryInterval"; - static final int DEFAULT_TIMEOUT_MS = 5000; - static final int DEFAULT_MAX_TRIES = 360; - static final int DEFAULT_RETRY_INTERVAL = 10000; - private final String user; private final String password; private final int connectionTimeout; @@ -447,6 +448,7 @@ public static class Index extends Forwarding { static final String INDEX_SECTION = "index"; static final String MAX_TRIES_KEY = "maxTries"; + static final String WAIT_TIMEOUT_KEY = "waitTimeout"; static final String RETRY_INTERVAL_KEY = "retryInterval"; static final String SYNCHRONIZE_FORCED_KEY = "synchronizeForced"; static final boolean DEFAULT_SYNCHRONIZE_FORCED = true;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java index 6635b80..8a267d6 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
@@ -24,6 +24,7 @@ import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.CACHE_SECTION; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE; +import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY; @@ -33,7 +34,6 @@ import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL; -import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java index de156a2..eb65106 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
@@ -19,11 +19,9 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet; import com.google.common.flogger.FluentLogger; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.Accounts; import com.google.gerrit.server.util.OneOffRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import java.io.IOException; import java.sql.Timestamp; @@ -48,12 +46,12 @@ } @Override - protected Iterable<AccountState> fetchItems(ReviewDb db) throws Exception { + protected Iterable<AccountState> fetchItems() throws Exception { return accounts.all(); } @Override - protected Optional<Timestamp> indexIfNeeded(ReviewDb db, AccountState as, Timestamp sinceTs) { + protected Optional<Timestamp> indexIfNeeded(AccountState as, Timestamp sinceTs) { try { Account a = as.getAccount(); Timestamp accountTs = a.getRegisteredOn(); @@ -63,7 +61,7 @@ accountIdx.index(a.getId(), Operation.INDEX, Optional.empty()); return Optional.of(accountTs); } - } catch (IOException | OrmException e) { + } catch (IOException e) { log.atSevere().withCause(e).log("Reindex failed"); } return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java index 67c8325..dd323b4 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
@@ -21,13 +21,11 @@ import com.google.common.flogger.FluentLogger; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.util.OneOffRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import java.io.IOException; import java.sql.Timestamp; @@ -77,14 +75,14 @@ } @Override - protected Iterable<Change> fetchItems(ReviewDb db) throws Exception { + protected Iterable<Change> fetchItems() throws Exception { Stream<Change> allChangesStream = Stream.empty(); Iterable<Project.NameKey> projects = projectCache.all(); for (Project.NameKey projectName : projects) { try (Repository repo = repoManager.openRepository(projectName)) { Stream<Change> projectChangesStream = notesFactory - .scan(repo, db, projectName) + .scan(repo, projectName) .map((ChangeNotesResult changeNotes) -> changeNotes.notes().getChange()); allChangesStream = Streams.concat(allChangesStream, projectChangesStream); } @@ -93,7 +91,7 @@ } @Override - protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Change c, Timestamp sinceTs) { + protected Optional<Timestamp> indexIfNeeded(Change c, Timestamp sinceTs) { try { Timestamp changeTs = c.getLastUpdatedOn(); if (changeTs.after(sinceTs)) { @@ -102,7 +100,7 @@ changeIdx.index(c.getProject() + "~" + c.getId(), Operation.INDEX, Optional.empty()); return Optional.of(changeTs); } - } catch (OrmException | IOException e) { + } catch (IOException e) { log.atSevere().withCause(e).log("Reindex failed"); } return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java index 53bf774..35445cc 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -22,13 +22,11 @@ import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.reviewdb.client.AccountGroupByIdAud; import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.group.InternalGroup; import com.google.gerrit.server.group.db.Groups; import com.google.gerrit.server.util.OneOffRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import java.io.IOException; import java.sql.Timestamp; @@ -65,12 +63,12 @@ } @Override - protected Iterable<GroupReference> fetchItems(ReviewDb db) throws Exception { + protected Iterable<GroupReference> fetchItems() throws Exception { return groups.getAllGroupReferences()::iterator; } @Override - protected Optional<Timestamp> indexIfNeeded(ReviewDb db, GroupReference g, Timestamp sinceTs) { + protected Optional<Timestamp> indexIfNeeded(GroupReference g, Timestamp sinceTs) { try { Optional<InternalGroup> internalGroup = groups.getGroup(g.getUUID()); if (internalGroup.isPresent()) { @@ -116,7 +114,7 @@ return groupLastTs; } } - } catch (OrmException | IOException | ConfigInvalidException e) { + } catch (IOException | ConfigInvalidException e) { log.atSevere().withCause(e).log("Reindex failed"); } return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java index f1783e7..b4db602 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
@@ -23,11 +23,9 @@ import com.google.gerrit.extensions.events.ChangeIndexedListener; import com.google.gerrit.extensions.events.GroupIndexedListener; import com.google.gerrit.extensions.events.ProjectIndexedListener; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; @@ -55,7 +53,6 @@ private final FlusherRunner accountFlusher; private final FlusherRunner groupFlusher; private final FlusherRunner projectFlusher; - private final SchemaFactory<ReviewDb> schemaFactory; private final ChangeFinder changeFinder; private final CurrentRequestContext currCtx; @@ -134,7 +131,6 @@ public IndexTs( @PluginData Path dataDir, WorkQueue queue, - SchemaFactory<ReviewDb> schemaFactory, ChangeFinder changeFinder, CurrentRequestContext currCtx) { this.dataDir = dataDir; @@ -143,7 +139,6 @@ this.accountFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.ACCOUNT); this.groupFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.GROUP); this.projectFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.PROJECT); - this.schemaFactory = schemaFactory; this.changeFinder = changeFinder; this.currCtx = currCtx; } @@ -167,7 +162,7 @@ public void onChangeIndexed(String projectName, int id) { currCtx.onlyWithContext( (ctx) -> { - try (ReviewDb db = schemaFactory.open()) { + try { ChangeNotes changeNotes = changeFinder.findOne(projectName + "~" + id); update( IndexName.CHANGE,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java index 582227d..ff5a965 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
@@ -16,7 +16,6 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet; import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject; @@ -35,12 +34,12 @@ } @Override - protected Iterable<Project.NameKey> fetchItems(ReviewDb db) { + protected Iterable<Project.NameKey> fetchItems() { return projectCache.all(); } @Override - protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Project.NameKey g, Timestamp sinceTs) { + protected Optional<Timestamp> indexIfNeeded(Project.NameKey g, Timestamp sinceTs) { return Optional.empty(); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java index 7a5669e..3cb0ed0 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
@@ -17,7 +17,6 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet; import com.google.common.base.Stopwatch; import com.google.common.flogger.FluentLogger; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.util.ManualRequestContext; import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject; @@ -27,6 +26,7 @@ import java.util.concurrent.TimeUnit; abstract class ReindexRunnable<T> implements Runnable { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final AbstractIndexRestApiServlet.IndexName itemName; @@ -49,14 +49,13 @@ if (maybeIndexTs.isPresent()) { newLastIndexTs = maxTimestamp(newLastIndexTs, Timestamp.valueOf(maybeIndexTs.get())); log.atFine().log("Scanning for all the %ss after %s", itemNameString, newLastIndexTs); - try (ManualRequestContext mctx = ctx.open(); - ReviewDb db = mctx.getReviewDbProvider().get()) { + try (ManualRequestContext mctx = ctx.open()) { int count = 0; int errors = 0; Stopwatch stopwatch = Stopwatch.createStarted(); - for (T c : fetchItems(db)) { + for (T c : fetchItems()) { try { - Optional<Timestamp> itemTs = indexIfNeeded(db, c, newLastIndexTs); + Optional<Timestamp> itemTs = indexIfNeeded(c, newLastIndexTs); if (itemTs.isPresent()) { count++; newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get()); @@ -102,7 +101,7 @@ return ts2; } - protected abstract Iterable<T> fetchItems(ReviewDb db) throws Exception; + protected abstract Iterable<T> fetchItems() throws Exception; - protected abstract Optional<Timestamp> indexIfNeeded(ReviewDb db, T item, Timestamp sinceTs); + protected abstract Optional<Timestamp> indexIfNeeded(T item, Timestamp sinceTs); }
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 27cb5c2..0f43eaf 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
@@ -26,14 +26,18 @@ @Singleton class CachePatternMatcher { private static final List<String> DEFAULT_PATTERNS = - ImmutableList.of( - "accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys", "web_sessions"); + ImmutableList.of("accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys"); private final Pattern pattern; @Inject CachePatternMatcher(Configuration cfg) { List<String> patterns = new ArrayList<>(DEFAULT_PATTERNS); + + if (cfg.websession().synchronize()) { + patterns.add("web_sessions"); + } + patterns.addAll(cfg.cache().patterns()); this.pattern = Pattern.compile(Joiner.on("|").join(patterns)); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java new file mode 100644 index 0000000..8c00282 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java
@@ -0,0 +1,44 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// 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 com.ericsson.gerrit.plugins.highavailability.event; + +import com.google.gerrit.reviewdb.client.Project; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; + +public class ProjectNameKeyAdapter + implements JsonSerializer<Project.NameKey>, JsonDeserializer<Project.NameKey> { + @Override + public JsonElement serialize( + Project.NameKey project, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(project.get()); + } + + @Override + public Project.NameKey deserialize( + JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json.isJsonObject()) { + return Project.nameKey(json.getAsJsonObject().get("name").getAsJsonPrimitive().getAsString()); + } + return Project.nameKey(json.getAsString()); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java index 3d9fd60..f156da4 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
@@ -14,7 +14,6 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventBroker; import com.google.gerrit.server.events.EventListener; @@ -24,7 +23,6 @@ import com.google.gerrit.server.plugincontext.PluginSetContext; import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; -import com.google.inject.Provider; class ForwardedAwareEventBroker extends EventBroker { @@ -34,15 +32,8 @@ PluginSetContext<EventListener> unrestrictedListeners, PermissionBackend permissionBackend, ProjectCache projectCache, - Factory notesFactory, - Provider<ReviewDb> dbProvider) { - super( - listeners, - unrestrictedListeners, - permissionBackend, - projectCache, - notesFactory, - dbProvider); + Factory notesFactory) { + super(listeners, unrestrictedListeners, permissionBackend, projectCache, notesFactory); } @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java index 51db006..414e795 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
@@ -18,7 +18,6 @@ import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventDispatcher; import com.google.gerrit.server.permissions.PermissionBackendException; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -42,9 +41,8 @@ * Dispatch an event in the local node, event will not be forwarded to the other node. * * @param event The event to dispatch - * @throws OrmException If an error occur while retrieving the change the event belongs to. */ - public void dispatch(Event event) throws OrmException, PermissionBackendException { + public void dispatch(Event event) throws PermissionBackendException { try { Context.setForwardedEvent(true); log.atFine().log("dispatching event %s", event.getType());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java index ba60830..f31331d 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
@@ -14,12 +14,10 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.index.account.AccountIndexer; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.io.IOException; import java.util.Optional; /** @@ -33,13 +31,12 @@ private final AccountIndexer indexer; @Inject - ForwardedIndexAccountHandler(AccountIndexer indexer, Configuration config) { - super(config.index()); + ForwardedIndexAccountHandler(AccountIndexer indexer) { this.indexer = indexer; } @Override - protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) throws IOException { + protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) { indexer.index(id); log.atFine().log("Account %s successfully indexed", id); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java index bb1128a..dee8876 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java
@@ -16,7 +16,6 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl.Factory; -import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb; import com.ericsson.gerrit.plugins.highavailability.index.ForwardedBatchIndexExecutor; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.util.OneOffRequestContext; @@ -30,13 +29,10 @@ @Inject ForwardedIndexBatchChangeHandler( ChangeIndexer indexer, - ChangeDb changeDb, Configuration config, @ForwardedBatchIndexExecutor ScheduledExecutorService indexExecutor, OneOffRequestContext oneOffCtx, - Factory changeCheckerFactory, - SingleChangeNoteDbMigrator noteDbMigration) { - super( - indexer, changeDb, config, indexExecutor, oneOffCtx, changeCheckerFactory, noteDbMigration); + Factory changeCheckerFactory) { + super(indexer, config, indexExecutor, oneOffCtx, changeCheckerFactory); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java index d2c0f85..42f3cef 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -18,18 +18,15 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration.Index; import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker; import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl; -import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb; import com.ericsson.gerrit.plugins.highavailability.index.ForwardedIndexExecutor; import com.google.common.base.Splitter; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.util.ManualRequestContext; import com.google.gerrit.server.util.OneOffRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; @@ -47,30 +44,23 @@ @Singleton public class ForwardedIndexChangeHandler extends ForwardedIndexingHandler<String> { private final ChangeIndexer indexer; - private final ChangeDb changeDb; private final ScheduledExecutorService indexExecutor; private final OneOffRequestContext oneOffCtx; private final int retryInterval; private final int maxTries; private final ChangeCheckerImpl.Factory changeCheckerFactory; - private final SingleChangeNoteDbMigrator noteDbMigration; @Inject ForwardedIndexChangeHandler( ChangeIndexer indexer, - ChangeDb changeDb, Configuration config, @ForwardedIndexExecutor ScheduledExecutorService indexExecutor, OneOffRequestContext oneOffCtx, - ChangeCheckerImpl.Factory changeCheckerFactory, - SingleChangeNoteDbMigrator noteDbMigration) { - super(config.index()); + ChangeCheckerImpl.Factory changeCheckerFactory) { this.indexer = indexer; - this.changeDb = changeDb; this.indexExecutor = indexExecutor; this.oneOffCtx = oneOffCtx; this.changeCheckerFactory = changeCheckerFactory; - this.noteDbMigration = noteDbMigration; Index indexConfig = config.index(); this.retryInterval = indexConfig != null ? indexConfig.retryInterval() : 0; @@ -78,19 +68,12 @@ } @Override - protected void doIndex(String id, Optional<IndexEvent> indexEvent) - throws IOException, OrmException { - try { - noteDbMigration.migrate(parseChangeId(id), parseProject(id)); - doIndex(id, indexEvent, 0); - } catch (SingleChangeNoteDbMigrationException e) { - log.atSevere().withCause(e).log( - "Migration for change %s, project %s " + "skipped.", parseChangeId(id), parseProject(id)); - } + protected void doIndex(String id, Optional<IndexEvent> indexEvent) throws IOException { + doIndex(id, indexEvent, 0); } private void doIndex(String id, Optional<IndexEvent> indexEvent, int retryCount) - throws IOException, OrmException { + throws IOException { try { ChangeChecker checker = changeCheckerFactory.create(id); Optional<ChangeNotes> changeNotes = checker.getChangeNotes(); @@ -132,11 +115,9 @@ } } - private void reindex(ChangeNotes notes) throws IOException, OrmException { - try (ReviewDb db = changeDb.open()) { - notes.reload(); - indexer.index(db, notes.getChange()); - } + private void reindex(ChangeNotes notes) { + notes.reload(); + indexer.index(notes.getChange()); } private boolean rescheduleIndex(String id, Optional<IndexEvent> indexEvent, int retryCount) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java index fb4d2f2..127af36 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
@@ -14,12 +14,10 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.index.group.GroupIndexer; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.io.IOException; import java.util.Optional; /** @@ -33,14 +31,12 @@ private final GroupIndexer indexer; @Inject - ForwardedIndexGroupHandler(GroupIndexer indexer, Configuration config) { - super(config.index()); + ForwardedIndexGroupHandler(GroupIndexer indexer) { this.indexer = indexer; } @Override - protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent) - throws IOException { + protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent) { indexer.index(uuid); log.atFine().log("Group %s successfully indexed", uuid); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java index 5ae4cc0..b6daf41 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
@@ -14,12 +14,10 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.google.gerrit.index.project.ProjectIndexer; import com.google.gerrit.reviewdb.client.Project; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.io.IOException; import java.util.Optional; /** @@ -33,14 +31,12 @@ private final ProjectIndexer indexer; @Inject - ForwardedIndexProjectHandler(ProjectIndexer indexer, Configuration config) { - super(config.index()); + ForwardedIndexProjectHandler(ProjectIndexer indexer) { this.indexer = indexer; } @Override - protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent) - throws IOException { + protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent) { indexer.index(projectName); log.atFine().log("Project %s successfully indexed", projectName); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java index 78e103d..44e35a8 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
@@ -14,13 +14,12 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.google.common.flogger.FluentLogger; -import com.google.common.util.concurrent.Striped; -import com.google.gwtorm.server.OrmException; import java.io.IOException; +import java.util.Collections; import java.util.Optional; -import java.util.concurrent.locks.Lock; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Base class to handle forwarded indexing. This class is meant to be extended by classes used on @@ -30,6 +29,7 @@ */ public abstract class ForwardedIndexingHandler<T> { protected static final FluentLogger log = FluentLogger.forEnclosingClass(); + private final Set<T> inFlightIndexing = Collections.newSetFromMap(new ConcurrentHashMap<>()); public enum Operation { INDEX, @@ -41,17 +41,10 @@ } } - private final Striped<Lock> idLocks; - - protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent) - throws IOException, OrmException; + protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent) throws IOException; protected abstract void doDelete(T id, Optional<IndexEvent> indexEvent) throws IOException; - protected ForwardedIndexingHandler(Configuration.Index indexConfig) { - idLocks = Striped.lock(indexConfig.numStripedLocks()); - } - /** * Index an item in the local node, indexing will not be forwarded to the other node. * @@ -59,16 +52,12 @@ * @param operation The operation to do; index or delete * @param indexEvent The index event details. * @throws IOException If an error occur while indexing. - * @throws OrmException If an error occur while retrieving a change related to the item to index */ - public void index(T id, Operation operation, Optional<IndexEvent> indexEvent) - throws IOException, OrmException { + public void index(T id, Operation operation, Optional<IndexEvent> indexEvent) throws IOException { log.atFine().log("%s %s %s", operation, id, indexEvent); - try { - Context.setForwardedEvent(true); - Lock idLock = idLocks.get(id); - idLock.lock(); + if (inFlightIndexing.add(id)) { try { + Context.setForwardedEvent(true); switch (operation) { case INDEX: doIndex(id, indexEvent); @@ -81,10 +70,12 @@ break; } } finally { - idLock.unlock(); + Context.unsetForwardedEvent(); + inFlightIndexing.remove(id); } - } finally { - Context.unsetForwardedEvent(); + } else { + throw new InFlightIndexedException( + String.format("Indexing for %s %s %s already in flight", operation, id, indexEvent)); } } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java index 851d7e8..b73b676 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -15,6 +15,7 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; import com.google.gerrit.server.events.Event; +import java.util.concurrent.CompletableFuture; /** Forward indexing, stream events and cache evictions to the other master */ public interface Forwarder { @@ -24,9 +25,10 @@ * * @param accountId the account to index. * @param indexEvent the details of the index event. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean indexAccount(int accountId, IndexEvent indexEvent); + CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent); /** * Forward a change indexing event to the other master. @@ -34,9 +36,10 @@ * @param projectName the project of the change to index. * @param changeId the change to index. * @param indexEvent the details of the index event. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean indexChange(String projectName, int changeId, IndexEvent indexEvent); + CompletableFuture<Boolean> indexChange(String projectName, int changeId, IndexEvent indexEvent); /** * Forward a change indexing event to the other master using batch index endpoint. @@ -44,67 +47,76 @@ * @param projectName the project of the change to index. * @param changeId the change to index. * @param indexEvent the details of the index event. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean batchIndexChange(String projectName, int changeId, IndexEvent indexEvent); + CompletableFuture<Boolean> batchIndexChange( + String projectName, int changeId, IndexEvent indexEvent); /** * Forward a delete change from index event to the other master. * * @param changeId the change to remove from the index. * @param indexEvent the details of the index event. - * @return rue if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean deleteChangeFromIndex(int changeId, IndexEvent indexEvent); + CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent); /** * Forward a group indexing event to the other master. * * @param uuid the group to index. * @param indexEvent the details of the index event. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean indexGroup(String uuid, IndexEvent indexEvent); + CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent); /** * Forward a project indexing event to the other master. * * @param projectName the project to index. * @param indexEvent the details of the index event. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean indexProject(String projectName, IndexEvent indexEvent); + CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent); /** * Forward a stream event to the other master. * * @param event the event to forward. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean send(Event event); + CompletableFuture<Boolean> send(Event event); /** * Forward a cache eviction event to the other master. * * @param cacheName the name of the cache to evict an entry from. * @param key the key identifying the entry to evict from the cache. - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean evict(String cacheName, Object key); + CompletableFuture<Boolean> evict(String cacheName, Object key); /** * Forward an addition to the project list cache to the other master. * * @param projectName the name of the project to add to the project list cache - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean addToProjectList(String projectName); + CompletableFuture<Boolean> addToProjectList(String projectName); /** * Forward a removal from the project list cache to the other master. * * @param projectName the name of the project to remove from the project list cache - * @return true if successful, otherwise false. + * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of + * false. */ - boolean removeFromProjectList(String projectName); + CompletableFuture<Boolean> removeFromProjectList(String projectName); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java new file mode 100644 index 0000000..87d28b3 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java
@@ -0,0 +1,26 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// 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 com.ericsson.gerrit.plugins.highavailability.forwarder; + +import java.io.IOException; + +public class InFlightIndexedException extends IOException { + + private static final long serialVersionUID = 1L; + + public InFlightIndexedException(String msg) { + super(msg); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrationException.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrationException.java deleted file mode 100644 index 3ae5e4f..0000000 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrationException.java +++ /dev/null
@@ -1,31 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// 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 com.ericsson.gerrit.plugins.highavailability.forwarder; - -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.Project; -import java.util.Arrays; - -public class SingleChangeNoteDbMigrationException extends Exception { - private static final long serialVersionUID = 1L; - - public SingleChangeNoteDbMigrationException( - Change.Id changeId, Project.NameKey project, Throwable t) { - super( - String.format( - "Error in NoteDb migration for change: %s," + " project: %s. %s", - changeId, project, Arrays.toString(t.getStackTrace()))); - } -}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrator.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrator.java deleted file mode 100644 index 0d12960..0000000 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigrator.java +++ /dev/null
@@ -1,80 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// 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 com.ericsson.gerrit.plugins.highavailability.forwarder; - -import com.google.common.collect.ImmutableSet; -import com.google.common.flogger.FluentLogger; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.Change.Id; -import com.google.gerrit.reviewdb.client.Project.NameKey; -import com.google.gerrit.reviewdb.client.RefNames; -import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.notedb.NotesMigration; -import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator; -import com.google.inject.Inject; -import com.google.inject.Provider; -import java.io.IOException; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Repository; - -class SingleChangeNoteDbMigrator { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private final Provider<NoteDbMigrator.Builder> migratorBuilderProvider; - private final NotesMigration migration; - private final GitRepositoryManager repoManager; - private boolean trial; - - @Inject - SingleChangeNoteDbMigrator( - Provider<NoteDbMigrator.Builder> migratorBuilderProvider, - @GerritServerConfig Config cfg, - NotesMigration migration, - GitRepositoryManager repoManager) { - this.migratorBuilderProvider = migratorBuilderProvider; - this.migration = migration; - this.repoManager = repoManager; - this.trial = NoteDbMigrator.getTrialMode(cfg); - } - - public void migrate(Change.Id id, NameKey project) throws SingleChangeNoteDbMigrationException { - if (migration.readChanges() - && !migration.disableChangeReviewDb() - && !metaRefExists(id, project)) { - try (NoteDbMigrator migrator = - migratorBuilderProvider - .get() - .setThreads(1) - .setAutoMigrate(true) - .setTrialMode(trial) - .setChanges(ImmutableSet.of(id)) - .build()) { - migrator.rebuild(); - } catch (Exception e) { - throw new SingleChangeNoteDbMigrationException(id, project, e); - } - } - } - - private boolean metaRefExists(Id id, NameKey project) - throws SingleChangeNoteDbMigrationException { - try (Repository repo = repoManager.openRepository(project)) { - String metaRef = RefNames.changeMetaRef(id); - return repo.getRefDatabase().exactRef(metaRef) != null; - } catch (IOException e) { - throw new SingleChangeNoteDbMigrationException(id, project, e); - } - } -}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java index 5e25d88..09d4603 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -16,7 +16,6 @@ import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler; @@ -26,7 +25,6 @@ import com.google.gerrit.server.cache.PerThreadCache; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gwtorm.server.OrmException; import java.io.IOException; import java.io.InputStreamReader; import java.util.Optional; @@ -114,10 +112,6 @@ } catch (IOException e) { sendError(rsp, SC_CONFLICT, e.getMessage()); log.atSevere().withCause(e).log("Unable to update %s index", indexName); - } catch (OrmException e) { - String msg = String.format("Error trying to find %s", indexName); - sendError(rsp, SC_NOT_FOUND, msg); - log.atFine().withCause(e).log(msg); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java index 61b714c..3d2d25e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -16,7 +16,6 @@ import static com.google.common.net.MediaType.JSON_UTF_8; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; @@ -25,7 +24,6 @@ import com.google.common.net.MediaType; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.permissions.PermissionBackendException; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; @@ -55,9 +53,6 @@ } forwardedEventHandler.dispatch(getEventFromRequest(req)); rsp.setStatus(SC_NO_CONTENT); - } catch (OrmException e) { - log.atFine().withCause(e).log("Error trying to find a change"); - sendError(rsp, SC_NOT_FOUND, "Change not found\n"); } catch (IOException | PermissionBackendException e) { log.atSevere().withCause(e).log("Unable to re-trigger event"); sendError(rsp, SC_BAD_REQUEST, e.getMessage());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java index e62cec2..c600019 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
@@ -14,10 +14,13 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.event.ProjectNameKeyAdapter; import com.google.common.base.Supplier; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventDeserializer; import com.google.gerrit.server.events.SupplierDeserializer; +import com.google.gerrit.server.events.SupplierSerializer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.inject.Provider; @@ -28,6 +31,8 @@ public Gson get() { return new GsonBuilder() .registerTypeAdapter(Event.class, new EventDeserializer()) + .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeyAdapter()) + .registerTypeAdapter(Supplier.class, new SupplierSerializer()) .registerTypeAdapter(Supplier.class, new SupplierDeserializer()) .create(); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java index f2ac080..cf98aae 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -15,11 +15,9 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult; -import com.google.common.base.Supplier; +import com.google.common.annotations.VisibleForTesting; import com.google.common.net.MediaType; -import com.google.gerrit.server.events.SupplierSerializer; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.inject.Inject; import java.io.IOException; import java.net.URI; @@ -32,12 +30,12 @@ class HttpSession { private final CloseableHttpClient httpClient; - private final Gson gson = - new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create(); + private final Gson gson; @Inject - HttpSession(CloseableHttpClient httpClient) { + HttpSession(CloseableHttpClient httpClient, GsonProvider gsonProvider) { this.httpClient = httpClient; + this.gson = gsonProvider.get(); } HttpResult post(String uri) throws IOException { @@ -67,7 +65,8 @@ } } - private String jsonEncode(Object content) { + @VisibleForTesting + String jsonEncode(Object content) { if (content instanceof String) { return (String) content; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java index 52065ed..6426b5a 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -28,10 +28,8 @@ import com.google.inject.Inject; import com.google.inject.Provider; import java.io.IOException; -import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import javax.net.ssl.SSLException; import org.apache.http.HttpException; import org.apache.http.client.ClientProtocolException; @@ -49,6 +47,7 @@ private final Configuration cfg; private final Provider<Set<PeerInfo>> peerInfoProvider; private final GsonProvider gson; + private final RestForwarderScheduler scheduler; @Inject RestForwarder( @@ -56,21 +55,24 @@ @PluginName String pluginName, Configuration cfg, Provider<Set<PeerInfo>> peerInfoProvider, - GsonProvider gson) { + GsonProvider gson, + RestForwarderScheduler scheduler) { this.httpSession = httpClient; this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName); this.cfg = cfg; this.peerInfoProvider = peerInfoProvider; this.gson = gson; + this.scheduler = scheduler; } @Override - public boolean indexAccount(final int accountId, IndexEvent event) { + public CompletableFuture<Boolean> indexAccount(final int accountId, IndexEvent event) { return execute(RequestMethod.POST, "index account", "index/account", accountId, event); } @Override - public boolean indexChange(String projectName, int changeId, IndexEvent event) { + public CompletableFuture<Boolean> indexChange( + String projectName, int changeId, IndexEvent event) { return execute( RequestMethod.POST, "index change", @@ -79,8 +81,8 @@ event); } - @Override - public boolean batchIndexChange(String projectName, int changeId, IndexEvent event) { + public CompletableFuture<Boolean> batchIndexChange( + String projectName, int changeId, IndexEvent event) { return execute( RequestMethod.POST, "index change", @@ -90,13 +92,13 @@ } @Override - public boolean deleteChangeFromIndex(final int changeId, IndexEvent event) { + public CompletableFuture<Boolean> deleteChangeFromIndex(final int changeId, IndexEvent event) { return execute( RequestMethod.DELETE, "delete change", "index/change", buildIndexEndpoint(changeId), event); } @Override - public boolean indexGroup(final String uuid, IndexEvent event) { + public CompletableFuture<Boolean> indexGroup(final String uuid, IndexEvent event) { return execute(RequestMethod.POST, "index group", "index/group", uuid, event); } @@ -110,24 +112,24 @@ } @Override - public boolean indexProject(String projectName, IndexEvent event) { + public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent event) { return execute( RequestMethod.POST, "index project", "index/project", Url.encode(projectName), event); } @Override - public boolean send(final Event event) { + public CompletableFuture<Boolean> send(final Event event) { return execute(RequestMethod.POST, "send event", "event", event.type, event); } @Override - public boolean evict(final String cacheName, final Object key) { + public CompletableFuture<Boolean> evict(final String cacheName, final Object key) { String json = gson.get().toJson(key); return execute(RequestMethod.POST, "invalidate cache " + cacheName, "cache", cacheName, json); } @Override - public boolean addToProjectList(String projectName) { + public CompletableFuture<Boolean> addToProjectList(String projectName) { return execute( RequestMethod.POST, "Update project_list, add ", @@ -136,7 +138,7 @@ } @Override - public boolean removeFromProjectList(String projectName) { + public CompletableFuture<Boolean> removeFromProjectList(String projectName) { return execute( RequestMethod.DELETE, "Update project_list, remove ", @@ -148,19 +150,19 @@ return Joiner.on("/").join("cache", Constants.PROJECT_LIST); } - private boolean execute(RequestMethod method, String action, String endpoint, Object id) { + private CompletableFuture<Boolean> execute( + RequestMethod method, String action, String endpoint, Object id) { return execute(method, action, endpoint, id, null); } - private boolean execute( + private CompletableFuture<Boolean> execute( RequestMethod method, String action, String endpoint, Object id, Object payload) { - List<CompletableFuture<Boolean>> futures = - peerInfoProvider.get().stream() - .map(peer -> createRequest(method, peer, action, endpoint, id, payload)) - .map(request -> CompletableFuture.supplyAsync(request::execute)) - .collect(Collectors.toList()); - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - return futures.stream().allMatch(CompletableFuture::join); + return peerInfoProvider.get().stream() + .map(peer -> createRequest(method, peer, action, endpoint, id, payload)) + .map(scheduler::execute) + .reduce( + CompletableFuture.completedFuture(true), + (a, b) -> a.thenCombine(b, (left, right) -> left && right)); } private Request createRequest( @@ -186,7 +188,7 @@ }; } - private abstract class Request { + protected abstract class Request { private final String action; private final Object key; private final String destination; @@ -199,42 +201,36 @@ this.destination = destination; } - boolean execute() { - log.atFine().log("Executing %s %s towards %s", action, key, destination); - for (; ; ) { - try { - execCnt++; - tryOnce(); - log.atFine().log("%s %s towards %s OK", action, key, destination); - return true; - } catch (ForwardingException e) { - int maxTries = cfg.http().maxTries(); - log.atFine().withCause(e).log( - "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries); - if (!e.isRecoverable()) { - log.atSevere().withCause(e).log( - "%s %s towards %s failed with unrecoverable error; giving up", - action, key, destination); - return false; - } - if (execCnt >= maxTries) { - log.atSevere().log( - "Failed to %s %s on %s after %d tries; giving up", - action, key, destination, maxTries); - return false; - } + @Override + public String toString() { + return String.format("%s:%s => %s (try #%d)", action, key, destination, execCnt); + } - log.atFine().log("Retrying to %s %s on %s", action, key, destination); - try { - Thread.sleep(cfg.http().retryInterval()); - } catch (InterruptedException ie) { - log.atSevere().withCause(ie).log( - "%s %s towards %s was interrupted; giving up", action, key, destination); - Thread.currentThread().interrupt(); - return false; - } + boolean execute() throws ForwardingException { + log.atFine().log("Executing %s %s towards %s", action, key, destination); + try { + execCnt++; + tryOnce(); + log.atFine().log("%s %s towards %s OK", action, key, destination); + return true; + } catch (ForwardingException e) { + int maxTries = cfg.http().maxTries(); + log.atFine().withCause(e).log( + "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries); + if (!e.isRecoverable()) { + log.atSevere().withCause(e).log( + "%s %s towards %s failed with unrecoverable error; giving up", + action, key, destination); + throw e; + } + if (execCnt >= maxTries) { + log.atSevere().log( + "Failed to %s %s on %s after %d tries; giving up", + action, key, destination, maxTries); + throw e; } } + return false; } void tryOnce() throws ForwardingException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java new file mode 100644 index 0000000..ebbd940 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java
@@ -0,0 +1,146 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest; + +import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.server.git.WorkQueue; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +@Singleton +public class RestForwarderScheduler { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + private final ScheduledExecutorService executor; + private final long retryIntervalMs; + + public class CompletablePromise<V> extends CompletableFuture<V> { + private Future<V> future; + + public CompletablePromise(Future<V> future) { + this.future = future; + executor.execute(this::tryToComplete); + } + + private void tryToComplete() { + if (future.isDone()) { + try { + complete(future.get()); + } catch (InterruptedException e) { + completeExceptionally(e); + } catch (ExecutionException e) { + completeExceptionally(e.getCause()); + } + return; + } + + if (future.isCancelled()) { + cancel(true); + return; + } + + executor.execute(this::tryToComplete); + } + } + + @Inject + public RestForwarderScheduler( + WorkQueue workQueue, Configuration cfg, Provider<Set<PeerInfo>> peerInfoProvider) { + int executorSize = peerInfoProvider.get().size() * cfg.index().threadPoolSize(); + retryIntervalMs = cfg.index().retryInterval(); + this.executor = workQueue.createQueue(executorSize, "RestForwarderScheduler"); + } + + @VisibleForTesting + public RestForwarderScheduler(ScheduledExecutorService executor) { + this.executor = executor; + retryIntervalMs = 0; + } + + public CompletableFuture<Boolean> execute(RestForwarder.Request request) { + return execute(request, 0); + } + + public CompletableFuture<Boolean> execute(RestForwarder.Request request, long delayMs) { + return supplyAsync( + request.toString(), + () -> { + try { + if (!request.execute()) { + log.atWarning().log( + "Rescheduling %s for retry after %d msec", request, retryIntervalMs); + return execute(request, retryIntervalMs); + } + return CompletableFuture.completedFuture(true); + } catch (ForwardingException e) { + log.atSevere().withCause(e).log("Forwarding of %s has failed", request); + return CompletableFuture.completedFuture(false); + } + }, + executor, + delayMs); + } + + private CompletableFuture<Boolean> supplyAsync( + String taskName, + Supplier<CompletableFuture<Boolean>> fn, + ScheduledExecutorService executor, + long delayMs) { + BooleanAsyncSupplier asyncSupplier = new BooleanAsyncSupplier(taskName, fn); + executor.schedule(asyncSupplier, delayMs, TimeUnit.MILLISECONDS); + return asyncSupplier.future(); + } + + static class BooleanAsyncSupplier implements Runnable { + private CompletableFuture<CompletableFuture<Boolean>> dep; + private Supplier<CompletableFuture<Boolean>> fn; + private String taskName; + + BooleanAsyncSupplier(String taskName, Supplier<CompletableFuture<Boolean>> fn) { + this.taskName = taskName; + this.dep = new CompletableFuture<>(); + this.fn = fn; + } + + public CompletableFuture<Boolean> future() { + return dep.thenCompose(Function.identity()); + } + + @Override + public void run() { + try { + dep.complete(fn.get()); + } catch (Throwable ex) { + dep.completeExceptionally(ex); + } + } + + @Override + public String toString() { + return taskName; + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java index ce04589..ef7f942 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
@@ -16,7 +16,6 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gwtorm.server.OrmException; import java.io.IOException; import java.util.Optional; @@ -27,18 +26,16 @@ * Return the Change notes read from ReviewDb or NoteDb. * * @return notes of the Change - * @throws OrmException if ReviewDb or NoteDb cannot be opened */ - Optional<ChangeNotes> getChangeNotes() throws OrmException; + Optional<ChangeNotes> getChangeNotes(); /** * Create a new index event POJO associated with the current Change. * * @return new IndexEvent * @throws IOException if the current Change cannot read - * @throws OrmException if ReviewDb cannot be opened */ - Optional<IndexEvent> newIndexEvent() throws IOException, OrmException; + Optional<IndexEvent> newIndexEvent() throws IOException; /** * Check if the local Change is aligned with the indexEvent received. @@ -46,9 +43,8 @@ * @param indexEvent indexing event * @return true if the local Change is up-to-date, false otherwise. * @throws IOException if an I/O error occurred while reading the local Change - * @throws OrmException if the local ReviewDb cannot be opened */ - boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException, OrmException; + boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException; /** * Return the last computed up-to-date Change time-stamp. @@ -57,7 +53,6 @@ * * @return the Change timestamp epoch in seconds * @throws IOException if an I/O error occurred while reading the local Change - * @throws OrmException if the local ReviewDb cannot be opened */ - Optional<Long> getComputedChangeTs() throws IOException, OrmException; + Optional<Long> getComputedChangeTs() throws IOException; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java index a1b797f..1656eb9 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -19,15 +19,12 @@ import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Comment; import com.google.gerrit.reviewdb.client.RefNames; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CommentsUtil; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage; import com.google.gerrit.server.util.ManualRequestContext; import com.google.gerrit.server.util.OneOffRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import java.io.IOException; @@ -41,7 +38,6 @@ private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final GitRepositoryManager gitRepoMgr; private final CommentsUtil commentsUtil; - private final ChangeDb changeDb; private final OneOffRequestContext oneOffReqCtx; private final String changeId; private final ChangeFinder changeFinder; @@ -56,20 +52,18 @@ public ChangeCheckerImpl( GitRepositoryManager gitRepoMgr, CommentsUtil commentsUtil, - ChangeDb changeDb, ChangeFinder changeFinder, OneOffRequestContext oneOffReqCtx, @Assisted String changeId) { this.changeFinder = changeFinder; this.gitRepoMgr = gitRepoMgr; this.commentsUtil = commentsUtil; - this.changeDb = changeDb; this.oneOffReqCtx = oneOffReqCtx; this.changeId = changeId; } @Override - public Optional<IndexEvent> newIndexEvent() throws IOException, OrmException { + public Optional<IndexEvent> newIndexEvent() throws IOException { Optional<Long> changeTs = getComputedChangeTs(); if (!changeTs.isPresent()) { return Optional.empty(); @@ -91,7 +85,7 @@ } @Override - public Optional<ChangeNotes> getChangeNotes() throws OrmException { + public Optional<ChangeNotes> getChangeNotes() { try (ManualRequestContext ctx = oneOffReqCtx.open()) { changeNotes = Optional.ofNullable(changeFinder.findOne(changeId)); return changeNotes; @@ -99,8 +93,7 @@ } @Override - public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption) - throws IOException, OrmException { + public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption) throws IOException { getComputedChangeTs(); log.atFine().log("Checking change %s against index event %s", this, indexEventOption); if (!computedChangeTs.isPresent()) { @@ -128,7 +121,7 @@ } @Override - public Optional<Long> getComputedChangeTs() throws IOException, OrmException { + public Optional<Long> getComputedChangeTs() { if (!computedChangeTs.isPresent()) { computedChangeTs = computeLastChangeTs(); } @@ -146,7 +139,7 @@ + getBranchTargetSha(repo) + "/meta:" + getMetaSha(repo); - } catch (IOException | OrmException e) { + } catch (IOException e) { log.atSevere().withCause(e).log("Unable to render change %s", changeId); return "change-id=" + changeId; } @@ -168,36 +161,26 @@ } } + private Optional<Long> computeLastChangeTs() { + return getChangeNotes().map(this::getTsFromChangeAndDraftComments); + } + private String getMetaSha(Repository repo) throws IOException { - if (PrimaryStorage.NOTE_DB.equals(PrimaryStorage.of(changeNotes.get().getChange()))) { - String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId()); - Ref ref = repo.exactRef(refName); - if (ref == null) { - throw new IOException( - String.format("Unable to find meta ref %s for change %s", refName, changeId)); - } - return ref.getTarget().getObjectId().getName(); + String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId()); + Ref ref = repo.exactRef(refName); + if (ref == null) { + throw new IOException( + String.format("Unable to find meta ref %s for change %s", refName, changeId)); } - - return null; + return ref.getTarget().getObjectId().getName(); } - private Optional<Long> computeLastChangeTs() throws OrmException { - try (ReviewDb db = changeDb.open()) { - return getChangeNotes().map(notes -> getTsFromChangeAndDraftComments(db, notes)); - } - } - - private long getTsFromChangeAndDraftComments(ReviewDb db, ChangeNotes notes) { + private long getTsFromChangeAndDraftComments(ChangeNotes notes) { Change change = notes.getChange(); Timestamp changeTs = change.getLastUpdatedOn(); - try { - for (Comment comment : commentsUtil.draftByChange(db, changeNotes.get())) { - Timestamp commentTs = comment.writtenOn; - changeTs = commentTs.after(changeTs) ? commentTs : changeTs; - } - } catch (OrmException e) { - log.atWarning().withCause(e).log("Unable to access draft comments for change %s", change); + for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) { + Timestamp commentTs = comment.writtenOn; + changeTs = commentTs.after(changeTs) ? commentTs : changeTs; } return changeTs.getTime() / 1000; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java deleted file mode 100644 index bef5363..0000000 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java +++ /dev/null
@@ -1,38 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// 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 com.ericsson.gerrit.plugins.highavailability.index; - -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.notedb.NotesMigration; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.SchemaFactory; -import com.google.inject.Inject; - -public class ChangeDb { - private static final DisabledReviewDb disabledReviewDb = new DisabledReviewDb(); - - private final NotesMigration migration; - private final SchemaFactory<ReviewDb> schemaFactory; - - @Inject - public ChangeDb(NotesMigration migration, SchemaFactory<ReviewDb> schemaFactory) { - this.migration = migration; - this.schemaFactory = schemaFactory; - } - - public ReviewDb open() throws OrmException { - return migration.readChanges() ? disabledReviewDb : schemaFactory.open(); - } -}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java index f8c6e31..46aadbb 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
@@ -20,7 +20,6 @@ import com.google.gerrit.server.util.OneOffRequestContext; import com.google.gerrit.server.util.RequestContext; import com.google.gerrit.server.util.ThreadLocalRequestContext; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.function.Consumer; @@ -51,8 +50,6 @@ if (ctx == null) { try (ManualRequestContext manualCtx = oneOffCtx.open()) { body.accept(manualCtx); - } catch (OrmException e) { - logger.atSevere().withCause(e).log("Unable to open request context"); } } else { body.accept(ctx);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java deleted file mode 100644 index 48c6ecb..0000000 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java +++ /dev/null
@@ -1,201 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// 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 com.ericsson.gerrit.plugins.highavailability.index; - -import com.google.common.util.concurrent.CheckedFuture; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.server.ChangeAccess; -import com.google.gerrit.reviewdb.server.ChangeMessageAccess; -import com.google.gerrit.reviewdb.server.PatchLineCommentAccess; -import com.google.gerrit.reviewdb.server.PatchSetAccess; -import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.reviewdb.server.SchemaVersionAccess; -import com.google.gwtorm.server.Access; -import com.google.gwtorm.server.AtomicUpdate; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.ResultSet; -import com.google.gwtorm.server.StatementExecutor; -import java.util.Map; - -/** ReviewDb that is disabled. */ -@SuppressWarnings("deprecation") -public class DisabledReviewDb implements ReviewDb { - public static class Disabled extends RuntimeException { - private static final long serialVersionUID = 1L; - - private Disabled() { - super("ReviewDb is disabled for changes"); - } - } - - public static class DisabledChangeAccess implements ChangeAccess { - - @Override - public String getRelationName() { - throw new Disabled(); - } - - @Override - public int getRelationID() { - throw new Disabled(); - } - - @Override - public ResultSet<Change> iterateAllEntities() throws OrmException { - throw new Disabled(); - } - - @Override - public Change.Id primaryKey(Change entity) { - throw new Disabled(); - } - - @Override - public Map<Change.Id, Change> toMap(Iterable<Change> c) { - throw new Disabled(); - } - - @Override - public CheckedFuture<Change, OrmException> getAsync(Change.Id key) { - throw new Disabled(); - } - - @Override - public ResultSet<Change> get(Iterable<Change.Id> keys) throws OrmException { - throw new Disabled(); - } - - @Override - public void insert(Iterable<Change> instances) throws OrmException { - throw new Disabled(); - } - - @Override - public void update(Iterable<Change> instances) throws OrmException { - throw new Disabled(); - } - - @Override - public void upsert(Iterable<Change> instances) throws OrmException { - throw new Disabled(); - } - - @Override - public void deleteKeys(Iterable<Change.Id> keys) throws OrmException { - throw new Disabled(); - } - - @Override - public void delete(Iterable<Change> instances) throws OrmException { - throw new Disabled(); - } - - @Override - public void beginTransaction(Change.Id key) throws OrmException { - throw new Disabled(); - } - - @Override - public Change atomicUpdate(Change.Id key, AtomicUpdate<Change> update) throws OrmException { - throw new Disabled(); - } - - @Override - public Change get(Change.Id id) throws OrmException { - return null; - } - - @Override - public ResultSet<Change> all() throws OrmException { - return null; - } - } - - @Override - public void close() { - // Do nothing. - } - - @Override - public void commit() { - throw new Disabled(); - } - - @Override - public void rollback() { - throw new Disabled(); - } - - @Override - public void updateSchema(StatementExecutor e) { - throw new Disabled(); - } - - @Override - public void pruneSchema(StatementExecutor e) { - throw new Disabled(); - } - - @Override - public Access<?, ?>[] allRelations() { - throw new Disabled(); - } - - @Override - public SchemaVersionAccess schemaVersion() { - throw new Disabled(); - } - - @Override - public ChangeAccess changes() { - return new DisabledChangeAccess(); - } - - @Override - public PatchSetApprovalAccess patchSetApprovals() { - throw new Disabled(); - } - - @Override - public ChangeMessageAccess changeMessages() { - throw new Disabled(); - } - - @Override - public PatchSetAccess patchSets() { - throw new Disabled(); - } - - @Override - public PatchLineCommentAccess patchComments() { - throw new Disabled(); - } - - @Override - public int nextAccountId() { - throw new Disabled(); - } - - @Override - public int nextAccountGroupId() { - throw new Disabled(); - } - - @Override - public int nextChangeId() { - throw new Disabled(); - } -}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java index 068ef0d..73e842f 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -14,6 +14,7 @@ package com.ericsson.gerrit.plugins.highavailability.index; +import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.forwarder.Context; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; @@ -27,8 +28,10 @@ import com.google.inject.Inject; import java.util.Collections; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; class IndexEventHandler implements ChangeIndexedListener, @@ -36,28 +39,37 @@ GroupIndexedListener, ProjectIndexedListener { private static final FluentLogger log = FluentLogger.forEnclosingClass(); - private final Executor executor; - private final Executor batchExecutor; + private final ScheduledExecutorService executor; + private final ScheduledExecutorService batchExecutor; private final Forwarder forwarder; private final String pluginName; private final Set<IndexTask> queuedTasks = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final ChangeCheckerImpl.Factory changeChecker; private final CurrentRequestContext currCtx; + private final IndexEventLocks locks; + + private final int retryInterval; + private final int maxTries; @Inject IndexEventHandler( - @IndexExecutor Executor executor, - @BatchIndexExecutor Executor batchExecutor, + @IndexExecutor ScheduledExecutorService executor, + @BatchIndexExecutor ScheduledExecutorService batchExecutor, @PluginName String pluginName, Forwarder forwarder, ChangeCheckerImpl.Factory changeChecker, - CurrentRequestContext currCtx) { + CurrentRequestContext currCtx, + Configuration cfg, + IndexEventLocks locks) { this.forwarder = forwarder; this.executor = executor; this.batchExecutor = batchExecutor; this.pluginName = pluginName; this.changeChecker = changeChecker; this.currCtx = currCtx; + this.locks = locks; + this.retryInterval = cfg.http().retryInterval(); + this.maxTries = cfg.http().maxTries(); } @Override @@ -141,6 +153,7 @@ abstract class IndexTask implements Runnable { protected final IndexEvent indexEvent; + private int retryCount = 0; IndexTask() { indexEvent = new IndexEvent(); @@ -152,11 +165,27 @@ @Override public void run() { - queuedTasks.remove(this); - execute(); + locks.withLock( + this, + () -> { + queuedTasks.remove(this); + return execute(); + }, + this::reschedule); } - abstract void execute(); + private void reschedule() { + if (++retryCount <= maxTries) { + log.atFine().log("Retrying %d times to %s", retryCount, this); + executor.schedule(this, retryInterval, TimeUnit.MILLISECONDS); + } else { + log.atSevere().log("Failed to %s after %d tries; giving up", this, maxTries); + } + } + + abstract CompletableFuture<Boolean> execute(); + + abstract String indexId(); } class IndexChangeTask extends IndexTask { @@ -170,8 +199,8 @@ } @Override - public void execute() { - forwarder.indexChange(projectName, changeId, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.indexChange(projectName, changeId, indexEvent); } @Override @@ -192,6 +221,11 @@ public String toString() { return String.format("[%s] Index change %s in target instance", pluginName, changeId); } + + @Override + String indexId() { + return "change/" + changeId; + } } class BatchIndexChangeTask extends IndexTask { @@ -205,8 +239,13 @@ } @Override - public void execute() { - forwarder.batchIndexChange(projectName, changeId, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.batchIndexChange(projectName, changeId, indexEvent); + } + + @Override + String indexId() { + return "change/" + changeId; } @Override @@ -238,8 +277,8 @@ } @Override - public void execute() { - forwarder.deleteChangeFromIndex(changeId, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.deleteChangeFromIndex(changeId, indexEvent); } @Override @@ -260,6 +299,11 @@ public String toString() { return String.format("[%s] Delete change %s in target instance", pluginName, changeId); } + + @Override + String indexId() { + return "change/" + changeId; + } } class IndexAccountTask extends IndexTask { @@ -270,8 +314,8 @@ } @Override - public void execute() { - forwarder.indexAccount(accountId, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.indexAccount(accountId, indexEvent); } @Override @@ -292,6 +336,11 @@ public String toString() { return String.format("[%s] Index account %s in target instance", pluginName, accountId); } + + @Override + String indexId() { + return "account/" + accountId; + } } class IndexGroupTask extends IndexTask { @@ -302,8 +351,8 @@ } @Override - public void execute() { - forwarder.indexGroup(groupUUID, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.indexGroup(groupUUID, indexEvent); } @Override @@ -324,6 +373,11 @@ public String toString() { return String.format("[%s] Index group %s in target instance", pluginName, groupUUID); } + + @Override + String indexId() { + return "group/" + groupUUID; + } } class IndexProjectTask extends IndexTask { @@ -334,8 +388,8 @@ } @Override - public void execute() { - forwarder.indexProject(projectName, indexEvent); + public CompletableFuture<Boolean> execute() { + return forwarder.indexProject(projectName, indexEvent); } @Override @@ -356,5 +410,10 @@ public String toString() { return String.format("[%s] Index project %s in target instance", pluginName, projectName); } + + @Override + String indexId() { + return "project/" + projectName; + } } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java new file mode 100644 index 0000000..3fd26db --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java
@@ -0,0 +1,95 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// 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 com.ericsson.gerrit.plugins.highavailability.index; + +import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.flogger.FluentLogger; +import com.google.common.util.concurrent.Striped; +import com.google.inject.Inject; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class IndexEventLocks { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + + private static final int NUMBER_OF_INDEX_TASK_TYPES = 4; + private static final int WAIT_TIMEOUT_MS = 5; + + private final Striped<Semaphore> semaphores; + + @Inject + public IndexEventLocks(Configuration cfg) { + this.semaphores = + Striped.semaphore(NUMBER_OF_INDEX_TASK_TYPES * cfg.index().numStripedLocks(), 1); + } + + public CompletableFuture<?> withLock( + IndexTask id, IndexCallFunction function, VoidFunction lockAcquireTimeoutCallback) { + String indexId = id.indexId(); + Semaphore idSemaphore = getSemaphore(indexId); + try { + log.atFine().log("Trying to acquire %s", id); + if (idSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + log.atFine().log("Acquired %s", id); + return function + .invoke() + .whenComplete( + (result, error) -> { + try { + log.atFine().log("Trying to release %s", id); + idSemaphore.release(); + log.atFine().log("Released %s", id); + } catch (Throwable t) { + log.atSevere().withCause(t).log("Unable to release %s", id); + throw t; + } + }); + } + + String timeoutMessage = + String.format( + "Acquisition of the locking of %s timed out after %d msec: consider increasing the number of shards", + indexId, WAIT_TIMEOUT_MS); + log.atWarning().log(timeoutMessage); + lockAcquireTimeoutCallback.invoke(); + CompletableFuture<?> failureFuture = new CompletableFuture<>(); + failureFuture.completeExceptionally(new InterruptedException(timeoutMessage)); + return failureFuture; + } catch (InterruptedException e) { + CompletableFuture<?> failureFuture = new CompletableFuture<>(); + failureFuture.completeExceptionally(e); + log.atSevere().withCause(e).log("Locking of %s was interrupted; giving up", indexId); + return failureFuture; + } + } + + @VisibleForTesting + protected Semaphore getSemaphore(String indexId) { + return semaphores.get(indexId); + } + + @FunctionalInterface + public interface VoidFunction { + void invoke(); + } + + @FunctionalInterface + public interface IndexCallFunction { + CompletableFuture<?> invoke(); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java index 4258dce..3bcc34f 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -20,26 +20,31 @@ import com.google.gerrit.extensions.events.ProjectIndexedListener; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.inject.Scopes; import com.google.inject.assistedinject.FactoryModuleBuilder; -import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; public class IndexModule extends LifecycleModule { @Override protected void configure() { - bind(Executor.class).annotatedWith(IndexExecutor.class).toProvider(IndexExecutorProvider.class); - bind(Executor.class) - .annotatedWith(BatchIndexExecutor.class) - .toProvider(BatchIndexExecutorProvider.class); + bind(ScheduledExecutorService.class) + .annotatedWith(IndexExecutor.class) + .toProvider(IndexExecutorProvider.class); bind(ScheduledExecutorService.class) .annotatedWith(ForwardedIndexExecutor.class) .toProvider(ForwardedIndexExecutorProvider.class); + bind(IndexEventLocks.class).in(Scopes.SINGLETON); + bind(ScheduledExecutorService.class) + .annotatedWith(BatchIndexExecutor.class) + .toProvider(BatchIndexExecutorProvider.class); bind(ScheduledExecutorService.class) .annotatedWith(ForwardedBatchIndexExecutor.class) .toProvider(ForwardedBatchIndexExecutorProvider.class); listener().to(IndexExecutorProvider.class); - DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexEventHandler.class); + DynamicSet.bind(binder(), ChangeIndexedListener.class) + .to(IndexEventHandler.class) + .in(Scopes.SINGLETON); DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexEventHandler.class); DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexEventHandler.class); DynamicSet.bind(binder(), ProjectIndexedListener.class).to(IndexEventHandler.class);
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index c7196c6..fd0b56e 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -1,20 +1,19 @@ This plugin allows making Gerrit highly available by having redundant Gerrit -masters. +active/passive instances. -The masters must be: +The Gerrit instances must be: -* connecting to the same database * sharing the git repositories using a shared file system (e.g. NFS) * behind a load balancer (e.g. HAProxy) -Currently, the mode supported is one primary (active) master and multiple backup -(passive) masters but eventually the plan is to support `n` active masters. In -the active/passive mode, the active master is handling all traffic while the -passives are kept updated to be always ready to take over. +Currently, the mode supported is one active instance and multiple backup +(passive) instances but eventually the plan is to support `n` active instances. +In the active/passive mode, the active instance is handling all traffic while +the passives are kept updated to be always ready to take over. -Even if database and git repositories are shared by the masters, there are a few -areas of concern in order to be able to switch traffic between masters in a +Even if git repositories are shared by the instances, there are a few areas +of concern in order to be able to switch traffic between instances in a transparent manner from the user's perspective. The 4 areas of concern are things that Gerrit stores either in memory or locally in the review site: @@ -23,68 +22,67 @@ * stream-events * web sessions -They need either to be shared or kept local to each master but synchronized. -This plugin needs to be installed in all the masters and it will take care of sharing -or synchronizing them. +They need either to be shared or kept local to each instances but synchronized. +This plugin needs to be installed in all the instances and it will take care of +sharing or synchronizing them. #### Caches -Every time a cache eviction occurs in one of the masters, the eviction will be -forwarded the other masters so their caches do not contain stale entries. +Every time a cache eviction occurs in one of the instances, the eviction will be +forwarded the other nodes so their caches do not contain stale entries. #### Secondary indexes -Every time the secondary index is modified in one of the masters, e.g., a change -is added, updated or removed from the index, the others master's index are -updated accordingly. This way, both indexes are kept synchronized. +Every time the secondary index is modified in one of the instances, e.g., a change +is added, updated or removed from the index, the others instances index are updated +accordingly. This way, both indexes are kept synchronized. #### Stream events -Every time a stream event occurs in one of the masters (see [more events info] -(https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)), -the event is forwarded to the other masters which re-plays it. This way, the -output of the stream-events command is the same, no matter which master a client -is connected to. +Every time a stream event occurs in one of the instances +(see [more events info](https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)), +the event is forwarded to the other instances which re-plays it. This way, the output +of the stream-events command is the same, no matter which instance a client is +connected to. #### Web session The built-in Gerrit H2 based web session cache is replaced with a file based -implementation that is shared amongst the masters. +implementation that is shared amongst the instances. ## Setup Prerequisites: -* Unique database server must be accessible from all the masters * Git repositories must be located on a shared file system * A directory on a shared file system must be available for @PLUGIN@ to use -For the masters: +For the instances: -* Configure database section in gerrit.config to use the shared database * Configure gerrit.basePath in gerrit.config to the shared repositories location -* Install and configure @PLUGIN@ plugin +* Configure gerrit.serverId in gerrit.config based on [config](config.md)'s introduction +* Install and configure this @PLUGIN@ plugin [further](config.md) or based on below. Here is an example of the minimal @PLUGIN@.config: -Primary master +Active instance ``` [main] - sharedDirectory = /directory/accessible/from/both/masters + sharedDirectory = /directory/accessible/from/both/nodes [peerInfo "static"] - url = http://backupMasterHost1:8081/ + url = http://backupNodeHost1:8081/ [http] user = username password = password ``` -Backup master +Backup instance ``` [main] - sharedDirectory = /directory/accessible/from/both/masters + sharedDirectory = /directory/accessible/from/both/nodes [peerInfo "static"] - url = http://primaryMasterHost:8080/ + url = http://primaryNodeHost:8080/ [http] user = username @@ -93,10 +91,10 @@ ### HA replica site -It is possible to create a copy of the master site and configure both sites to run -in HA mode as peers. This is possible when the directory where the copy will be -created is accessible from this machine. Such a replica site can be created by -means of a gerrit [site init](../../../Documentation/pgm-init.html) step, +It is possible to create a copy of the instance site and configure both +sites to run in HA mode as peers. This is possible when the directory where +the copy will be created is accessible from this machine. Such a replica site +can be created by means of a gerrit [site init](../../../Documentation/pgm-init.html) step, contributed by the plugin under its init section. This init step is optional but defaults to creating the replica. If you want to
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index ea20e89..360218d 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -2,8 +2,14 @@ @PLUGIN@ Configuration ========================= -The @PLUGIN@ plugin must be installed on all the instances and the following fields -should be specified in `$site_path/etc/@PLUGIN@.config` file: +The @PLUGIN@ plugin must be installed on all the instances. Each instance should +be configured with the same [gerrit.serverId](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/config-gerrit.html#gerrit.serverId). +If there are existing changes in [NoteDb](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/note-db.html) +made with another `serverId`, then this plugin might not be able to access them. +Likewise, if the HA gerrit.serverIds differ, then changes conveyed by one +instance will not be accessible by the other. + +The following fields should be specified in `$site_path/etc/@PLUGIN@.config` files: File '@PLUGIN@.config' -------------------- @@ -49,7 +55,7 @@ ``` ```main.sharedDirectory``` -: Path to a directory accessible from both master instances. +: Path to a directory accessible from both instances. When given as a relative path, then it is resolved against the $SITE_PATH or Gerrit server. For example, if $SITE_PATH is "/gerrit/root" and sharedDirectory is given as "shared/dir" then the real path of the shared @@ -75,13 +81,6 @@ Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval). When not specified, polling of conditional reindexing is disabled. -```autoReindex.interval``` -: Enable the tracking of the latest change indexed under data/high-availability - for each of the indexes. At startup scans all the changes, accounts and groups - and reindex the ones that have been updated by other nodes while the server was down. - When not specified, the default is "false", that means no automatic tracking - and indexing at start. - ```peerInfo.strategy``` : Strategy to find other peers. Supported strategies are `static` or `jgroups`. Defaults to `jgroups`. @@ -161,9 +160,6 @@ : The interval of time in milliseconds between the subsequent auto-retries. When not specified, the default value is set to 10000ms. -NOTE: the default settings for `http.timeout` and `http.maxTries` ensure that -the plugin will keep retrying to forward a message for one hour. - ```cache.synchronize``` : Whether to synchronize cache evictions. Defaults to true. @@ -216,6 +212,9 @@ : The interval of time in milliseconds between the subsequent auto-retries. Defaults to 30000 (30 seconds). +NOTE: the default settings for `http.socketTimeout` and `http.maxTries` ensure +that the plugin will keep retrying to forward a message for one hour. + ```websession.synchronize``` : Whether to synchronize web sessions. Defaults to true.
diff --git a/src/test/docker/docker-compose.yaml b/src/test/docker/docker-compose.yaml index a6dda6d..1365cd8 100644 --- a/src/test/docker/docker-compose.yaml +++ b/src/test/docker/docker-compose.yaml
@@ -2,17 +2,6 @@ services: - postgres: - image: postgres:9.5.4 - environment: - - POSTGRES_USER=gerrit - - POSTGRES_PASSWORD=secret - - POSTGRES_DB=reviewdb - networks: - - gerrit-net - volumes: - - ./pgdata:/var/lib/postgresql/data - gerrit-01: build: gerrit ports: @@ -20,8 +9,6 @@ - "29411:29418" networks: - gerrit-net - depends_on: - - postgres volumes: - /dev/urandom:/dev/random - ./gitvolume:/var/gerrit/git @@ -39,7 +26,6 @@ networks: - gerrit-net depends_on: - - postgres - gerrit-01 volumes: - /dev/urandom:/dev/random
diff --git a/src/test/docker/etc/gerrit.config b/src/test/docker/etc/gerrit.config index c110835..90a4057 100644 --- a/src/test/docker/etc/gerrit.config +++ b/src/test/docker/etc/gerrit.config
@@ -1,12 +1,7 @@ [gerrit] basePath = git canonicalWebUrl = http://gerrit:8080/ -[database] - type = postgresql - hostname = postgres - database = reviewdb - username = gerrit - password = secret + serverId = f7696647-8efd-41b1-bd60-d321bc071ea9 [index] type = LUCENE [auth]
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile index 1511350..ec7ebd8 100644 --- a/src/test/docker/gerrit/Dockerfile +++ b/src/test/docker/gerrit/Dockerfile
@@ -1,19 +1,17 @@ -FROM gerritcodereview/gerrit:2.16.22 +FROM gerritcodereview/gerrit:3.0.12 -ENV GERRIT_BRANCH=stable-2.16 +ENV GERRIT_BRANCH=stable-3.0 ENV GERRIT_CI_URL=https://archive-ci.gerritforge.com/job USER root -RUN yum install -y iputils-ping netcat postgresql curl lsof gettext moreutils net-tools netcat inetutils-ping sudo +RUN yum install -y iputils-ping netcat curl lsof gettext moreutils net-tools netcat inetutils-ping sudo USER gerrit ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody.jar /var/gerrit/plugins/javamelody.jar -ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody-deps_deploy.jar /var/gerrit/lib/javamelody-deps_deploy.jar ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-high-availability-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/high-availability/high-availability.jar /var/gerrit/plugins/high-availability.jar -ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-delete-project-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/delete-project/delete-project.jar /var/gerrit/plugins/delete-project.jar USER root @@ -24,4 +22,6 @@ ARG GERRIT_UID=1000 RUN usermod -u ${GERRIT_UID} gerrit &> /dev/null + +ENTRYPOINT ["/usr/bin/env"] CMD /bin/start.sh
diff --git a/src/test/docker/gerrit/start.sh b/src/test/docker/gerrit/start.sh index 7aa3550..0dae4c3 100755 --- a/src/test/docker/gerrit/start.sh +++ b/src/test/docker/gerrit/start.sh
@@ -1,18 +1,14 @@ #!/bin/bash -e -wait-for-it.sh postgres:5432 -t 600 -- echo "Postgres is up" - if [[ ! -z "$WAIT_FOR" ]] then wait-for-it.sh $WAIT_FOR -t 600 -- echo "$WAIT_FOR is up" fi -chown -R gerrit: /var/gerrit - sudo -u gerrit cp /var/gerrit/etc/gerrit.config.orig /var/gerrit/etc/gerrit.config sudo -u gerrit cp /var/gerrit/etc/high-availability.config.orig /var/gerrit/etc/high-availability.config -if [[ ! -f /var/gerrit/git/All-Projects.git/config ]] +if [[ ! -f /var/gerrit/etc/ssh_host_ed25519_key ]] then echo "Initializing Gerrit site ..." sudo -u gerrit java -jar /var/gerrit/bin/gerrit.war init -d /var/gerrit --batch
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 5bca2cb..3444625 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -19,6 +19,7 @@ import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_NUM_STRIPED_LOCKS; import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE; +import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY; @@ -28,7 +29,6 @@ import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL; -import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY; import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY; @@ -115,15 +115,16 @@ @Test public void testGetPeerInfoStrategy() { - assertThat(getConfiguration().peerInfo().strategy()).isSameAs(DEFAULT_PEER_INFO_STRATEGY); + assertThat(getConfiguration().peerInfo().strategy()) + .isSameInstanceAs(DEFAULT_PEER_INFO_STRATEGY); globalPluginConfig.setString( PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.STATIC.name()); - assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.STATIC); + assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.STATIC); globalPluginConfig.setString( PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.JGROUPS.name()); - assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.JGROUPS); + assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.JGROUPS); } @Test @@ -132,7 +133,7 @@ globalPluginConfig.setStringList(PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY, URLS); assertThat(getConfiguration().peerInfoStatic().urls()) - .containsAllIn(ImmutableList.of(URL, "http://anotherUrl")); + .containsAtLeastElementsIn(ImmutableList.of(URL, "http://anotherUrl")); } @Test @@ -156,7 +157,9 @@ globalPluginConfig.setStringList( JGROUPS_SECTION, null, SKIP_INTERFACE_KEY, ImmutableList.of("lo*", "eth0")); - assertThat(getConfiguration().jgroups().skipInterface()).containsAllOf("lo*", "eth0").inOrder(); + assertThat(getConfiguration().jgroups().skipInterface()) + .containsExactly("lo*", "eth0") + .inOrder(); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java index 32cc23a..9517f29 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
@@ -76,7 +76,7 @@ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(afterCurrentTime)); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isTrue(); assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime); verify(indexer).index(uuid, Operation.INDEX, Optional.empty()); @@ -89,7 +89,7 @@ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(beforeCurrentTime)); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isFalse(); verify(indexer, never()).index(uuid, Operation.INDEX, Optional.empty()); } @@ -98,7 +98,7 @@ public void groupIsNotIndexedGroupReferenceNotPresent() { Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now()); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isFalse(); } @@ -115,7 +115,7 @@ null, afterCurrentTime))); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isTrue(); assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime); verify(indexer).index(uuid, Operation.INDEX, Optional.empty()); @@ -137,7 +137,7 @@ when(groups.getMembersAudit(any(), any())) .thenReturn(Collections.singletonList(accountGroupMemberAudit)); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isTrue(); assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime); verify(indexer).index(uuid, Operation.INDEX, Optional.empty()); @@ -158,7 +158,7 @@ null, afterCurrentTime))); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isTrue(); assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime); verify(indexer).index(uuid, Operation.INDEX, Optional.empty()); @@ -181,7 +181,7 @@ .thenReturn(Collections.singletonList(accountGroupByIdAud)); Optional<Timestamp> groupLastTs = - groupReindexRunnable.indexIfNeeded(null, groupReference, currentTime); + groupReindexRunnable.indexIfNeeded(groupReference, currentTime); assertThat(groupLastTs.isPresent()).isTrue(); assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime); verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java index 03c1928..9b6e66b 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
@@ -20,6 +20,7 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.google.common.collect.ImmutableList; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -76,4 +77,28 @@ assertWithMessage(cache + " should not match").that(matcher.matches(cache)).isFalse(); } } + + @Test + public void testShouldNotMatchWebSessionsWhenNotSynchronized() { + String cache = "web_sessions"; + when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList()); + when(configurationMock.websession().synchronize()).thenReturn(false); + CachePatternMatcher matcher = new CachePatternMatcher(configurationMock); + + assertWithMessage(cache + " should NOT match when websession.synchronize is false") + .that(matcher.matches(cache)) + .isFalse(); + } + + @Test + public void testShouldMatchWebSessionsWhenSynchronized() { + String cache = "web_sessions"; + when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList()); + when(configurationMock.websession().synchronize()).thenReturn(true); + CachePatternMatcher matcher = new CachePatternMatcher(configurationMock); + + assertWithMessage(cache + " should match when websession.synchronize is true") + .that(matcher.matches(cache)) + .isTrue(); + } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java new file mode 100644 index 0000000..2b9ee35 --- /dev/null +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java
@@ -0,0 +1,73 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// 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 com.ericsson.gerrit.plugins.highavailability.event; + +import static com.google.common.truth.Truth.assertThat; + +import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.GsonProvider; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.data.AccountAttribute; +import com.google.gerrit.server.data.RefUpdateAttribute; +import com.google.gerrit.server.events.RefUpdatedEvent; +import com.google.gson.Gson; +import org.junit.Test; + +public class EventDeserializerTest { + private static final String LEGACY_PROJECT_KEY = "{\"name\": \"project\"}"; + + private static final String NEW_PROJECT_KEY = "\"project\""; + + private final Gson gson = new GsonProvider().get(); + + @Test + public void deserializePatchSetCreatedEventLegacyProjectKey() { + Project.NameKey n = gson.fromJson(LEGACY_PROJECT_KEY, Project.NameKey.class); + assertThat(n.get()).isEqualTo("project"); + } + + @Test + public void deserializePatchSetCreatedEventNewProjectKey() { + Project.NameKey n = gson.fromJson(NEW_PROJECT_KEY, Project.NameKey.class); + assertThat(n.get()).isEqualTo("project"); + } + + @Test + public void refUpdatedEventRoundTrip() { + RefUpdatedEvent refUpdatedEvent = new RefUpdatedEvent(); + + RefUpdateAttribute refUpdatedAttribute = new RefUpdateAttribute(); + refUpdatedAttribute.refName = "refs/heads/master"; + refUpdatedEvent.refUpdate = createSupplier(refUpdatedAttribute); + + AccountAttribute accountAttribute = new AccountAttribute(); + accountAttribute.email = "some.user@domain.com"; + refUpdatedEvent.submitter = createSupplier(accountAttribute); + + String serializedEvent = gson.toJson(refUpdatedEvent); + RefUpdatedEvent e = gson.fromJson(serializedEvent, RefUpdatedEvent.class); + + assertThat(e).isNotNull(); + assertThat(e.refUpdate).isInstanceOf(Supplier.class); + assertThat(e.refUpdate.get().refName).isEqualTo(refUpdatedAttribute.refName); + assertThat(e.submitter).isInstanceOf(Supplier.class); + assertThat(e.submitter.get().email).isEqualTo(accountAttribute.email); + } + + private static <T> Supplier<T> createSupplier(T value) { + return Suppliers.memoize(() -> value); + } +}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java index ec5b1de..36cc953 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
@@ -39,7 +39,7 @@ DynamicSet<EventListener> set = DynamicSet.emptySet(); set.add("high-availability", listenerMock); PluginSetContext<EventListener> listeners = new PluginSetContext<>(set, mockMetrics); - broker = new ForwardedAwareEventBroker(null, listeners, null, null, null, null); + broker = new ForwardedAwareEventBroker(null, listeners, null, null, null); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java index a855c5e..8ef8e2f 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
@@ -15,14 +15,14 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; import static com.google.common.truth.Truth.assertThat; -import static com.google.gerrit.testing.GerritJUnit.assertThrows; +import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventDispatcher; import com.google.gerrit.server.events.ProjectCreatedEvent; -import com.google.gwtorm.server.OrmException; +import com.google.gerrit.server.permissions.PermissionBackendException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -76,14 +76,18 @@ (Answer<Void>) invocation -> { assertThat(Context.isForwardedEvent()).isTrue(); - throw new OrmException("someMessage"); + throw new PermissionBackendException("someMessage"); }) .when(dispatcherMock) .postEvent(event); assertThat(Context.isForwardedEvent()).isFalse(); - OrmException thrown = assertThrows(OrmException.class, () -> handler.dispatch(event)); - assertThat(thrown).hasMessageThat().contains("someMessage"); + try { + handler.dispatch(event); + fail("should have throw a PermissionBackendException"); + } catch (PermissionBackendException e) { + assertThat(e.getMessage()).isEqualTo("someMessage"); + } assertThat(Context.isForwardedEvent()).isFalse(); verify(dispatcherMock).postEvent(event);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java index b88e9c0..14f3d54 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
@@ -19,9 +19,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.index.account.AccountIndexer; @@ -38,16 +36,12 @@ public class ForwardedIndexAccountHandlerTest { @Mock private AccountIndexer indexerMock; - @Mock private Configuration configMock; - @Mock private Configuration.Index indexMock; private ForwardedIndexAccountHandler handler; private Account.Id id; @Before public void setUp() throws Exception { - when(configMock.index()).thenReturn(indexMock); - when(indexMock.numStripedLocks()).thenReturn(10); - handler = new ForwardedIndexAccountHandler(indexerMock, configMock); + handler = new ForwardedIndexAccountHandler(indexerMock); id = new Account.Id(123); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java index 5e53a01..a6da8e2 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -15,11 +15,9 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; import static com.google.common.truth.Truth.assertThat; -import static com.google.gerrit.testing.GerritJUnit.assertThrows; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,14 +26,11 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker; import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl; -import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb; import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.util.OneOffRequestContext; import com.google.gerrit.server.util.time.TimeUtil; -import com.google.gwtorm.server.OrmException; import java.io.IOException; import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; @@ -54,16 +49,10 @@ private static String TEST_CHANGE_ID = TEST_PROJECT + "~" + TEST_CHANGE_NUMBER; private static final boolean CHANGE_EXISTS = true; private static final boolean CHANGE_DOES_NOT_EXIST = false; - private static final boolean DO_NOT_THROW_IO_EXCEPTION = false; - private static final boolean DO_NOT_THROW_ORM_EXCEPTION = false; - private static final boolean THROW_IO_EXCEPTION = true; - private static final boolean THROW_ORM_EXCEPTION = true; private static final boolean CHANGE_UP_TO_DATE = true; private static final boolean CHANGE_OUTDATED = false; @Mock private ChangeIndexer indexerMock; - @Mock private ChangeDb changeDbMock; - @Mock private ReviewDb dbMock; @Mock private ChangeNotes changeNotes; @Mock private Configuration configMock; @Mock private Configuration.Index indexMock; @@ -72,42 +61,33 @@ @Mock private ChangeCheckerImpl.Factory changeCheckerFactoryMock; @Mock private ChangeChecker changeCheckerAbsentMock; @Mock private ChangeChecker changeCheckerPresentMock; - @Mock private SingleChangeNoteDbMigrator noteDbMigration; private ForwardedIndexChangeHandler handler; private Change.Id id; @Before public void setUp() throws Exception { - when(changeDbMock.open()).thenReturn(dbMock); id = new Change.Id(TEST_CHANGE_NUMBER); Change change = new Change(null, id, null, null, TimeUtil.nowTs()); when(changeNotes.getChange()).thenReturn(change); when(configMock.index()).thenReturn(indexMock); - when(indexMock.numStripedLocks()).thenReturn(10); when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerAbsentMock); handler = new ForwardedIndexChangeHandler( - indexerMock, - changeDbMock, - configMock, - indexExecutorMock, - ctxMock, - changeCheckerFactoryMock, - noteDbMigration); + indexerMock, configMock, indexExecutorMock, ctxMock, changeCheckerFactoryMock); } @Test public void changeIsIndexedWhenUpToDate() throws Exception { setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE); handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()); - verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class)); + verify(indexerMock, times(1)).index(any(Change.class)); } @Test public void changeIsStillIndexedEvenWhenOutdated() throws Exception { setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_OUTDATED); handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.of(new IndexEvent())); - verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class)); + verify(indexerMock, times(1)).index(any(Change.class)); } @Test @@ -124,21 +104,6 @@ } @Test - public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception { - setupChangeAccessRelatedMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION, CHANGE_UP_TO_DATE); - assertThrows( - OrmException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty())); - } - - @Test - public void indexerThrowsIOExceptionTryingToIndexChange() throws Exception { - setupChangeAccessRelatedMocks( - CHANGE_EXISTS, DO_NOT_THROW_ORM_EXCEPTION, THROW_IO_EXCEPTION, CHANGE_UP_TO_DATE); - assertThrows( - IOException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty())); - } - - @Test public void shouldSetAndUnsetForwardedContext() throws Exception { setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE); // this doAnswer is to allow to assert that context is set to forwarded @@ -150,13 +115,13 @@ return null; }) .when(indexerMock) - .index(any(ReviewDb.class), any(Change.class)); + .index(any(Change.class)); assertThat(Context.isForwardedEvent()).isFalse(); handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()); assertThat(Context.isForwardedEvent()).isFalse(); - verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class)); + verify(indexerMock, times(1)).index(any(Change.class)); } @Test @@ -169,7 +134,7 @@ throw new IOException("someMessage"); }) .when(indexerMock) - .index(any(ReviewDb.class), any(Change.class)); + .index(any(Change.class)); assertThat(Context.isForwardedEvent()).isFalse(); try { @@ -180,39 +145,14 @@ } assertThat(Context.isForwardedEvent()).isFalse(); - verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class)); + verify(indexerMock, times(1)).index(any(Change.class)); } - private void setupChangeAccessRelatedMocks(boolean changeExist, boolean changeUpToDate) - throws Exception { - setupChangeAccessRelatedMocks( - changeExist, DO_NOT_THROW_ORM_EXCEPTION, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate); - } - - private void setupChangeAccessRelatedMocks( - boolean changeExist, boolean ormException, boolean changeUpToDate) - throws OrmException, IOException { - setupChangeAccessRelatedMocks( - changeExist, ormException, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate); - } - - private void setupChangeAccessRelatedMocks( - boolean changeExists, boolean ormException, boolean ioException, boolean changeIsUpToDate) - throws OrmException, IOException { - if (ormException) { - doThrow(new OrmException("")).when(changeDbMock).open(); - } else { - when(changeDbMock.open()).thenReturn(dbMock); - } - + private void setupChangeAccessRelatedMocks(boolean changeExists, boolean changeIsUpToDate) + throws IOException { if (changeExists) { when(changeCheckerFactoryMock.create(TEST_CHANGE_ID)).thenReturn(changeCheckerPresentMock); when(changeCheckerPresentMock.getChangeNotes()).thenReturn(Optional.of(changeNotes)); - if (ioException) { - doThrow(new IOException("io-error")) - .when(indexerMock) - .index(any(ReviewDb.class), any(Change.class)); - } } when(changeCheckerPresentMock.isChangeUpToDate(any())).thenReturn(changeIsUpToDate);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java index 845abb0..7e30dea 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
@@ -19,9 +19,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.index.group.GroupIndexer; @@ -38,16 +36,12 @@ public class ForwardedIndexGroupHandlerTest { @Mock private GroupIndexer indexerMock; - @Mock private Configuration configMock; - @Mock private Configuration.Index indexMock; private ForwardedIndexGroupHandler handler; private AccountGroup.UUID uuid; @Before public void setUp() throws Exception { - when(configMock.index()).thenReturn(indexMock); - when(indexMock.numStripedLocks()).thenReturn(10); - handler = new ForwardedIndexGroupHandler(indexerMock, configMock); + handler = new ForwardedIndexGroupHandler(indexerMock); uuid = new AccountGroup.UUID("123"); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java index a0c6979..ea36532 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
@@ -19,9 +19,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.google.gerrit.index.project.ProjectIndexer; import com.google.gerrit.reviewdb.client.Project; @@ -38,16 +36,12 @@ public class ForwardedIndexProjectHandlerTest { @Mock private ProjectIndexer indexerMock; - @Mock private Configuration configMock; - @Mock private Configuration.Index indexMock; private ForwardedIndexProjectHandler handler; private Project.NameKey nameKey; @Before public void setUp() { - when(configMock.index()).thenReturn(indexMock); - when(indexMock.numStripedLocks()).thenReturn(10); - handler = new ForwardedIndexProjectHandler(indexerMock, configMock); + handler = new ForwardedIndexProjectHandler(indexerMock); nameKey = new Project.NameKey("project/name"); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigratorTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigratorTest.java deleted file mode 100644 index 4c24afd..0000000 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/SingleChangeNoteDbMigratorTest.java +++ /dev/null
@@ -1,133 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// 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 -// -package com.ericsson.gerrit.plugins.highavailability.forwarder; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.notedb.MutableNotesMigration; -import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage; -import com.google.gerrit.server.notedb.NotesMigration; -import com.google.gerrit.server.notedb.rebuild.MigrationException; -import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator; -import com.google.gwtorm.server.OrmException; -import com.google.inject.Provider; -import java.io.IOException; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDatabase; -import org.eclipse.jgit.lib.Repository; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class SingleChangeNoteDbMigratorTest { - @Mock Provider<NoteDbMigrator.Builder> migratorBuilderProvider; - - @Mock(answer = Answers.RETURNS_SELF) - NoteDbMigrator.Builder migratorBuilder; - - @Mock Config cfg; - @Mock GitRepositoryManager repoManager; - @Mock Repository repository; - @Mock RefDatabase refDatabase; - @Mock Ref ref; - @Mock NoteDbMigrator noteDbMigrator; - - NotesMigration migration; - Change.Id id = new Change.Id(1); - Project.NameKey project = new Project.NameKey("test_project"); - SingleChangeNoteDbMigrator objectUnderTest; - - @Before - public void setUp() throws Exception { - when(migratorBuilderProvider.get()).thenReturn(migratorBuilder); - when(migratorBuilder.build()).thenReturn(noteDbMigrator); - when(repoManager.openRepository(any())).thenReturn(repository); - when(repository.getRefDatabase()).thenReturn(refDatabase); - when(cfg.getBoolean(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(true); - when(refDatabase.exactRef(anyString())).thenReturn(null); - - migration = - MutableNotesMigration.newDisabled().setReadChanges(true).setDisableChangeReviewDb(false); - - objectUnderTest = - new SingleChangeNoteDbMigrator(migratorBuilderProvider, cfg, migration, repoManager); - } - - @Test - public void shouldMigrateChange() - throws MigrationException, OrmException, SingleChangeNoteDbMigrationException { - objectUnderTest.migrate(id, project); - verify(noteDbMigrator, times(1)).rebuild(); - } - - @Test - public void shouldSkipMigrationWhenMetaRefExists() - throws OrmException, IOException, SingleChangeNoteDbMigrationException { - when(refDatabase.exactRef(anyString())).thenReturn(ref); - objectUnderTest.migrate(id, project); - verify(noteDbMigrator, never()).rebuild(); - } - - @Test - public void shouldSkipMigrationWhenReviewDbIsDisabled() - throws OrmException, IOException, SingleChangeNoteDbMigrationException { - migration = - MutableNotesMigration.newDisabled() - .setReadChanges(true) - .setChangePrimaryStorage(PrimaryStorage.NOTE_DB) - .setDisableChangeReviewDb(true); - objectUnderTest = - new SingleChangeNoteDbMigrator(migratorBuilderProvider, cfg, migration, repoManager); - - objectUnderTest.migrate(id, project); - verify(noteDbMigrator, never()).rebuild(); - } - - @Test - public void shouldSkipMigrationWhenReadChangesIsFalse() - throws OrmException, IOException, SingleChangeNoteDbMigrationException { - migration = - MutableNotesMigration.newDisabled().setReadChanges(false).setDisableChangeReviewDb(false); - objectUnderTest = - new SingleChangeNoteDbMigrator(migratorBuilderProvider, cfg, migration, repoManager); - - objectUnderTest.migrate(id, project); - verify(noteDbMigrator, never()).rebuild(); - } - - @Test - public void shouldNotSkipMigrationWhenNotInTrialMode() - throws OrmException, IOException, SingleChangeNoteDbMigrationException { - when(cfg.getBoolean(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(false); - objectUnderTest = - new SingleChangeNoteDbMigrator(migratorBuilderProvider, cfg, migration, repoManager); - - objectUnderTest.migrate(id, project); - verify(noteDbMigrator, times(1)).rebuild(); - } -}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java index 835977f..7872785 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -16,7 +16,6 @@ import static com.google.common.net.MediaType.JSON_UTF_8; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; import static org.mockito.ArgumentMatchers.any; @@ -29,7 +28,7 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.events.EventTypes; import com.google.gerrit.server.events.RefEvent; -import com.google.gwtorm.server.OrmException; +import com.google.gerrit.server.permissions.PermissionBackendException; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -83,11 +82,11 @@ + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":" + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}"; when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event))); - doThrow(new OrmException(ERR_MSG)) + doThrow(new PermissionBackendException(ERR_MSG)) .when(forwardedEventHandlerMock) .dispatch(any(RefReplicationDoneEvent.class)); eventRestApiServlet.doPost(requestMock, responseMock); - verify(responseMock).sendError(SC_NOT_FOUND, "Change not found\n"); + verify(responseMock).sendError(SC_BAD_REQUEST, ERR_MSG); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java index 5e0d4c9..4071363 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -28,6 +28,7 @@ import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.stubbing.Scenario; +import com.google.gerrit.reviewdb.client.Project; import java.net.SocketTimeoutException; import org.junit.Before; import org.junit.Rule; @@ -70,7 +71,7 @@ when(configMock.http().socketTimeout()).thenReturn(TIMEOUT); when(configMock.http().retryInterval()).thenReturn(RETRY_INTERVAL); - httpSession = new HttpSession(new HttpClientProvider(configMock).get()); + httpSession = new HttpSession(new HttpClientProvider(configMock).get(), new GsonProvider()); } @Test @@ -170,4 +171,12 @@ assertThat(httpSession.post(uri).isSuccessful()).isFalse(); } + + @Test + public void encodeProjectName() { + String projectStr = "project"; + Project.NameKey project = Project.nameKey(projectStr); + String json = httpSession.jsonEncode(project); + assertThat(json).isEqualTo("\"" + projectStr + "\""); + } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java index c1fa765..fb3649e 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
@@ -15,7 +15,6 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -26,7 +25,6 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; -import com.google.gwtorm.server.OrmException; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -81,15 +79,6 @@ } @Test - public void indexerThrowsOrmExceptionTryingToIndexChange() throws Exception { - doThrow(new OrmException("some message")) - .when(handlerMock) - .index(eq(CHANGE_ID), eq(Operation.INDEX), any()); - servlet.doPost(requestMock, responseMock); - verify(responseMock).sendError(SC_NOT_FOUND, "Error trying to find change"); - } - - @Test public void sendErrorThrowsIOException() throws Exception { doThrow(new IOException(IO_ERROR)) .when(handlerMock)
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java index 5bd0cf8..cb90461 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -37,6 +37,8 @@ import com.google.inject.Provider; import java.io.IOException; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import org.junit.Before; import org.junit.Test; @@ -91,17 +93,22 @@ private static final String EVENT_ENDPOINT = Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "event", event.type); + private static final long TEST_TIMEOUT = 10; + private static final TimeUnit TEST_TIMEOUT_UNITS = TimeUnit.SECONDS; + private RestForwarder forwarder; private HttpSession httpSessionMock; + private Configuration configMock; + Provider<Set<PeerInfo>> peersMock; @SuppressWarnings("unchecked") @Before public void setUp() { httpSessionMock = mock(HttpSession.class); - Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS); + configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS); when(configMock.http().maxTries()).thenReturn(3); when(configMock.http().retryInterval()).thenReturn(10); - Provider<Set<PeerInfo>> peersMock = mock(Provider.class); + peersMock = mock(Provider.class); when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL))); forwarder = new RestForwarder( @@ -109,126 +116,169 @@ PLUGIN_NAME, configMock, peersMock, - gsonProvider); // TODO: Create provider + gsonProvider, // TODO: Create provider + new RestForwarderScheduler(Executors.newScheduledThreadPool(1))); } @Test public void testIndexAccountOK() throws Exception { when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isTrue(); + assertThat( + forwarder + .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test public void testIndexAccountFailed() throws Exception { when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexAccountThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_ACCOUNT_ENDPOINT), any()); - assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexGroupOK() throws Exception { when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isTrue(); + assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test public void testIndexGroupFailed() throws Exception { when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse(); + assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexGroupThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_GROUP_ENDPOINT), any()); - assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse(); + assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexChangeOK() throws Exception { when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue(); + assertThat( + forwarder + .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test public void testIndexChangeFailed() throws Exception { when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexChangeThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_CHANGE_ENDPOINT), any()); - assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testIndexBatchChangeOK() throws Exception { when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue(); + assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + .isTrue(); } @Test public void testIndexBatchChangeFailed() throws Exception { when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + .isFalse(); } @Test public void testIndexBatchChangeThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + .isFalse(); } @Test public void testChangeDeletedFromIndexOK() throws Exception { when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT))) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isTrue(); + assertThat( + forwarder + .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test public void testChangeDeletedFromIndexFailed() throws Exception { when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT))) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testChangeDeletedFromThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).delete(eq(DELETE_CHANGE_ENDPOINT)); - assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse(); + assertThat( + forwarder + .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test public void testEventSentOK() throws Exception { when(httpSessionMock.post(EVENT_ENDPOINT, event)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.send(event)).isTrue(); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isTrue(); } @Test public void testEventSentFailed() throws Exception { when(httpSessionMock.post(EVENT_ENDPOINT, event)).thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.send(event)).isFalse(); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse(); } @Test public void testEventSentThrowsException() throws Exception { doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT, event); - assertThat(forwarder.send(event)).isFalse(); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse(); } @Test @@ -237,7 +287,8 @@ String keyJson = gson.toJson(key); when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.PROJECTS, key)).isTrue(); + assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -246,7 +297,8 @@ String keyJson = gson.toJson(key); when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.ACCOUNTS, key)).isTrue(); + assertThat(forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -255,7 +307,8 @@ String keyJson = gson.toJson(key); String endpoint = buildCacheEndpoint(Constants.GROUPS); when(httpSessionMock.post(endpoint, keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.GROUPS, key)).isTrue(); + assertThat(forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -264,7 +317,9 @@ String keyJson = gson.toJson(key); when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE), keyJson)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.GROUPS_BYINCLUDE, key)).isTrue(); + assertThat( + forwarder.evict(Constants.GROUPS_BYINCLUDE, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -273,7 +328,8 @@ String keyJson = gson.toJson(key); when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS), keyJson)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key)).isTrue(); + assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -282,7 +338,8 @@ String keyJson = gson.toJson(key); when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson)) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse(); + assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test @@ -292,7 +349,8 @@ doThrow(new IOException()) .when(httpSessionMock) .post(buildCacheEndpoint(Constants.PROJECTS), keyJson); - assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse(); + assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } private static String buildCacheEndpoint(String name) { @@ -304,7 +362,8 @@ String projectName = PROJECT_TO_ADD; when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.addToProjectList(projectName)).isTrue(); + assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -312,7 +371,8 @@ String projectName = PROJECT_TO_ADD; when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null)) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.addToProjectList(projectName)).isFalse(); + assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test @@ -321,7 +381,8 @@ doThrow(new IOException()) .when(httpSessionMock) .post(buildProjectListCacheEndpoint(projectName), null); - assertThat(forwarder.addToProjectList(projectName)).isFalse(); + assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test @@ -329,7 +390,8 @@ String projectName = PROJECT_TO_DELETE; when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName))) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.removeFromProjectList(projectName)).isTrue(); + assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test @@ -337,7 +399,8 @@ String projectName = PROJECT_TO_DELETE; when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName))) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.removeFromProjectList(projectName)).isFalse(); + assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test @@ -346,7 +409,8 @@ doThrow(new IOException()) .when(httpSessionMock) .delete((buildProjectListCacheEndpoint(projectName))); - assertThat(forwarder.removeFromProjectList(projectName)).isFalse(); + assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } private static String buildProjectListCacheEndpoint(String projectName) { @@ -354,41 +418,57 @@ } @Test - public void testRetryOnErrorThenSuccess() throws IOException { + public void testRetryOnErrorThenSuccess() throws Exception { when(httpSessionMock.post(anyString(), anyString())) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(true, SUCCESS)); - assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue(); + assertThat( + forwarder + .evict(Constants.PROJECT_LIST, new Object()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test - public void testRetryOnIoExceptionThenSuccess() throws IOException { + public void testRetryOnIoExceptionThenSuccess() throws Exception { when(httpSessionMock.post(anyString(), anyString())) .thenThrow(new IOException()) .thenThrow(new IOException()) .thenReturn(new HttpResult(true, SUCCESS)); - assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue(); + assertThat( + forwarder + .evict(Constants.PROJECT_LIST, new Object()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isTrue(); } @Test - public void testNoRetryAfterNonRecoverableException() throws IOException { + public void testNoRetryAfterNonRecoverableException() throws Exception { when(httpSessionMock.post(anyString(), anyString())) .thenThrow(new SSLException("Non Recoverable")) .thenReturn(new HttpResult(true, SUCCESS)); - assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse(); + assertThat( + forwarder + .evict(Constants.PROJECT_LIST, new Object()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } @Test - public void testFailureAfterMaxTries() throws IOException { + public void testFailureAfterMaxTries() throws Exception { when(httpSessionMock.post(anyString(), anyString())) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)); - assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse(); + assertThat( + forwarder + .evict(Constants.PROJECT_LIST, new Object()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .isFalse(); } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java index 4b2462e..a66e896 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
@@ -26,11 +26,11 @@ @Override public String getExpectedRequest() { - return "/plugins/high-availability/index/account/" + testAccount.id; + return "/plugins/high-availability/index/account/" + testAccount.id(); } @Override public void doAction() throws Exception { - gApi.accounts().id(testAccount.id.get()).setActive(false); + gApi.accounts().id(testAccount.id().get()).setActive(false); } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java similarity index 90% rename from src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java index b691034..bc5210d 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
@@ -21,28 +21,20 @@ import com.google.gerrit.acceptance.PushOneCommit.Result; import com.google.gerrit.acceptance.TestPlugin; import com.google.gerrit.reviewdb.client.RefNames; -import com.google.gerrit.testing.NoteDbMode; -import com.google.gwtorm.server.OrmException; import java.io.IOException; import java.util.Optional; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.junit.BeforeClass; import org.junit.Test; @TestPlugin( name = "high-availability", sysModule = "com.ericsson.gerrit.plugins.highavailability.Module", httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule") -public class ChangeCheckerNoteDbIT extends LightweightPluginDaemonTest { +public class ChangeCheckerIT extends LightweightPluginDaemonTest { ChangeCheckerImpl.Factory changeCheckerFactory; - @BeforeClass - public static void setupTestSuite() { - System.setProperty("gerrit.notedb", NoteDbMode.ON.name()); - } - @Override public void setUpTestPlugin() throws Exception { super.setUpTestPlugin(); @@ -50,7 +42,7 @@ } @Test - public void shouldPopulateMetaShaWhenNoteDb() throws Exception { + public void shouldPopulateMetaSha() throws Exception { Result change = createChange(); ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); Optional<IndexEvent> eventOption = changeChecker.newIndexEvent(); @@ -121,7 +113,7 @@ assertThat(changeChecker.isChangeUpToDate(event)).isFalse(); } - private String readMetaSha(Result change) throws IOException, OrmException { + private String readMetaSha(Result change) throws IOException { try (Repository repo = repoManager.openRepository(change.getChange().change().getProject())) { String refName = RefNames.changeMetaRef(change.getChange().getId()); Ref ref = repo.exactRef(refName);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java deleted file mode 100644 index a6cb449..0000000 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java +++ /dev/null
@@ -1,116 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// 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 com.ericsson.gerrit.plugins.highavailability.index; - -import static com.google.common.truth.Truth.assertThat; - -import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; -import com.google.gerrit.acceptance.LightweightPluginDaemonTest; -import com.google.gerrit.acceptance.PushOneCommit.Result; -import com.google.gerrit.acceptance.TestPlugin; -import com.google.gerrit.testing.NoteDbMode; -import java.util.Optional; -import org.junit.BeforeClass; -import org.junit.Test; - -@TestPlugin( - name = "high-availability", - sysModule = "com.ericsson.gerrit.plugins.highavailability.Module", - httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule") -public class ChangeCheckerReviewDbIT extends LightweightPluginDaemonTest { - - ChangeCheckerImpl.Factory changeCheckerFactory; - - @BeforeClass - public static void setupTestSuite() { - System.setProperty("gerrit.notedb", NoteDbMode.OFF.name()); - } - - @Override - public void setUpTestPlugin() throws Exception { - super.setUpTestPlugin(); - changeCheckerFactory = plugin.getSysInjector().getInstance(ChangeCheckerImpl.Factory.class); - } - - @Test - public void shouldNotPopulateMetaShaWhenReviewDb() throws Exception { - Result change = createChange(); - ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); - Optional<IndexEvent> eventOption = changeChecker.newIndexEvent(); - - assertThat(eventOption.isPresent()).isTrue(); - IndexEvent event = eventOption.get(); - assertThat(event.metaSha).isNull(); - } - - @Test - public void shouldReturnIsUpToDateTrueWhenEventContainsCorrectMetaAndTargetSha() - throws Exception { - Result change = createChange(); - ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); - Optional<IndexEvent> event = changeChecker.newIndexEvent(); - - assertThat(changeChecker.isChangeUpToDate(event)).isTrue(); - } - - @Test - public void shouldReturnIsUpToDateTrueWhenMetaShaIsNull() throws Exception { - Result change = createChange(); - ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); - Optional<IndexEvent> event = - changeChecker - .newIndexEvent() - .map( - e -> { - e.metaSha = null; - return e; - }); - - assertThat(changeChecker.isChangeUpToDate(event)).isTrue(); - } - - @Test - public void shouldReturnIsUpToDateTrueWhenTargetShaIsNull() throws Exception { - Result change = createChange(); - ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); - Optional<IndexEvent> event = - changeChecker - .newIndexEvent() - .map( - e -> { - e.targetSha = null; - return e; - }); - - assertThat(changeChecker.isChangeUpToDate(event)).isTrue(); - } - - @Test - public void shouldReturnFalseWhenTargetShaIsNotUpToDate() throws Exception { - String testTargetRefSha = "abed47baf2818a86b68cf712073a748a6b5b293e"; - Result change = createChange(); - ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId()); - Optional<IndexEvent> event = - changeChecker - .newIndexEvent() - .map( - e -> { - e.targetSha = testTargetRefSha; - return e; - }); - - assertThat(changeChecker.isChangeUpToDate(event)).isFalse(); - } -}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java index 4c20ae1..5f08cc5 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -32,20 +33,37 @@ import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexAccountTask; import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexChangeTask; import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexGroupTask; -import com.google.common.util.concurrent.MoreExecutors; +import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask; +import com.ericsson.gerrit.plugins.highavailability.index.IndexEventLocks.VoidFunction; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.util.OneOffRequestContext; import com.google.gerrit.server.util.RequestContext; import com.google.gerrit.server.util.ThreadLocalRequestContext; +import java.util.Collection; +import java.util.List; import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -56,6 +74,8 @@ private static final int ACCOUNT_ID = 2; private static final String UUID = "3"; private static final String OTHER_UUID = "4"; + private static final Integer INDEX_WAIT_TIMEOUT_MS = 5; + private static final int MAX_TEST_PARALLELISM = 4; private IndexEventHandler indexEventHandler; @Mock private Forwarder forwarder; @@ -64,7 +84,13 @@ private Change.Id changeId; private Account.Id accountId; private AccountGroup.UUID accountGroupUUID; + private ScheduledExecutorService executor = new CurrentThreadScheduledExecutorService(); + private ScheduledExecutorService batchExecutor = new CurrentThreadScheduledExecutorService(); + private ScheduledExecutorService testExecutor = + Executors.newScheduledThreadPool(MAX_TEST_PARALLELISM); @Mock private RequestContext mockCtx; + @Mock private Configuration configuration; + private IndexEventLocks idLocks; private CurrentRequestContext currCtx = new CurrentRequestContext(null, null, null) { @@ -82,18 +108,48 @@ when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerMock); when(changeCheckerMock.newIndexEvent()).thenReturn(Optional.of(new IndexEvent())); + Configuration.Index cfgIndex = mock(Configuration.Index.class); + when(configuration.index()).thenReturn(cfgIndex); + when(cfgIndex.numStripedLocks()).thenReturn(Configuration.DEFAULT_NUM_STRIPED_LOCKS); + + Configuration.Http http = mock(Configuration.Http.class); + when(configuration.http()).thenReturn(http); + when(http.maxTries()).thenReturn(Configuration.Http.DEFAULT_MAX_TRIES); + when(http.retryInterval()).thenReturn(Configuration.Http.DEFAULT_RETRY_INTERVAL); + when(forwarder.indexAccount(eq(ACCOUNT_ID), any())) + .thenReturn(CompletableFuture.completedFuture(true)); + when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any())) + .thenReturn(CompletableFuture.completedFuture(true)); + when(forwarder.indexGroup(eq(UUID), any())).thenReturn(CompletableFuture.completedFuture(true)); + when(forwarder.indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any())) + .thenReturn(CompletableFuture.completedFuture(true)); + + idLocks = new IndexEventLocks(configuration); setUpIndexEventHandler(currCtx); } public void setUpIndexEventHandler(CurrentRequestContext currCtx) throws Exception { + setUpIndexEventHandler(currCtx, idLocks, configuration); + } + + public void setUpIndexEventHandler(CurrentRequestContext currCtx, IndexEventLocks idLocks) + throws Exception { + setUpIndexEventHandler(currCtx, idLocks, configuration); + } + + public void setUpIndexEventHandler( + CurrentRequestContext currCtx, IndexEventLocks idLocks, Configuration configuration) + throws Exception { indexEventHandler = new IndexEventHandler( - MoreExecutors.directExecutor(), - MoreExecutors.directExecutor(), + executor, + batchExecutor, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, - currCtx); + currCtx, + configuration, + idLocks); } @Test @@ -116,6 +172,160 @@ } @Test + public void shouldNotIndexChangeWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); + + verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()); + } + + @Test + public void shouldNotIndexAccountWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onAccountIndexed(accountId.get()); + + verify(forwarder, never()).indexAccount(eq(ACCOUNT_ID), any()); + } + + @Test + public void shouldNotDeleteChangeWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onChangeDeleted(changeId.get()); + + verify(forwarder, never()).deleteChangeFromIndex(eq(CHANGE_ID), any()); + } + + @Test + public void shouldNotIndexGroupWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + + verify(forwarder, never()).indexGroup(eq(UUID), any()); + } + + @Test + public void shouldNotIndexProjectWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onProjectIndexed(PROJECT_NAME); + + verify(forwarder, never()).indexProject(eq(PROJECT_NAME), any()); + } + + @Test + public void shouldRetryIndexChangeWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false, true); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); + + verify(locks, times(2)).withLock(any(), any(), any()); + verify(forwarder, times(1)).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()); + } + + @Test + public void shouldRetryUpToMaxTriesWhenCannotAcquireLock() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + + Configuration cfg = mock(Configuration.class); + Configuration.Http httpCfg = mock(Configuration.Http.class); + when(httpCfg.maxTries()).thenReturn(10); + when(cfg.http()).thenReturn(httpCfg); + setUpIndexEventHandler(currCtx, locks, cfg); + + indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); + + verify(locks, times(11)).withLock(any(), any(), any()); + verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()); + } + + @Test + public void shouldNotRetryWhenMaxTriesLowerThanOne() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore semaphore = mock(Semaphore.class); + when(locks.getSemaphore(anyString())).thenReturn(semaphore); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + + Configuration cfg = mock(Configuration.class); + Configuration.Http httpCfg = mock(Configuration.Http.class); + when(httpCfg.maxTries()).thenReturn(0); + when(cfg.http()).thenReturn(httpCfg); + setUpIndexEventHandler(currCtx, locks, cfg); + + indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); + + verify(locks, times(1)).withLock(any(), any(), any()); + verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()); + } + + @Test + public void shouldLockPerIndexEventType() throws Exception { + IndexEventLocks locks = mock(IndexEventLocks.class); + Semaphore indexChangeLock = mock(Semaphore.class); + when(indexChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(false); + Semaphore accountChangeLock = mock(Semaphore.class); + when(accountChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS))) + .thenReturn(true); + when(locks.getSemaphore(eq("change/" + CHANGE_ID))).thenReturn(indexChangeLock); + when(locks.getSemaphore(eq("account/" + ACCOUNT_ID))).thenReturn(accountChangeLock); + Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any()); + setUpIndexEventHandler(currCtx, locks); + + indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); + indexEventHandler.onAccountIndexed(accountId.get()); + + verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()); + verify(forwarder).indexAccount(eq(ACCOUNT_ID), any()); + } + + @Test public void shouldReindexInRemoteWhenContextIsMissingButForcedIndexingEnabled() throws Exception { ThreadLocalRequestContext threadLocalCtxMock = mock(ThreadLocalRequestContext.class); OneOffRequestContext oneOffCtxMock = mock(OneOffRequestContext.class); @@ -182,7 +392,14 @@ ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class); indexEventHandler = new IndexEventHandler( - poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx); + poolMock, + poolBatchMock, + PLUGIN_NAME, + forwarder, + changeCheckerFactoryMock, + currCtx, + configuration, + idLocks); indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get()); verify(poolMock, times(1)) @@ -195,7 +412,14 @@ ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class); indexEventHandler = new IndexEventHandler( - poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx); + poolMock, + poolBatchMock, + PLUGIN_NAME, + forwarder, + changeCheckerFactoryMock, + currCtx, + configuration, + idLocks); indexEventHandler.onAccountIndexed(accountId.get()); indexEventHandler.onAccountIndexed(accountId.get()); verify(poolMock, times(1)).execute(indexEventHandler.new IndexAccountTask(ACCOUNT_ID)); @@ -207,7 +431,14 @@ ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class); indexEventHandler = new IndexEventHandler( - poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx); + poolMock, + poolBatchMock, + PLUGIN_NAME, + forwarder, + changeCheckerFactoryMock, + currCtx, + configuration, + idLocks); indexEventHandler.onGroupIndexed(accountGroupUUID.get()); indexEventHandler.onGroupIndexed(accountGroupUUID.get()); verify(poolMock, times(1)).execute(indexEventHandler.new IndexGroupTask(UUID)); @@ -323,4 +554,226 @@ assertThat(task.equals(differentGroupIdTask)).isFalse(); assertThat(task.hashCode()).isNotEqualTo(differentGroupIdTask.hashCode()); } + + class TestTask<T> implements Runnable { + private IndexTask task; + private CyclicBarrier testBarrier; + private Supplier<T> successFunc; + private VoidFunction failureFunc; + private CompletableFuture<T> future; + + public TestTask( + IndexTask task, + CyclicBarrier testBarrier, + Supplier<T> successFunc, + VoidFunction failureFunc) { + this.task = task; + this.testBarrier = testBarrier; + this.successFunc = successFunc; + this.failureFunc = failureFunc; + this.future = new CompletableFuture<>(); + } + + @SuppressWarnings("unchecked") + @Override + public void run() { + try { + testBarrier.await(); + idLocks + .withLock( + task, + () -> + runLater( + INDEX_WAIT_TIMEOUT_MS * 2, + () -> CompletableFuture.completedFuture(successFunc.get())), + failureFunc) + .whenComplete( + (v, t) -> { + if (t == null) { + future.complete((T) v); + } else { + future.completeExceptionally(t); + } + }); + } catch (Throwable t) { + future = new CompletableFuture<>(); + future.completeExceptionally(t); + } + } + + public void join() { + try { + future.join(); + } catch (Exception e) { + } + } + + private CompletableFuture<T> runLater( + long scheduledTimeMsec, Supplier<CompletableFuture<T>> supplier) { + CompletableFuture<T> resFuture = new CompletableFuture<>(); + testExecutor.schedule( + () -> { + try { + return supplier + .get() + .whenComplete( + (v, t) -> { + if (t == null) { + resFuture.complete(v); + } + resFuture.completeExceptionally(t); + }); + } catch (Throwable t) { + return resFuture.completeExceptionally(t); + } + }, + scheduledTimeMsec, + TimeUnit.MILLISECONDS); + return resFuture; + } + } + + @Test + public void indexLocksShouldBlockConcurrentIndexChange() throws Exception { + IndexChangeTask indexTask1 = + indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent()); + IndexChangeTask indexTask2 = + indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent()); + testIsolationOfCuncurrentIndexTasks(indexTask1, indexTask2); + } + + @Test + public void indexLocksShouldBlockConcurrentIndexAndDeleteChange() throws Exception { + IndexChangeTask indexTask = + indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent()); + DeleteChangeTask deleteTask = + indexEventHandler.new DeleteChangeTask(CHANGE_ID, new IndexEvent()); + testIsolationOfCuncurrentIndexTasks(indexTask, deleteTask); + } + + private void testIsolationOfCuncurrentIndexTasks(IndexTask indexTask1, IndexTask indexTask2) + throws Exception { + AtomicInteger changeIndexedCount = new AtomicInteger(); + AtomicInteger lockFailedCounts = new AtomicInteger(); + CyclicBarrier changeThreadsSync = new CyclicBarrier(2); + + TestTask<Integer> task1 = + new TestTask<>( + indexTask1, + changeThreadsSync, + () -> changeIndexedCount.incrementAndGet(), + () -> lockFailedCounts.incrementAndGet()); + TestTask<Integer> task2 = + new TestTask<>( + indexTask2, + changeThreadsSync, + () -> changeIndexedCount.incrementAndGet(), + () -> lockFailedCounts.incrementAndGet()); + + new Thread(task1).start(); + new Thread(task2).start(); + task1.join(); + task2.join(); + + /* Both assertions needs to be true, the order doesn't really matter: + * - Only one of the two tasks should succeed + * - Only one of the two tasks should fail to acquire the lock + */ + assertThat(changeIndexedCount.get()).isEqualTo(1); + assertThat(lockFailedCounts.get()).isEqualTo(1); + } + + private class CurrentThreadScheduledExecutorService implements ScheduledExecutorService { + + @Override + public void shutdown() {} + + @Override + public List<Runnable> shutdownNow() { + return null; + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return false; + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + return null; + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + return null; + } + + @Override + public Future<?> submit(Runnable task) { + return null; + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + return null; + } + + @Override + public <T> List<Future<T>> invokeAll( + Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return null; + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws InterruptedException, ExecutionException { + return null; + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + @Override + public void execute(Runnable command) { + command.run(); + } + + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + command.run(); + return null; + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + return null; + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + return null; + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + return null; + } + } }
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json index 281515d..43ee6ec 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
@@ -1,6 +1,6 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects", + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects", "entries": "PROJECTS_ENTRIES" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json index 3fd506d..f128e73 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
@@ -1,6 +1,6 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT2/_PROJECT", + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/_PROJECT", "cmd": "clone" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json index b535c1d..a167922 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
@@ -1,6 +1,6 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT1/a/changes/", + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/changes/", "project": "_PROJECT" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json index da3f028..2e5073b 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
@@ -1,5 +1,5 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT1/a/projects/PROJECT" + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/projects/PROJECT" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json index 8e0a304..4ecbe68 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
@@ -1,6 +1,6 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT2/a/changes/", + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/changes/", "number": "NUMBER" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json index be47699..2405b03 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
@@ -1,5 +1,5 @@ [ { - "url": "http://HOSTNAME/a/projects/PROJECT/delete-project~delete" + "url": "HTTP_SCHEME://HOSTNAME/a/projects/PROJECT/delete-project~delete" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json index c938973..ae904f5 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
@@ -1,5 +1,5 @@ [ { - "url": "http://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush" + "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush" } ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json index f7450a4..25521c7 100644 --- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json +++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
@@ -1,5 +1,5 @@ [ { - "url": "http://HOSTNAME/a/config/server/caches/projects" + "url": "HTTP_SCHEME://HOSTNAME/a/config/server/caches/projects" } ]
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala index 10374f8..6a0d07f 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
@@ -32,7 +32,7 @@ this.producer = Some(producer) } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(session => { if (producer.nonEmpty) { @@ -41,12 +41,12 @@ session } }) - .exec(http(unique).get("${url}") + .exec(http(uniqueName).get("${url}") .check(regex("\"" + memKey + "\": (\\d+)") .is(session => session(entriesKey).as[String]))) setUp( test.inject( - atOnceUsers(1) + atOnceUsers(single) )).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala index 13a741a..4fa0ca5 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
@@ -23,37 +23,37 @@ class CloneUsingHAGerrit2 extends GitSimulation { private val data: FeederBuilder = jsonFile(resource).convert(keys).queue - private var default: String = name + private var projectName = className - def this(default: String) { + def this(projectName: String) { this() - this.default = default + this.projectName = projectName } override def replaceOverride(in: String): String = { val next = replaceProperty("http_port2", 8082, in) - replaceKeyWith("_project", default, next) + replaceKeyWith("_project", projectName, next) } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(gitRequest) - private val createProject = new CreateProjectUsingHAGerrit1(default) - private val deleteProject = new DeleteProjectUsingHAGerrit(default) + private val createProject = new CreateProjectUsingHAGerrit1(projectName) + private val deleteProject = new DeleteProjectUsingHAGerrit(projectName) setUp( createProject.test.inject( nothingFor(stepWaitTime(createProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), test.inject( nothingFor(stepWaitTime(this) seconds), - atOnceUsers(1) - ), + atOnceUsers(single) + ).protocols(gitProtocol), deleteProject.test.inject( nothingFor(stepWaitTime(deleteProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), - ).protocols(gitProtocol, httpProtocol) + ).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala index f07aabd..26732b6 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
@@ -24,7 +24,7 @@ class CreateChangeUsingHAGerrit1 extends GerritSimulation { private val data: FeederBuilder = jsonFile(resource).convert(keys).queue - private val default: String = name + private val projectName = className private val numberKey = "_number" override def relativeRuntimeWeight = 10 @@ -33,7 +33,7 @@ replaceProperty("http_port1", 8081, in) } - private val test: ScenarioBuilder = scenario(unique) + private val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(httpRequest .body(ElFileBody(body)).asJson @@ -43,26 +43,26 @@ session }) - private val createProject = new CreateProjectUsingHAGerrit1(default) - private val deleteProject = new DeleteProjectUsingHAGerrit(default) + private val createProject = new CreateProjectUsingHAGerrit1(projectName) + private val deleteProject = new DeleteProjectUsingHAGerrit(projectName) private val deleteChange = new DeleteChangeUsingHAGerrit2 setUp( createProject.test.inject( nothingFor(stepWaitTime(createProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), test.inject( nothingFor(stepWaitTime(this) seconds), - atOnceUsers(1) + atOnceUsers(single) ), deleteChange.test.inject( nothingFor(stepWaitTime(deleteChange) seconds), - atOnceUsers(1) + atOnceUsers(single) ), deleteProject.test.inject( nothingFor(stepWaitTime(deleteProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), ).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala index 964aadb..5773bcf 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
@@ -22,9 +22,9 @@ class CreateProjectUsingHAGerrit1 extends ProjectSimulation { private val data: FeederBuilder = jsonFile(resource).convert(keys).queue - def this(default: String) { + def this(projectName: String) { this() - this.default = default + this.projectName = projectName } override def replaceOverride(in: String): String = { @@ -32,12 +32,12 @@ super.replaceOverride(next) } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(httpRequest.body(RawFileBody(body)).asJson) setUp( test.inject( - atOnceUsers(1) + atOnceUsers(single) )).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala index a4da93c..fd83e7a 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
@@ -20,34 +20,34 @@ import scala.concurrent.duration._ class CreateProjectUsingHAGerritTwice extends GitSimulation { - private val default: String = name + private val projectName = className - private val createProject = new CreateProjectUsingHAGerrit1(default) - private val deleteProject = new DeleteProjectUsingHAGerrit(default) - private val createItAgain = new CreateProjectUsingHAGerrit1(default) - private val verifyProject = new CloneUsingHAGerrit2(default) - private val deleteItAfter = new DeleteProjectUsingHAGerrit(default) + private val createProject = new CreateProjectUsingHAGerrit1(projectName) + private val deleteProject = new DeleteProjectUsingHAGerrit(projectName) + private val createItAgain = new CreateProjectUsingHAGerrit1(projectName) + private val verifyProject = new CloneUsingHAGerrit2(projectName) + private val deleteItAfter = new DeleteProjectUsingHAGerrit(projectName) setUp( createProject.test.inject( nothingFor(stepWaitTime(createProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), deleteProject.test.inject( nothingFor(stepWaitTime(deleteProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), createItAgain.test.inject( nothingFor(stepWaitTime(createItAgain) seconds), - atOnceUsers(1) + atOnceUsers(single) ), verifyProject.test.inject( nothingFor(stepWaitTime(verifyProject) seconds), - atOnceUsers(1) - ), + atOnceUsers(single) + ).protocols(gitProtocol), deleteItAfter.test.inject( nothingFor(stepWaitTime(deleteItAfter) seconds), - atOnceUsers(1) + atOnceUsers(single) ), - ).protocols(gitProtocol, httpProtocol) + ).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala index 5b0f461..d3dc7c1 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
@@ -30,7 +30,7 @@ replaceProperty("http_port2", 8082, in) } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(session => { if (number.nonEmpty) { @@ -39,10 +39,10 @@ session } }) - .exec(http(unique).delete("${url}${number}")) + .exec(http(uniqueName).delete("${url}${number}")) setUp( test.inject( - atOnceUsers(1) + atOnceUsers(single) )).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala index 428085d..cdc6e9e 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
@@ -22,17 +22,17 @@ class DeleteProjectUsingHAGerrit extends ProjectSimulation { private val data: FeederBuilder = jsonFile(resource).convert(keys).queue - def this(default: String) { + def this(projectName: String) { this() - this.default = default + this.projectName = projectName } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(httpRequest) setUp( test.inject( - atOnceUsers(1) + atOnceUsers(single) )).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala index 9c317d4..4f619b8 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
@@ -23,7 +23,7 @@ class FlushProjectsCacheUsingHAGerrit2 extends CacheFlushSimulation { private val data: FeederBuilder = jsonFile(resource).convert(keys).queue - private val default: String = name + private val projectName = className override def relativeRuntimeWeight = 2 @@ -31,35 +31,35 @@ replaceProperty("http_port2", 8082, in) } - private val flushCache: ScenarioBuilder = scenario(unique) + private val flushCache: ScenarioBuilder = scenario(uniqueName) .feed(data) .exec(httpRequest) - private val createProject = new CreateProjectUsingHAGerrit1(default) + private val createProject = new CreateProjectUsingHAGerrit1(projectName) private val getCacheEntriesAfterProject = new GetProjectsCacheEntries(this) private val checkCacheEntriesAfterFlush = new CheckProjectsCacheFlushEntriesUsingHAGerrit1(this) - private val deleteProject = new DeleteProjectUsingHAGerrit(default) + private val deleteProject = new DeleteProjectUsingHAGerrit(projectName) setUp( createProject.test.inject( nothingFor(stepWaitTime(createProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), getCacheEntriesAfterProject.test.inject( nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), flushCache.inject( nothingFor(stepWaitTime(this) seconds), - atOnceUsers(1) + atOnceUsers(single) ), checkCacheEntriesAfterFlush.test.inject( nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds), - atOnceUsers(1) + atOnceUsers(single) ), deleteProject.test.inject( nothingFor(stepWaitTime(deleteProject) seconds), - atOnceUsers(1) + atOnceUsers(single) ), ).protocols(httpProtocol) }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala index 9565365..ed19c0b 100644 --- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala +++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
@@ -28,9 +28,9 @@ this.consumer = Some(consumer) } - val test: ScenarioBuilder = scenario(unique) + val test: ScenarioBuilder = scenario(uniqueName) .feed(data) - .exec(http(unique).get("${url}") + .exec(http(uniqueName).get("${url}") .check(regex("\"" + memKey + "\": (\\d+)").saveAs(entriesKey))) .exec(session => { if (consumer.nonEmpty) { @@ -41,6 +41,6 @@ setUp( test.inject( - atOnceUsers(1) + atOnceUsers(single) )).protocols(httpProtocol) }