Merge branch 'stable-3.10' into stable-3.11 * stable-3.10: Use a proper NFSv4 client for mounting the shared git Remove LockWrapper.Factory binding Optimize IndexEvent retries on the same change Change-Id: Iaf0960a595d4cdc0ba532c0b39fdc6646f3d59b0
diff --git a/BUILD b/BUILD index 9bb0445..3735cb3 100644 --- a/BUILD +++ b/BUILD
@@ -20,10 +20,11 @@ ], resources = glob(["src/main/resources/**/*"]), deps = [ - "@jgroups//jar", - "@jgroups-kubernetes//jar", - "@failsafe//jar", - ":global-refdb-neverlink", + ":global-refdb-neverlink", + "@auto-value//jar", + "@failsafe//jar", + "@jgroups-kubernetes//jar", + "@jgroups//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 55f7375..8d7c86a 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -66,6 +66,7 @@ private final Main main; private final AutoReindex autoReindex; + private final IndexSync indexSync; private final PeerInfo peerInfo; private final JGroups jgroups; private final JGroupsKubernetes jgroupsKubernetes; @@ -98,6 +99,7 @@ public Configuration(Config cfg, SitePaths site) { main = new Main(site, cfg); autoReindex = new AutoReindex(cfg); + indexSync = new IndexSync(cfg); peerInfo = new PeerInfo(cfg); switch (peerInfo.strategy()) { case STATIC: @@ -141,6 +143,10 @@ return autoReindex; } + public IndexSync indexSync() { + return indexSync; + } + public PeerInfo peerInfo() { return peerInfo; } @@ -276,6 +282,60 @@ } } + public static class IndexSync { + + static final String INDEX_SYNC_SECTION = "indexSync"; + static final String ENABLED = "enabled"; + static final String DELAY = "delay"; + static final String PERIOD = "period"; + static final String INITIAL_SYNC_AGE = "initialSyncAge"; + static final String SYNC_AGE = "syncAge"; + + static final boolean DEFAULT_SYNC_INDEX = false; + static final Duration DEFAULT_DELAY = Duration.ofSeconds(0); + static final Duration DEFAULT_PERIOD = Duration.ofSeconds(2); + static final String DEFAULT_INITIAL_SYNC_AGE = "1hour"; + static final String DEFAULT_SYNC_AGE = "1minute"; + + private final boolean enabled; + private final Duration delay; + private final Duration period; + private final String initialSyncAge; + private final String syncAge; + + public IndexSync(Config cfg) { + enabled = cfg.getBoolean(INDEX_SYNC_SECTION, ENABLED, DEFAULT_SYNC_INDEX); + delay = getDuration(cfg, INDEX_SYNC_SECTION, DELAY, DEFAULT_DELAY); + period = getDuration(cfg, INDEX_SYNC_SECTION, PERIOD, DEFAULT_PERIOD); + + String v = cfg.getString(INDEX_SYNC_SECTION, "", INITIAL_SYNC_AGE); + initialSyncAge = v != null ? v : DEFAULT_INITIAL_SYNC_AGE; + + v = cfg.getString(INDEX_SYNC_SECTION, "", SYNC_AGE); + syncAge = v != null ? v : DEFAULT_SYNC_AGE; + } + + public boolean enabled() { + return enabled; + } + + public Duration delay() { + return delay; + } + + public Duration period() { + return period; + } + + public String initialSyncAge() { + return initialSyncAge; + } + + public String syncAge() { + return syncAge; + } + } + public static class PeerInfo { static final PeerInfoStrategy DEFAULT_PEER_INFO_STRATEGY = PeerInfoStrategy.STATIC; static final String STRATEGY_KEY = "strategy";
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java index d9d5c50..4611790 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -21,6 +21,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups.JGroupsForwarderModule; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderModule; import com.ericsson.gerrit.plugins.highavailability.index.IndexModule; +import com.ericsson.gerrit.plugins.highavailability.indexsync.IndexSyncModule; import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfoModule; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.inject.Inject; @@ -63,6 +64,9 @@ } if (config.index().synchronize()) { install(new IndexModule()); + if (config.indexSync().enabled()) { + install(new IndexSyncModule()); + } } if (config.autoReindex().enabled()) { install(new AutoReindexModule());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java index 8f37d9c..9229d01 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
@@ -20,6 +20,7 @@ import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper; import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate; import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbExceptionHook; import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager; import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase; import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate; @@ -29,6 +30,8 @@ import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement; import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement; import com.google.gerrit.extensions.config.FactoryModule; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.server.ExceptionHook; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; import com.google.inject.Scopes; @@ -64,5 +67,7 @@ .to(CustomSharedRefEnforcementByProject.class) .in(Scopes.SINGLETON); } + + DynamicSet.bind(binder(), ExceptionHook.class).to(SharedRefDbExceptionHook.class); } }
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 414e795..9f7124e 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
@@ -17,7 +17,6 @@ import com.google.common.flogger.FluentLogger; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventDispatcher; -import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -42,11 +41,13 @@ * * @param event The event to dispatch */ - public void dispatch(Event event) throws PermissionBackendException { + public void dispatch(Event event) { try { Context.setForwardedEvent(true); log.atFine().log("dispatching event %s", event.getType()); dispatcher.postEvent(event); + } catch (Exception e) { + log.atSevere().withCause(e).log("Unable to re-trigger event"); } finally { Context.unsetForwardedEvent(); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java index 54096ac..73df3eb 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java
@@ -26,7 +26,6 @@ import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Account; import com.google.gerrit.server.events.Event; -import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -108,13 +107,7 @@ } else if (cmd instanceof PostEvent) { Event event = ((PostEvent) cmd).getEvent(); - try { - eventHandler.dispatch(event); - log.atFine().log("Dispatching event %s done", event); - } catch (PermissionBackendException e) { - log.atSevere().withCause(e).log("Dispatching event %s failed", event); - return false; - } + eventHandler.dispatch(event); } else if (cmd instanceof AddToProjectList) { String projectName = ((AddToProjectList) cmd).getProjectName();
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 37f0f20..e2e3302 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
@@ -24,7 +24,6 @@ import com.google.common.net.MediaType; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventGson; -import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -53,10 +52,10 @@ sendError(rsp, SC_UNSUPPORTED_MEDIA_TYPE, "Expecting " + JSON_UTF_8 + " content type"); return; } - forwardedEventHandler.dispatch(getEventFromRequest(req)); + Event event = getEventFromRequest(req); rsp.setStatus(SC_NO_CONTENT); - } catch (IOException | PermissionBackendException e) { - log.atSevere().withCause(e).log("Unable to re-trigger event"); + forwardedEventHandler.dispatch(event); + } catch (IOException e) { sendError(rsp, SC_BAD_REQUEST, e.getMessage()); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java new file mode 100644 index 0000000..4ebf175 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java
@@ -0,0 +1,86 @@ +// Copyright (C) 2024 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 static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_OK; + +import com.google.common.collect.ImmutableList; +import com.google.gerrit.index.query.Predicate; +import com.google.gerrit.index.query.QueryParseException; +import com.google.gerrit.index.query.QueryResult; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeQueryBuilder; +import com.google.gerrit.server.query.change.ChangeQueryProcessor; +import com.google.gson.Gson; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Singleton +public class QueryChangesUpdatedSinceServlet extends AbstractRestApiServlet { + private static final long serialVersionUID = 1L; + + Gson gson = new Gson(); + + private ChangeQueryBuilder changeQueryBuilder; + private final Provider<ChangeQueryProcessor> queryProcessorProvider; + + @Inject + QueryChangesUpdatedSinceServlet( + ChangeQueryBuilder changeQueryBuilder, + Provider<ChangeQueryProcessor> queryProcessorProvider) { + this.changeQueryBuilder = changeQueryBuilder; + this.queryProcessorProvider = queryProcessorProvider; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse rsp) + throws ServletException, IOException { + try { + String age = req.getPathInfo().substring(1); + ChangeQueryProcessor queryProcessor = queryProcessorProvider.get(); + queryProcessor.enforceVisibility(false); + queryProcessor.setNoLimit(true); + // TODO: prevent too large age, because of the noLimit option + Predicate<ChangeData> predicate = Predicate.not(changeQueryBuilder.age(age)); + QueryResult<ChangeData> result = queryProcessor.query(predicate); + ImmutableList<ChangeData> cds = result.entities(); + ArrayList<String> response = new ArrayList<>(cds.size()); + for (ChangeData cd : cds) { + response.add(String.format("%s~%s", cd.project().get(), cd.getId().get())); + } + + String json = gson.toJson(response); + rsp.setStatus(SC_OK); + rsp.setContentType("application/json"); + rsp.setCharacterEncoding("UTF-8"); + PrintWriter out = rsp.getWriter(); + out.print(json); + out.print("\n"); + out.flush(); + } catch (IllegalArgumentException e) { + rsp.setStatus(SC_BAD_REQUEST); + } catch (QueryParseException e) { + throw new ServletException(e); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java index 4d3de37..ffe3afc 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
@@ -27,5 +27,7 @@ serve("/event/*").with(EventRestApiServlet.class); serve("/cache/project_list/*").with(ProjectListApiServlet.class); serve("/cache/*").with(CacheRestApiServlet.class); + + serve("/query/changes.updated.since/*").with(QueryChangesUpdatedSinceServlet.class); } }
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 af730b7..a392935 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
@@ -17,9 +17,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Change; -import com.google.gerrit.entities.HumanComment; import com.google.gerrit.entities.RefNames; -import com.google.gerrit.server.DraftCommentsReader; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; @@ -28,7 +26,6 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import java.io.IOException; -import java.sql.Timestamp; import java.util.Objects; import java.util.Optional; import org.eclipse.jgit.lib.ObjectId; @@ -38,7 +35,6 @@ public class ChangeCheckerImpl implements ChangeChecker { private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final GitRepositoryManager gitRepoMgr; - private final DraftCommentsReader draftCommentsReader; private final OneOffRequestContext oneOffReqCtx; private final String changeId; private final ChangeFinder changeFinder; @@ -52,13 +48,11 @@ @Inject public ChangeCheckerImpl( GitRepositoryManager gitRepoMgr, - DraftCommentsReader draftCommentsReader, ChangeFinder changeFinder, OneOffRequestContext oneOffReqCtx, @Assisted String changeId) { this.changeFinder = changeFinder; this.gitRepoMgr = gitRepoMgr; - this.draftCommentsReader = draftCommentsReader; this.oneOffReqCtx = oneOffReqCtx; this.changeId = changeId; } @@ -173,7 +167,7 @@ } private Optional<Long> computeLastChangeTs() { - return getChangeNotes().map(this::getTsFromChangeAndDraftComments); + return getChangeNotes().map(this::getTsFromChange); } private String getMetaSha(Repository repo) throws IOException { @@ -186,14 +180,8 @@ return ref.getTarget().getObjectId().getName(); } - private long getTsFromChangeAndDraftComments(ChangeNotes notes) { + private long getTsFromChange(ChangeNotes notes) { Change change = notes.getChange(); - Timestamp changeTs = Timestamp.from(change.getLastUpdatedOn()); - for (HumanComment comment : - draftCommentsReader.getDraftsByChangeForAllAuthors(changeNotes.get())) { - Timestamp commentTs = comment.writtenOn; - changeTs = commentTs.after(changeTs) ? commentTs : changeTs; - } - return changeTs.getTime() / 1000; + return change.getLastUpdatedOn().toEpochMilli() / 1000; } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncModule.java new file mode 100644 index 0000000..8d6b282 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncModule.java
@@ -0,0 +1,32 @@ +// Copyright (C) 2024 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.indexsync; + +import com.google.gerrit.extensions.config.FactoryModule; +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.inject.internal.UniqueAnnotations; + +public class IndexSyncModule extends FactoryModule { + @Override + protected void configure() { + // NOTE: indexSync.enabled is handled in the plugins main Module + // When not enabled, then this module is not installed + bind(LifecycleListener.class) + .annotatedWith(UniqueAnnotations.create()) + .to(IndexSyncScheduler.class); + bind(QueryChangesResponseHandler.class); + factory(IndexSyncRunner.Factory.class); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java new file mode 100644 index 0000000..92834bd --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java
@@ -0,0 +1,203 @@ +// Copyright (C) 2024 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.indexsync; + +import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH; + +import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.flogger.FluentLogger; +import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.gerrit.entities.Change; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.index.query.Predicate; +import com.google.gerrit.index.query.QueryParseException; +import com.google.gerrit.index.query.QueryResult; +import com.google.gerrit.server.change.ChangeFinder; +import com.google.gerrit.server.index.IndexExecutor; +import com.google.gerrit.server.index.change.ChangeIndexCollection; +import com.google.gerrit.server.index.change.ChangeIndexer; +import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeQueryBuilder; +import com.google.gerrit.server.query.change.ChangeQueryProcessor; +import com.google.inject.Provider; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; +import dev.failsafe.function.CheckedSupplier; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; + +public class IndexSyncRunner implements CheckedSupplier<Boolean> { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + + interface Factory { + IndexSyncRunner create(String age); + } + + private final Provider<Set<PeerInfo>> peerInfoProvider; + private final CloseableHttpClient httpClient; + private final String pluginRelativePath; + private final QueryChangesResponseHandler queryChangesResponseHandler; + private final ChangeIndexer.Factory changeIndexerFactory; + private final ListeningExecutorService executor; + private final ChangeIndexCollection changeIndexes; + private final ChangeQueryBuilder queryBuilder; + private final Provider<ChangeQueryProcessor> queryProcessorProvider; + private final ChangeFinder changeFinder; + private final String age; + + @AssistedInject + IndexSyncRunner( + Provider<Set<PeerInfo>> peerInfoProvider, + CloseableHttpClient httpClient, + @PluginName String pluginName, + QueryChangesResponseHandler queryChangesResponseHandler, + ChangeIndexer.Factory changeIndexerFactory, + @IndexExecutor(BATCH) ListeningExecutorService executor, + ChangeIndexCollection changeIndexes, + ChangeQueryBuilder queryBuilder, + Provider<ChangeQueryProcessor> queryProcessorProvider, + ChangeFinder changeFinder, + @Assisted String age) { + this.peerInfoProvider = peerInfoProvider; + this.httpClient = httpClient; + this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName); + this.queryChangesResponseHandler = queryChangesResponseHandler; + this.changeIndexerFactory = changeIndexerFactory; + this.executor = executor; + this.changeIndexes = changeIndexes; + this.queryBuilder = queryBuilder; + this.queryProcessorProvider = queryProcessorProvider; + this.changeFinder = changeFinder; + this.age = age; + } + + @Override + public Boolean get() { + log.atFine().log("Starting indexSync"); + Set<PeerInfo> peers = peerInfoProvider.get(); + if (peers.size() == 0) { + return false; + } + + ChangeIndexer indexer = changeIndexerFactory.create(executor, changeIndexes, false); + // NOTE: this loop will stop as soon as the initial sync is performed from one peer + for (PeerInfo peer : peers) { + if (syncFrom(peer, indexer)) { + log.atFine().log("Finished indexSync"); + return true; + } + } + + return false; + } + + private boolean syncFrom(PeerInfo peer, ChangeIndexer indexer) { + log.atFine().log("Syncing index with %s", peer.getDirectUrl()); + String peerUrl = peer.getDirectUrl(); + String uri = + Joiner.on("/").join(peerUrl, pluginRelativePath, "query/changes.updated.since", age); + HttpGet queryRequest = new HttpGet(uri); + List<String> ids; + try { + log.atFine().log("Executing %s", queryRequest); + ids = httpClient.execute(queryRequest, queryChangesResponseHandler); + } catch (IOException e) { + log.atSevere().withCause(e).log("Error while querying changes from %s", uri); + return false; + } + + try { + List<ListenableFuture<Boolean>> indexingTasks = new ArrayList<>(ids.size()); + for (String id : ids) { + indexingTasks.add(indexAsync(id, indexer)); + } + Futures.allAsList(indexingTasks).get(); + } catch (InterruptedException | ExecutionException e) { + log.atSevere().withCause(e).log("Error while reindexing %s", ids); + return false; + } + + syncChangeDeletions(ids, indexer); + + return true; + } + + private ListenableFuture<Boolean> indexAsync(String id, ChangeIndexer indexer) { + List<String> fields = Splitter.on("~").splitToList(id); + if (fields.size() != 2) { + throw new IllegalArgumentException(String.format("Unexpected change ID format %s", id)); + } + Project.NameKey projectName = Project.nameKey(fields.get(0)); + Integer changeNumber = Ints.tryParse(fields.get(1)); + if (changeNumber == null) { + throw new IllegalArgumentException( + String.format("Unexpected change number format %s", fields.get(1))); + } + log.atInfo().log("Scheduling async reindex of: %s", id); + return indexer.asyncReindexIfStale(projectName, Change.id(changeNumber)); + } + + private void syncChangeDeletions(List<String> theirChanges, ChangeIndexer indexer) { + Set<String> ourChanges = queryLocalIndex(); + for (String d : Sets.difference(ourChanges, ImmutableSet.copyOf(theirChanges))) { + deleteIfMissingInNoteDb(d, indexer); + } + } + + private Set<String> queryLocalIndex() { + ChangeQueryProcessor queryProcessor = queryProcessorProvider.get(); + queryProcessor.enforceVisibility(false); + queryProcessor.setNoLimit(true); + Predicate<ChangeData> predicate = Predicate.not(queryBuilder.age(age)); + QueryResult<ChangeData> result; + try { + result = queryProcessor.query(predicate); + } catch (QueryParseException e) { + throw new RuntimeException(e); + } + + ImmutableList<ChangeData> cds = result.entities(); + return cds.stream() + .map(cd -> cd.project().get() + "~" + cd.getId().get()) + .collect(Collectors.toSet()); + } + + private void deleteIfMissingInNoteDb(String id, ChangeIndexer indexer) { + List<ChangeNotes> changeNotes = changeFinder.find(id); + if (changeNotes.isEmpty()) { + List<String> parts = Splitter.on("~").splitToList(id); + Project.NameKey project = Project.nameKey(parts.get(0)); + Change.Id changeId = Change.id(Integer.parseInt(parts.get(1))); + log.atInfo().log("Change %s present in index but not in noteDb. Deleting from index", id); + indexer.deleteAsync(project, changeId); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncScheduler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncScheduler.java new file mode 100644 index 0000000..9d647cf --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncScheduler.java
@@ -0,0 +1,92 @@ +// Copyright (C) 2024 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.indexsync; + +import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.server.git.WorkQueue; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import dev.failsafe.Failsafe; +import dev.failsafe.FailsafeExecutor; +import dev.failsafe.RetryPolicy; +import dev.failsafe.event.ExecutionCompletedEvent; +import java.time.Duration; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Singleton +public class IndexSyncScheduler implements LifecycleListener { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + + private final WorkQueue workQueue; + private final IndexSyncRunner.Factory indexSyncRunnerFactory; + private final Configuration.IndexSync indexSync; + + private ScheduledExecutorService executor; + + @Inject + IndexSyncScheduler( + WorkQueue workQueue, IndexSyncRunner.Factory indexSyncRunnerFactory, Configuration cfg) { + this.workQueue = workQueue; + this.indexSyncRunnerFactory = indexSyncRunnerFactory; + this.indexSync = cfg.indexSync(); + } + + @Override + public void start() { + executor = workQueue.createQueue(4, "IndexSyncRunner"); + + scheduleInitialSync(); + schedulePeriodicSync(); + } + + private void scheduleInitialSync() { + // Initial sync has to be run once but we may need to retry it until the other + // peer becomes reachable + // Therefore, we use failsafe to define and execute retries + RetryPolicy<Boolean> retryPolicy = + RetryPolicy.<Boolean>builder() + .withMaxAttempts(12 * 60) // 5s * 12 * 60 = 1 hour + .withDelay(Duration.ofSeconds(5)) + .onRetriesExceeded(e -> logRetriesExceeded(e)) + .handleResult(false) + .build(); + FailsafeExecutor<Boolean> failsafeExecutor = Failsafe.with(retryPolicy).with(executor); + + IndexSyncRunner sync = indexSyncRunnerFactory.create(indexSync.initialSyncAge()); + failsafeExecutor.getAsync(sync); + } + + private void schedulePeriodicSync() { + // Periodic sync runs at fixed rate and we don't need failsafe for retries + IndexSyncRunner sync = indexSyncRunnerFactory.create(indexSync.syncAge()); + executor.scheduleAtFixedRate( + () -> sync.get(), + indexSync.delay().getSeconds(), + indexSync.period().getSeconds(), + TimeUnit.SECONDS); + } + + private void logRetriesExceeded(ExecutionCompletedEvent<Boolean> e) { + log.atSevere().log("Retries for initial index sync exceeded %s", e); + } + + @Override + public void stop() { + executor.shutdown(); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/QueryChangesResponseHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/QueryChangesResponseHandler.java new file mode 100644 index 0000000..66e957a --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/QueryChangesResponseHandler.java
@@ -0,0 +1,50 @@ +// Copyright (C) 2024 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.indexsync; + +import static javax.servlet.http.HttpServletResponse.SC_OK; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Singleton; +import java.io.IOException; +import java.util.List; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpResponseException; +import org.apache.http.client.ResponseHandler; +import org.apache.http.util.EntityUtils; + +@Singleton +public class QueryChangesResponseHandler implements ResponseHandler<List<String>> { + + private final Gson gson = new Gson(); + + @Override + public List<String> handleResponse(HttpResponse rsp) throws ClientProtocolException, IOException { + StatusLine status = rsp.getStatusLine(); + if (rsp.getStatusLine().getStatusCode() != SC_OK) { + throw new HttpResponseException(status.getStatusCode(), "Query failed"); + } + HttpEntity entity = rsp.getEntity(); + if (entity == null) { + return List.of(); + } + String body = EntityUtils.toString(entity); + return gson.fromJson(body, new TypeToken<List<String>>() {}.getType()); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/PeerInfo.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/PeerInfo.java index 74310ae..111a175 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/PeerInfo.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/PeerInfo.java
@@ -14,6 +14,8 @@ package com.ericsson.gerrit.plugins.highavailability.peers; +import java.util.Objects; + public class PeerInfo { private final String directUrl; @@ -25,4 +27,14 @@ public String getDirectUrl() { return directUrl; } + + @Override + public int hashCode() { + return Objects.hash(directUrl); + } + + @Override + public boolean equals(Object o) { + return (o instanceof PeerInfo) && Objects.equals(directUrl, ((PeerInfo) o).directUrl); + } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java index 776a0a4..63f65d6 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
@@ -14,6 +14,7 @@ package com.ericsson.gerrit.plugins.highavailability.peers.jgroups; +import autovalue.shaded.com.google.common.collect.ImmutableMap; import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo; import com.google.common.annotations.VisibleForTesting; @@ -24,8 +25,11 @@ import com.google.inject.Provider; import com.google.inject.Singleton; import java.net.InetAddress; +import java.util.Iterator; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.jgroups.Address; import org.jgroups.JChannel; import org.jgroups.Message; @@ -39,9 +43,8 @@ * each gerrit server publishes its url to all cluster members (publishes it to all channels). * * <p>This provider maintains a list of all members which joined the jgroups cluster. This may be - * more than two. But will always pick the first node which sent its url as the peer to be returned - * by {@link #get()}. It will continue to return that node until that node leaves the jgroups - * cluster. + * more than two. The set of urls of all peers is returned by {@link #get()}. If a node leaves the + * jgroups cluster it's removed from this set. */ @Singleton public class JGroupsPeerInfoProvider @@ -53,8 +56,7 @@ private final String myUrl; private JChannel channel; - private Optional<PeerInfo> peerInfo = Optional.empty(); - private Address peerAddress; + private Map<Address, PeerInfo> peers = new ConcurrentHashMap<>(); @Inject JGroupsPeerInfoProvider( @@ -70,34 +72,28 @@ @Override public void receive(Message msg) { - synchronized (this) { - if (peerAddress != null) { - return; - } - peerAddress = msg.getSrc(); - String url = (String) msg.getObject(); - peerInfo = Optional.of(new PeerInfo(url)); - log.atInfo().log("receive(): Set new peerInfo: %s", url); + String url = (String) msg.getObject(); + if (url == null) { + return; + } + Address addr = msg.getSrc(); + PeerInfo old = peers.put(addr, new PeerInfo(url)); + if (old == null) { + log.atInfo().log("receive(): Add new peerInfo: %s", url); + } else { + log.atInfo().log("receive(): Update peerInfo: from %s to %s", old.getDirectUrl(), url); } } @Override public void viewAccepted(View view) { log.atInfo().log("viewAccepted(view: %s) called", view); - synchronized (this) { - if (view.getMembers().size() > 2) { - log.atWarning().log( - "%d members joined the jgroups cluster %s (%s). " - + " Only two members are supported. Members: %s", - view.getMembers().size(), - jgroupsConfig.clusterName(), - channel.getName(), - view.getMembers()); - } - if (peerAddress != null && !view.getMembers().contains(peerAddress)) { - log.atInfo().log("viewAccepted(): removed peerInfo"); - peerAddress = null; - peerInfo = Optional.empty(); + Iterator<Map.Entry<Address, PeerInfo>> it = peers.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Address, PeerInfo> e = it.next(); + if (!view.getMembers().contains(e.getKey())) { + log.atInfo().log("viewAccepted(): removed peerInfo %s", e.getValue().getDirectUrl()); + it.remove(); } } if (view.size() > 1) { @@ -144,14 +140,9 @@ this.channel = channel; } - @VisibleForTesting - void setPeerInfo(Optional<PeerInfo> peerInfo) { - this.peerInfo = peerInfo; - } - @Override public Set<PeerInfo> get() { - return peerInfo.isPresent() ? ImmutableSet.of(peerInfo.get()) : ImmutableSet.of(); + return ImmutableSet.copyOf(peers.values()); } @Override @@ -167,17 +158,19 @@ channel.getName(), jgroupsConfig.clusterName()); channel.close(); } - peerInfo = Optional.empty(); - peerAddress = null; + peers.clear(); } @VisibleForTesting - Address getPeerAddress() { - return peerAddress; + Map<Address, PeerInfo> getPeers() { + return ImmutableMap.copyOf(peers); } @VisibleForTesting - void setPeerAddress(Address peerAddress) { - this.peerAddress = peerAddress; + void addPeer(Address address, PeerInfo info) { + if (address == null) { + return; + } + this.peers.put(address, info); } }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index d89f7b3..c7a3e35 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -105,6 +105,39 @@ Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval). When not specified, polling of conditional reindexing is disabled. +**NOTE:** The indexSync feature exposes a REST endpoint that can be used to discover project names. +Admins are advised to restrict access to the REST endpoints exposed by this plugin. + +```indexSync.enabled``` +: When indexSync is enabled, the primary servers will synchronize indexes with the intention to + self-heal any missed reindexing event. + +```indexSync.delay``` +: If enabled, index sync will start running after this initial delay. + Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval). + When not specified, the default is zero: run immediately. + +```indexSync.period``` + Period between two index sync executions. If any execution of this task takes longer than + this period, then subsequent executions may start late, but will not concurrently execute. + Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval). + When not specified, the default is `2 seconds`. + +```indexSync.initialSyncAge``` + This options defines the max age of changes in the other peer for which local index shall + be synchronized on the initial run of the index sync task. The age defined here is usualy + larger than the `syncAge` in order to accommodate max foreseen downtime of a server during + restarts. + The age is express in the format of the `age:` change query parameter. + When not specified, the default is `1hour`. + +```indexSync.syncAge``` + This option defines the max age of changes in the other peer for this local index shall be + synchronized on each run, except for the initial run. + The age is express in the format of the `age:` change query parameter. + When not specified, the default is `5minutes`. + + ```peerInfo.strategy``` : Strategy to find other peers. Supported strategies are `static` or `jgroups`. Defaults to `jgroups`.
diff --git a/src/test/README.md b/src/test/README.md index fd59f64..16e4df9 100644 --- a/src/test/README.md +++ b/src/test/README.md
@@ -1,24 +1,17 @@ # Gerrit high-availability docker setup example -The Docker Compose project in the docker directory contains a simple test +The Docker Compose project in the docker directory contains a simple test environment of two Gerrit masters in HA configuration, with their git repos hosted on NFS filesystem. ## How to build -The project can be built using docker-compose (make sure you set the -`platform` attribute in the docker-compose.yaml file if you're not -in an amd64 arch). +The project can be built using docker compose. To build the Docker VMs: + ```bash - # first, remove the buildx if it exists and its not running - $ docker buildx inspect docker-ha | grep Status - $ docker buildx rm docker-ha - # create the docker-ha buildx node, provide your architecture and start it up - docker buildx create --name docker-ha --platform "linux/amd64" --driver docker-container --use \ - && docker buildx inspect --bootstrap \ - && docker-compose build + docker compose build ``` ### Building the Docker VMs using a non-default user id @@ -28,7 +21,7 @@ Then, run the following: ``` $ export GERRIT_UID=$(id -u) - $ docker-compose build --build-arg GERRIT_UID + $ docker compose build --build-arg GERRIT_UID ``` Above, exporting that UID is optional and will be 1000 by default. @@ -51,10 +44,11 @@ Use the 'up' target to startup the Docker Compose VMs. ``` - $ docker-compose up -d + $ docker compose up -d ``` ## Background on using an NFS server + We are using the `erichough/nfs-server` image mainly because it's easy to use & we had success with it. The work has been inspired by [this blog post](https://nothing2say.co.uk/running-a-linux-based-nfs-server-in-docker-on-windows-b64445d5ada2). @@ -67,11 +61,11 @@ shared over the network. You can change this in the nfs server and gerrit docker files, and in the `exports.txt` file. -The NFS server is using a static IP. The Docker Compose YAML file defines a +The NFS server is using a static IP. The Docker Compose YAML file defines a bridge network with the subnet `192.168.1.0/24` (this is what allows us to give the NFS Server a known, static IP). -The `addr=192.168.1.5` option (in the `nfs-client-volume` volume) is the +The `addr=192.168.1.5` option (in the `nfs-client-volume` volume) is the reason we need a static IP for the server (and hence a configured subnet for the network). Note that using a name (ie. addr=nfs-server) we weren't able to get the DNS resolution to work properly. @@ -86,7 +80,7 @@ into the image sacrificing a bit of flexibility, but we feel this is a small price to pay to have everything automated. -# Gerrit high-availability local setup example +## Gerrit high-availability local setup example 1. Init gerrit instances with high-availability plugin installed: 1. Optionally, set http port of those instance to 8081 and 8082. @@ -137,5 +131,5 @@ target. ``` - $ docker-compose down + $ docker compose down ```
diff --git a/src/test/docker/docker-compose.elasticsearch.yaml b/src/test/docker/docker-compose.elasticsearch.yaml new file mode 100644 index 0000000..31f6269 --- /dev/null +++ b/src/test/docker/docker-compose.elasticsearch.yaml
@@ -0,0 +1,157 @@ +version: '3' + +services: + + nfs-server: + build: nfs +# platform: linux/arm64/v8 # uncomment for Apple Silicon arch + privileged: true + container_name: nfs-server + environment: + NFS_LOG_LEVEL: DEBUG + hostname: nfs-server + healthcheck: + test: ["CMD-SHELL", "sleep 10"] # required, otherwise the gerrit service will fail to start with a "connection refused" error + interval: 1s + timeout: 1m + retries: 10 + ports: + - 2049:2049 + networks: + gerrit-net: + ipv4_address: 192.168.1.5 + volumes: + - nfs-server-volume:/var/gerrit/git + + zookeeper-refdb: + image: zookeeper + ports: + - "2181:2181" + networks: + - gerrit-net + healthcheck: + test: ["CMD-SHELL", "./bin/zkServer.sh", "status"] # required, otherwise the gerrit service will fail to start with a "connection refused" error + interval: 1s + timeout: 1m + retries: 10 + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.9.2 + container_name: elasticsearch + environment: + - cluster.name=elasticsearch-cluster + - node.name=elasticsearch + - cluster.initial_master_nodes=elasticsearch + - bootstrap.memory_lock=true + - xpack.security.enabled=false + - xpack.security.http.ssl.enabled=false + - ELASTIC_PASSWORD=os_Secret1234 + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + ports: + - 9200:9200 + - 9600:9600 + networks: + - gerrit-net + healthcheck: + test: ["CMD-SHELL", "curl -k -u elastic:os_Secret1234 --silent --fail http://localhost:9200/_cluster/health"] + interval: 10s + timeout: 1m + retries: 10 + start_period: 10s + start_interval: 5s + + gerrit-01: + build: gerrit + privileged: true + depends_on: + elasticsearch: + condition: service_healthy + nfs-server: + condition: service_healthy + zookeeper-refdb: + condition: service_healthy + ports: + - "8081:8080" + - "29411:29418" + networks: + - gerrit-net + volumes: + - /dev/urandom:/dev/random + - git-volume:/var/gerrit/git + - shareddir:/var/gerrit/shareddir + - ./etc/gerrit_es.config:/var/gerrit/etc/gerrit.config.orig + - ./etc/high-availability.gerrit-01.config:/var/gerrit/etc/high-availability.config.orig + - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig + environment: + - HOSTNAME=localhost + - INDEX_TYPE=ELASTICSEARCH + + gerrit-02: + build: gerrit + privileged: true + ports: + - "8082:8080" + - "29412:29418" + networks: + - gerrit-net + depends_on: + gerrit-01: + condition: service_started + nfs-server: + condition: service_healthy + volumes: + - /dev/urandom:/dev/random + - git-volume:/var/gerrit/git + - shareddir:/var/gerrit/shareddir + - ./etc/gerrit_es.config:/var/gerrit/etc/gerrit.config.orig + - ./etc/high-availability.gerrit-02.config:/var/gerrit/etc/high-availability.config.orig + - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig + environment: + - HOSTNAME=localhost + - INDEX_TYPE=ELASTICSEARCH + - WAIT_FOR=gerrit-01:8080 + + haproxy: + build: haproxy + ports: + - "80:80" + - "29418:29418" + networks: + - gerrit-net + volumes_from: + - syslog-sidecar + depends_on: + - syslog-sidecar + - gerrit-01 + - gerrit-02 + + syslog-sidecar: + build: docker-syslog-ng-stdout + networks: + - gerrit-net + +networks: + gerrit-net: + ipam: + driver: default + config: + - subnet: 192.168.1.0/24 + +volumes: + shareddir: + nfs-server-volume: + elasticsearch-data: + git-volume: + driver: "local" + driver_opts: + type: nfs + o: "addr=192.168.1.5,rw" + device: ":/var/gerrit/git"
diff --git a/src/test/docker/docker-compose.yaml b/src/test/docker/docker-compose.yaml index 11f4861..d2b971f 100644 --- a/src/test/docker/docker-compose.yaml +++ b/src/test/docker/docker-compose.yaml
@@ -2,7 +2,6 @@ nfs-server: build: nfs -# platform: linux/arm64/v8 # uncomment for Apple Silicon arch privileged: true container_name: nfs-server environment: @@ -18,10 +17,22 @@ ports: - 2049:2049 networks: - nfs-server-bridge: + gerrit-net: ipv4_address: 192.168.1.5 volumes: - - nfs-server-volume:/var/gerrit + - nfs-server-volume:/var/gerrit/git + + zookeeper-refdb: + image: zookeeper + ports: + - "2181:2181" + networks: + - gerrit-net + healthcheck: + test: ["CMD-SHELL", "./bin/zkServer.sh", "status"] # required, otherwise the gerrit service will fail to start with a "connection refused" error + interval: 1s + timeout: 1m + retries: 10 gerrit-01: build: gerrit @@ -29,17 +40,20 @@ depends_on: nfs-server: condition: service_healthy + zookeeper-refdb: + condition: service_healthy ports: - "8081:8080" - "29411:29418" networks: - nfs-server-bridge: null + - gerrit-net volumes: - /dev/urandom:/dev/random - shareddir:/var/gerrit/shareddir - ./etc/jgit.config:/var/gerrit/etc/jgit.config.orig - ./etc/gerrit.config:/var/gerrit/etc/gerrit.config.orig - ./etc/high-availability.gerrit-01.config:/var/gerrit/etc/high-availability.config.orig + - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig environment: - HOSTNAME=localhost @@ -50,7 +64,7 @@ - "8082:8080" - "29412:29418" networks: - nfs-server-bridge: null + - gerrit-net depends_on: gerrit-01: condition: service_started @@ -62,6 +76,7 @@ - ./etc/jgit.config:/var/gerrit/etc/jgit.config.orig - ./etc/gerrit.config:/var/gerrit/etc/gerrit.config.orig - ./etc/high-availability.gerrit-02.config:/var/gerrit/etc/high-availability.config.orig + - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig environment: - HOSTNAME=localhost - WAIT_FOR=gerrit-01:8080 @@ -72,9 +87,9 @@ - "80:80" - "29418:29418" networks: - nfs-server-bridge: null - volumes: - - syslog-sidecar:/syslog-sidecar + - gerrit-net + volumes_from: + - syslog-sidecar depends_on: - syslog-sidecar - gerrit-01 @@ -83,10 +98,10 @@ syslog-sidecar: build: docker-syslog-ng-stdout networks: - nfs-server-bridge: null + - gerrit-net networks: - nfs-server-bridge: + gerrit-net: ipam: driver: default config:
diff --git a/src/test/docker/etc/gerrit.config b/src/test/docker/etc/gerrit.config index 90a4057..408316a 100644 --- a/src/test/docker/etc/gerrit.config +++ b/src/test/docker/etc/gerrit.config
@@ -2,11 +2,14 @@ basePath = git canonicalWebUrl = http://gerrit:8080/ serverId = f7696647-8efd-41b1-bd60-d321bc071ea9 + installDbModule = com.ericsson.gerrit.plugins.highavailability.ValidationModule + installModule = com.gerritforge.gerrit.globalrefdb.validation.LibModule [index] type = LUCENE [auth] type = DEVELOPMENT_BECOME_ANY_ACCOUNT cookiedomain = localhost + cookieHttpOnly = false [sendemail] smtpServer = localhost [sshd]
diff --git a/src/test/docker/etc/gerrit_es.config b/src/test/docker/etc/gerrit_es.config new file mode 100644 index 0000000..669a3af --- /dev/null +++ b/src/test/docker/etc/gerrit_es.config
@@ -0,0 +1,30 @@ +[gerrit] + basePath = git + canonicalWebUrl = http://gerrit:8080/ + serverId = f7696647-8efd-41b1-bd60-d321bc071ea9 + installDbModule = com.ericsson.gerrit.plugins.highavailability.ValidationModule + installModule = com.gerritforge.gerrit.globalrefdb.validation.LibModule + installIndexModule = com.google.gerrit.elasticsearch.PrimaryElasticIndexModule +[elasticsearch] + server = http://elasticsearch:9200 + username = elastic + password = os_Secret1234 +[auth] + type = DEVELOPMENT_BECOME_ANY_ACCOUNT + cookiedomain = localhost + cookieHttpOnly = false +[sendemail] + smtpServer = localhost +[sshd] + listenAddress = *:29418 +[httpd] + listenUrl = proxy-http://*:8080/ + requestLog = true +[cache] + directory = cache +[container] + user = gerrit +[download] + scheme = http + scheme = ssh + scheme = anon_http
diff --git a/src/test/docker/etc/high-availability.gerrit-01.config b/src/test/docker/etc/high-availability.gerrit-01.config index a21f05c..d8619c1 100644 --- a/src/test/docker/etc/high-availability.gerrit-01.config +++ b/src/test/docker/etc/high-availability.gerrit-01.config
@@ -6,3 +6,6 @@ [peerInfo "static"] url = http://gerrit-02:8080 + +[ref-database] + enabled = true
diff --git a/src/test/docker/etc/high-availability.gerrit-02.config b/src/test/docker/etc/high-availability.gerrit-02.config index d05c7ec..54cc1f2 100644 --- a/src/test/docker/etc/high-availability.gerrit-02.config +++ b/src/test/docker/etc/high-availability.gerrit-02.config
@@ -5,4 +5,7 @@ strategy = static [peerInfo "static"] - url = http://gerrit-01:8080 \ No newline at end of file + url = http://gerrit-01:8080 + +[ref-database] + enabled = true
diff --git a/src/test/docker/etc/zookeeper-refdb.config b/src/test/docker/etc/zookeeper-refdb.config new file mode 100644 index 0000000..d3cdb80 --- /dev/null +++ b/src/test/docker/etc/zookeeper-refdb.config
@@ -0,0 +1,4 @@ +[ref-database "zookeeper"] + connectString = "zookeeper-refdb:2181" + rootNode = "gerrit/HA" + transactionLockTimeoutMs = 1000
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile index ea07b5b..6e0d639 100644 --- a/src/test/docker/gerrit/Dockerfile +++ b/src/test/docker/gerrit/Dockerfile
@@ -1,4 +1,4 @@ -FROM almalinux:9.3 +FROM almalinux:9.4 # Install dependencies RUN yum -y install \ @@ -11,7 +11,7 @@ nfs-utils \ && yum -y clean all -ENV GERRIT_VERSION=3.10 +ENV GERRIT_BRANCH stable-3.11 # Add gerrit user RUN adduser -p -m --uid 1000 gerrit --home-dir /home/gerrit @@ -23,19 +23,32 @@ RUN mkdir -p /var/gerrit && chown -R gerrit /var/gerrit ADD --chown=gerrit \ - "https://gerrit-ci.gerritforge.com/job/Gerrit-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/gerrit/bazel-bin/release.war" \ + "https://gerrit-ci.gerritforge.com/job/Gerrit-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/gerrit/bazel-bin/release.war" \ /tmp/gerrit.war ADD --chown=gerrit \ -"https://gerrit-ci.gerritforge.com/job/plugin-javamelody-bazel-master-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody.jar" \ + "https://gerrit-ci.gerritforge.com/view/Plugins-master/job/plugin-javamelody-bazel-master-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody.jar" \ /var/gerrit/plugins/javamelody.jar + ADD --chown=gerrit \ - "https://gerrit-ci.gerritforge.com/job/plugin-high-availability-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/high-availability/high-availability.jar" \ + "https://gerrit-ci.gerritforge.com/job/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 \ - "https://gerrit-ci.gerritforge.com/job/module-global-refdb-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/global-refdb/global-refdb.jar" \ + +RUN mkdir -p /var/gerrit/lib && \ + ln -sf /var/gerrit/plugins/high-availability.jar /var/gerrit/lib/high-availability.jar + +ADD --chown=gerrit:gerrit \ + "https://gerrit-ci.gerritforge.com/job/module-global-refdb-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/global-refdb/global-refdb.jar" \ /var/gerrit/lib/global-refdb.jar +ADD --chown=gerrit:gerrit \ + "https://gerrit-ci.gerritforge.com/job/plugin-zookeeper-refdb-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/zookeeper-refdb/zookeeper-refdb.jar" \ + /var/gerrit/plugins/zookeeper-refdb.jar + +ADD --chown=gerrit:gerrit \ + "https://gerrit-ci.gerritforge.com/view/Plugins-master/job/module-index-elasticsearch-bazel-master/lastSuccessfulBuild/artifact/bazel-bin/plugins/index-elasticsearch/index-elasticsearch.jar" \ + /var/gerrit_plugins/index-elasticsearch.jar + ADD --chown=gerrit:gerrit ./wait-for-it.sh /bin # Change user
diff --git a/src/test/docker/gerrit/entrypoint.sh b/src/test/docker/gerrit/entrypoint.sh index 3a98d92..aaed80c 100755 --- a/src/test/docker/gerrit/entrypoint.sh +++ b/src/test/docker/gerrit/entrypoint.sh
@@ -9,7 +9,15 @@ sudo -u gerrit cp /var/gerrit/etc/jgit.config.orig /var/gerrit/etc/jgit.config 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 +sudo -u gerrit cp /var/gerrit/etc/zookeeper-refdb.config.orig /var/gerrit/etc/zookeeper-refdb.config +sudo -u gerrit git config -f /var/gerrit/etc/healthcheck.config healthcheck.auth.enabled false +sudo -u gerrit git config -f /var/gerrit/etc/healthcheck.config healthcheck.querychanges.enabled false +sudo -u gerrit git config -f /var/gerrit/etc/healthcheck.config healthcheck.changesindex.enabled false +if [[ "$INDEX_TYPE" == "ELASTICSEARCH" ]]; then + ln -sf /var/gerrit_plugins/index-elasticsearch.jar /var/gerrit/lib/index-elasticsearch.jar + ln -sf /var/gerrit_plugins/index-elasticsearch.jar /var/gerrit/plugins/index-elasticsearch.jar +fi echo "Mount NFS ..." mkdir /var/gerrit/git && chown gerrit:gerrit /var/gerrit/git
diff --git a/src/test/docker/haproxy/haproxy.cfg b/src/test/docker/haproxy/haproxy.cfg index e86cdce..07a6746 100644 --- a/src/test/docker/haproxy/haproxy.cfg +++ b/src/test/docker/haproxy/haproxy.cfg
@@ -45,7 +45,7 @@ timeout connect 10s timeout server 5m server gerrit_ssh_01 gerrit-01:29418 check port 8080 inter 10s fall 3 rise 2 - server gerrit-ssh_02 gerrit-02:29418 check port 8080 inter 10s fall 3 rise 2 backup + server gerrit-ssh_02 gerrit-02:29418 check port 8080 inter 10s fall 3 rise 2 backend gerrit_http_nodes mode http @@ -55,7 +55,7 @@ option httpchk GET /config/server/version HTTP/1.0 http-check expect status 200 server gerrit_01 gerrit-01:8080 check - server gerrit_02 gerrit-02:8080 check backup + server gerrit_02 gerrit-02:8080 check backend gerrit_http_nodes_balanced mode http
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 3e1f8fd..73e3401 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,7 +15,6 @@ 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.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -82,9 +81,7 @@ .postEvent(event); assertThat(Context.isForwardedEvent()).isFalse(); - PermissionBackendException thrown = - assertThrows(PermissionBackendException.class, () -> handler.dispatch(event)); - assertThat(thrown).hasMessageThat().isEqualTo("someMessage"); + handler.dispatch(event); assertThat(Context.isForwardedEvent()).isFalse(); verify(dispatcherMock).postEvent(event);
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 3c6b931..7c23e29 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
@@ -26,6 +26,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedEventHandler; import com.google.common.net.MediaType; import com.google.gerrit.entities.Project; +import com.google.gerrit.server.events.EventDispatcher; import com.google.gerrit.server.events.EventGsonProvider; import com.google.gerrit.server.events.EventTypes; import com.google.gerrit.server.events.RefEvent; @@ -41,6 +42,7 @@ 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) @@ -85,11 +87,15 @@ + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":" + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}"; when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event))); + + EventDispatcher dispatcher = Mockito.mock(EventDispatcher.class); doThrow(new PermissionBackendException(ERR_MSG)) - .when(forwardedEventHandlerMock) - .dispatch(any(RefReplicationDoneEvent.class)); + .when(dispatcher) + .postEvent(any(RefReplicationDoneEvent.class)); + ForwardedEventHandler forwardedEventHandler = new ForwardedEventHandler(dispatcher); + eventRestApiServlet = new EventRestApiServlet(forwardedEventHandler, gson); eventRestApiServlet.doPost(requestMock, responseMock); - verify(responseMock).sendError(SC_BAD_REQUEST, ERR_MSG); + verify(responseMock).setStatus(SC_NO_CONTENT); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java index 2cf3fad..dfea1df 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java
@@ -19,7 +19,6 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.gerrit.entities.Change; -import com.google.gerrit.server.DraftCommentsReader; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; @@ -38,7 +37,6 @@ public class ChangeCheckerImplTest { @Mock private GitRepositoryManager gitRepoMgr; - @Mock private DraftCommentsReader draftCommentsReader; @Mock private ChangeFinder changeFinder; @Mock private OneOffRequestContext oneOffReqCtx; @Mock private ChangeNotes testChangeNotes; @@ -52,9 +50,7 @@ @Before public void setUp() { - changeChecker = - new ChangeCheckerImpl( - gitRepoMgr, draftCommentsReader, changeFinder, oneOffReqCtx, changeId); + changeChecker = new ChangeCheckerImpl(gitRepoMgr, changeFinder, oneOffReqCtx, changeId); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java index fc8c29a..0c53959 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
@@ -87,6 +87,8 @@ when(pluginConfigurationMock.jgroupsKubernetes().namespace()).thenReturn(namespace); when(pluginConfigurationMock.jgroupsKubernetes().labels()).thenReturn(labels); + when(myUrlProvider.get()).thenReturn("http://127.0.0.1:7800"); + NetworkInterface eth0 = NetworkInterface.getByName("eth0"); if (eth0 != null) { when(finder.findAddress()).thenReturn(eth0.inetAddresses().findFirst());
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java index fd203e0..a098be6 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java
@@ -16,12 +16,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo; import java.util.List; -import java.util.Optional; import java.util.Set; import org.jgroups.Address; import org.jgroups.JChannel; @@ -43,7 +43,7 @@ private InetAddressFinder finder; private JGroupsPeerInfoProvider jGroupsPeerInfoProvider; - private Optional<PeerInfo> peerInfo; + private PeerInfo peerInfo; @Mock private JChannel channel; @Mock private MyUrlProvider myUrlProviderTest; @Mock private Message message; @@ -57,7 +57,7 @@ JChannel channel = new JChannelProvider(pluginConfigurationMock).get(); jGroupsPeerInfoProvider = new JGroupsPeerInfoProvider(pluginConfigurationMock, finder, myUrlProviderTest, channel); - peerInfo = Optional.of(new PeerInfo("test message")); + peerInfo = new PeerInfo("test message"); channel.setName("testChannel"); } @@ -68,7 +68,7 @@ jGroupsPeerInfoProvider.receive(message); - assertThat(jGroupsPeerInfoProvider.getPeerAddress()).isEqualTo(peerAddress); + assertThat(jGroupsPeerInfoProvider.getPeers()).containsKey(peerAddress); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); for (PeerInfo testPeerInfo : testPeerInfoSet) { assertThat(testPeerInfo.getDirectUrl()).contains("test message"); @@ -78,20 +78,52 @@ @Test public void testReceiveWhenPeerAddressIsNotNull() throws Exception { - jGroupsPeerInfoProvider.setPeerAddress(new IpAddress("checkAddress.com")); + lenient().when(message.getSrc()).thenReturn(peerAddress); + when(message.getObject()).thenReturn(null); jGroupsPeerInfoProvider.receive(message); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); - assertThat(testPeerInfoSet.isEmpty()).isTrue(); - assertThat(testPeerInfoSet.size()).isEqualTo(0); + assertThat(testPeerInfoSet).isEmpty(); + } + + @Test + public void testReceiveMultiplePeers() throws Exception { + IpAddress addr1 = new IpAddress("192.168.1.5:7800"); + IpAddress addr2 = new IpAddress("192.168.1.6:7800"); + IpAddress addr3 = new IpAddress("192.168.1.7:7800"); + PeerInfo peer1 = new PeerInfo("URL1"); + PeerInfo peer2 = new PeerInfo("URL2"); + PeerInfo peer3 = new PeerInfo("URL3"); + + receive(addr1, peer1); + receive(addr2, peer2); + receive(addr3, peer3); + + Set<PeerInfo> peers = jGroupsPeerInfoProvider.get(); + assertThat(peers.size()).isEqualTo(3); + assertThat(peers).containsExactly(peer1, peer2, peer3); + + // remove one peer with address ADDR1 from the view + List<Address> reducedView = List.of(addr2, addr3); + when(view.getMembers()).thenReturn(reducedView); + when(view.size()).thenReturn(2); + jGroupsPeerInfoProvider.setChannel(channel); + jGroupsPeerInfoProvider.viewAccepted(view); + peers = jGroupsPeerInfoProvider.get(); + assertThat(peers.size()).isEqualTo(2); + assertThat(peers).containsExactly(peer2, peer3); + } + + public void receive(final IpAddress addr, final PeerInfo peer) { + when(message.getSrc()).thenReturn(addr); + when(message.getObject()).thenReturn(peer.getDirectUrl()); + jGroupsPeerInfoProvider.receive(message); } @Test(expected = None.class) public void testViewAcceptedWithNoExceptionThrown() throws Exception { - when(view.getMembers()).thenReturn(members); when(view.size()).thenReturn(3); - when(members.size()).thenReturn(3); jGroupsPeerInfoProvider.setChannel(channel); jGroupsPeerInfoProvider.viewAccepted(view); } @@ -100,37 +132,32 @@ public void testViewAcceptedWhenPeerAddressIsNotNullAndIsNotMemberOfView() { when(view.getMembers()).thenReturn(members); when(view.size()).thenReturn(2); - when(members.size()).thenReturn(2); when(members.contains(peerAddress)).thenReturn(false); - jGroupsPeerInfoProvider.setPeerAddress(peerAddress); - jGroupsPeerInfoProvider.setPeerInfo(peerInfo); + jGroupsPeerInfoProvider.addPeer(peerAddress, peerInfo); jGroupsPeerInfoProvider.setChannel(channel); jGroupsPeerInfoProvider.viewAccepted(view); - assertThat(jGroupsPeerInfoProvider.getPeerAddress()).isEqualTo(null); + assertThat(jGroupsPeerInfoProvider.getPeers()).isEmpty(); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); - assertThat(testPeerInfoSet.isEmpty()).isTrue(); - assertThat(testPeerInfoSet.size()).isEqualTo(0); + assertThat(testPeerInfoSet).isEmpty(); } @Test - public void testConnect() throws NoSuchFieldException, IllegalAccessException { + public void testConnect() { jGroupsPeerInfoProvider.connect(); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); - assertThat(testPeerInfoSet.isEmpty()).isTrue(); - assertThat(testPeerInfoSet.size()).isEqualTo(0); + assertThat(testPeerInfoSet).isEmpty(); } @Test public void testGetWhenPeerInfoIsOptionalEmpty() { Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); - assertThat(testPeerInfoSet.isEmpty()).isTrue(); - assertThat(testPeerInfoSet.size()).isEqualTo(0); + assertThat(testPeerInfoSet).isEmpty(); } @Test public void testGetWhenPeerInfoIsPresent() { - jGroupsPeerInfoProvider.setPeerInfo(peerInfo); + jGroupsPeerInfoProvider.addPeer(peerAddress, peerInfo); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); for (PeerInfo testPeerInfo : testPeerInfoSet) { assertThat(testPeerInfo.getDirectUrl()).contains("test message"); @@ -140,12 +167,10 @@ @Test public void testStop() throws Exception { - jGroupsPeerInfoProvider.setPeerAddress(peerAddress); - jGroupsPeerInfoProvider.setPeerInfo(peerInfo); + jGroupsPeerInfoProvider.addPeer(peerAddress, peerInfo); jGroupsPeerInfoProvider.stop(); - assertThat(jGroupsPeerInfoProvider.getPeerAddress()).isEqualTo(null); + assertThat(jGroupsPeerInfoProvider.getPeers().isEmpty()); Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get(); - assertThat(testPeerInfoSet.isEmpty()).isTrue(); - assertThat(testPeerInfoSet.size()).isEqualTo(0); + assertThat(testPeerInfoSet).isEmpty(); } }