Merge branch 'stable-3.1' into stable-3.2 * stable-3.1: Fix merge issue for ForwardedIndexGroupHandlerTest class Add 'Forwarded-BatchIndex-Event' to events skipped from high-availability Do not forward events from high-availability Honour index retries when indexing groups Change-Id: I281c704d12935d6e1c620bc20f49c2f5ce0331ea
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl index 9cbb332..fee8aca 100644 --- a/external_plugin_deps.bzl +++ b/external_plugin_deps.bzl
@@ -9,6 +9,6 @@ maven_jar( name = "events-broker", - artifact = "com.gerritforge:events-broker:3.1.4", - sha1 = "5672908dde0bd02cabc95efe34a8d8507d44b6ac", + artifact = "com.gerritforge:events-broker:3.2.0-rc4", + sha1 = "53e3f862ac2c2196dba716756ac9586f4b63af47", )
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh index 87af305..f3f3ae6 100755 --- a/setup_local_env/setup.sh +++ b/setup_local_env/setup.sh
@@ -16,7 +16,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -GERRIT_BRANCH=stable-3.1 +GERRIT_BRANCH=stable-3.2 GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins EVENTS_BROKER_VER=`grep 'com.gerritforge:events-broker' $(dirname $0)/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java index ebe28d4..f509cd4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -61,6 +61,7 @@ private final Supplier<Cache> cache; private final Supplier<Event> event; private final Supplier<Index> index; + private final Supplier<Projects> projects; private final Supplier<SharedRefDatabase> sharedRefDb; private final Supplier<Collection<Message>> replicationConfigValidation; private final Supplier<Broker> broker; @@ -79,6 +80,7 @@ cache = memoize(() -> new Cache(lazyMultiSiteCfg)); event = memoize(() -> new Event(lazyMultiSiteCfg)); index = memoize(() -> new Index(lazyMultiSiteCfg)); + projects = memoize(() -> new Projects(lazyMultiSiteCfg)); sharedRefDb = memoize(() -> new SharedRefDatabase(lazyMultiSiteCfg)); broker = memoize(() -> new Broker(lazyMultiSiteCfg)); } @@ -107,6 +109,10 @@ return broker.get(); } + public Projects projects() { + return projects.get(); + } + public Collection<Message> validate() { return replicationConfigValidation.get(); } @@ -198,6 +204,20 @@ } } + public static class Projects { + public static final String SECTION = "projects"; + public static final String PATTERN_KEY = "pattern"; + public List<String> patterns; + + public Projects(Supplier<Config> cfg) { + patterns = ImmutableList.copyOf(cfg.get().getStringList("projects", null, PATTERN_KEY)); + } + + public List<String> getPatterns() { + return patterns; + } + } + /** Common parameters to cache, event, index */ public abstract static class Forwarding { static final boolean DEFAULT_SYNCHRONIZE = true;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java new file mode 100644 index 0000000..d6fac47 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java
@@ -0,0 +1,109 @@ +// 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.googlesource.gerrit.plugins.multisite; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.ProjectEvent; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.List; +import java.util.Set; + +@Singleton +public class ProjectsFilter { + public enum PatternType { + REGEX, + WILDCARD, + EXACT_MATCH; + + public static PatternType getPatternType(String pattern) { + if (pattern.startsWith(AccessSection.REGEX_PREFIX)) { + return REGEX; + } else if (pattern.endsWith("*")) { + return WILDCARD; + } else { + return EXACT_MATCH; + } + } + } + + private Set<NameKey> globalProjects = Sets.newConcurrentHashSet(); + private Set<NameKey> localProjects = Sets.newConcurrentHashSet(); + private final List<String> projectPatterns; + + @Inject + public ProjectsFilter(Configuration cfg) { + projectPatterns = cfg.projects().getPatterns(); + } + + public boolean matches(Event event) { + if (event == null) { + throw new IllegalArgumentException("Event object cannot be null"); + } + if (event instanceof ProjectEvent) { + return matches(((ProjectEvent) event).getProjectNameKey()); + } + return false; + } + + public boolean matches(String projectName) { + return matches(NameKey.parse(projectName)); + } + + public boolean matches(Project.NameKey name) { + if (name == null || Strings.isNullOrEmpty(name.get())) { + throw new IllegalArgumentException( + String.format("Project name cannot be null or empty, but was %s", name)); + } + if (projectPatterns.isEmpty() || globalProjects.contains(name)) { + return true; + } + + if (localProjects.contains(name)) { + return false; + } + + String projectName = name.get(); + + for (String pattern : projectPatterns) { + if (matchesPattern(projectName, pattern)) { + globalProjects.add(name); + return true; + } + } + localProjects.add(name); + return false; + } + + private boolean matchesPattern(String projectName, String pattern) { + boolean match = false; + switch (PatternType.getPatternType(pattern)) { + case REGEX: + match = projectName.matches(pattern); + break; + case WILDCARD: + match = projectName.startsWith(pattern.substring(0, pattern.length() - 1)); + break; + case EXACT_MATCH: + match = projectName.equals(pattern); + } + return match; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java index b56f2ea..ddf9d84 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
@@ -21,11 +21,13 @@ import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase; import java.util.Optional; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; public class SharedRefDatabaseWrapper implements GlobalRefDatabase { + private static final GlobalRefDatabase NOOP_REFDB = new NoopSharedRefDatabase(); @Inject(optional = true) private DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem; @@ -95,6 +97,6 @@ } private GlobalRefDatabase sharedRefDb() { - return sharedRefDbDynamicItem.get(); + return Optional.ofNullable(sharedRefDbDynamicItem).map(di -> di.get()).orElse(NOOP_REFDB); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java index acf8df0..b8521a3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java
@@ -26,8 +26,7 @@ @Singleton class CachePatternMatcher { private static final List<String> DEFAULT_PATTERNS = - ImmutableList.of( - "accounts", "^groups.*", "ldap_groups", "ldap_usernames", "projects", "sshkeys"); + ImmutableList.of("^groups.*", "ldap_groups", "ldap_usernames", "projects", "sshkeys"); private final Pattern pattern;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java index 1060977..2e2f9de 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask; import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder; @@ -31,12 +32,16 @@ private final DynamicSet<ProjectListUpdateForwarder> forwarders; private final Executor executor; + private final ProjectsFilter projectsFilter; @Inject public ProjectListUpdateHandler( - DynamicSet<ProjectListUpdateForwarder> forwarders, @CacheExecutor Executor executor) { + DynamicSet<ProjectListUpdateForwarder> forwarders, + @CacheExecutor Executor executor, + ProjectsFilter filter) { this.forwarders = forwarders; this.executor = executor; + this.projectsFilter = filter; } @Override @@ -52,7 +57,7 @@ } private void process(ProjectEvent event, boolean delete) { - if (!Context.isForwardedEvent()) { + if (!Context.isForwardedEvent() && projectsFilter.matches(event.getProjectName())) { executor.execute( new ProjectListUpdateTask(new ProjectListUpdateEvent(event.getProjectName(), delete))); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java index aafd59f..a1b7a53 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
@@ -19,6 +19,7 @@ import com.google.gerrit.server.events.EventListener; import com.google.gerrit.server.events.ProjectEvent; import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder; import java.util.concurrent.Executor; @@ -26,17 +27,24 @@ class EventHandler implements EventListener { private final Executor executor; private final DynamicSet<StreamEventForwarder> forwarders; + private final ProjectsFilter projectsFilter; @Inject - EventHandler(DynamicSet<StreamEventForwarder> forwarders, @EventExecutor Executor executor) { + EventHandler( + DynamicSet<StreamEventForwarder> forwarders, + @EventExecutor Executor executor, + ProjectsFilter projectsFilter) { this.forwarders = forwarders; this.executor = executor; + this.projectsFilter = projectsFilter; } @Override public void onEvent(Event event) { if (!Context.isForwardedEvent() && event instanceof ProjectEvent) { - executor.execute(new EventTask(event)); + if (projectsFilter.matches(event)) { + executor.execute(new EventTask(event)); + } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java index 6ba18ed..02972b0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
@@ -79,7 +79,7 @@ @Override public Optional<ChangeNotes> getChangeNotes() { try (ManualRequestContext ctx = oneOffReqCtx.open()) { - this.changeNotes = Optional.ofNullable(changeFinder.findOne(changeId)); + this.changeNotes = changeFinder.findOne(changeId); return changeNotes; } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java index fdc3b28..5effd0e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -21,6 +21,7 @@ import com.google.gerrit.extensions.events.ProjectIndexedListener; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask; import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder; @@ -45,6 +46,7 @@ private final DynamicSet<IndexEventForwarder> forwarders; private final Set<IndexTask> queuedTasks = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final ChangeCheckerImpl.Factory changeChecker; + private final ProjectsFilter projectsFilter; private final GroupChecker groupChecker; @Inject @@ -52,10 +54,12 @@ @IndexExecutor Executor executor, DynamicSet<IndexEventForwarder> forwarders, ChangeCheckerImpl.Factory changeChecker, + ProjectsFilter projectsFilter, GroupChecker groupChecker) { this.forwarders = forwarders; this.executor = executor; this.changeChecker = changeChecker; + this.projectsFilter = projectsFilter; this.groupChecker = groupChecker; } @@ -92,7 +96,7 @@ @Override public void onProjectIndexed(String projectName) { - if (!Context.isForwardedEvent()) { + if (!Context.isForwardedEvent() && projectsFilter.matches(projectName)) { IndexProjectTask task = new IndexProjectTask(new ProjectIndexEvent(projectName)); if (queuedTasks.add(task)) { executor.execute(task); @@ -101,7 +105,7 @@ } private void executeIndexChangeTask(String projectName, int id) { - if (!Context.isForwardedEvent()) { + if (!Context.isForwardedEvent() && projectsFilter.matches(projectName)) { ChangeChecker checker = changeChecker.create(projectName + "~" + id); try {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java index ba2a7d6..8655824 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java
@@ -30,6 +30,8 @@ @Override public boolean isUpToDate(Optional<ProjectIndexEvent> indexEvent) { - return indexEvent.map(e -> Project.nameKey(e.projectName)).map(projectCache::get).isPresent(); + return indexEvent + .flatMap(event -> projectCache.get(Project.nameKey(event.projectName))) + .isPresent(); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java index 7aa3328..0afe308 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
@@ -18,6 +18,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.googlesource.gerrit.plugins.multisite.LockWrapper; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement; @@ -51,9 +52,17 @@ ValidationMetrics validationMetrics, SharedRefEnforcement refEnforcement, LockWrapper.Factory lockWrapperFactory, + ProjectsFilter projectsFilter, @Assisted String projectName, @Assisted RefDatabase refDb) { - super(sharedRefDb, validationMetrics, refEnforcement, lockWrapperFactory, projectName, refDb); + super( + sharedRefDb, + validationMetrics, + refEnforcement, + lockWrapperFactory, + projectsFilter, + projectName, + refDb); } public void executeBatchUpdateWithValidation( @@ -61,7 +70,8 @@ NoParameterVoidFunction batchRefUpdateFunction, OneParameterVoidFunction<List<ReceiveCommand>> batchRefUpdateRollbackFunction) throws IOException { - if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) { + if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED + || !isGlobalProject(projectName)) { batchRefUpdateFunction.invoke(); return; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java index 73bab49..c8ddb43 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -31,6 +31,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import java.io.IOException; @@ -58,6 +59,7 @@ private final GitRepositoryManager gitRepositoryManager; private final GitReferenceUpdated gitReferenceUpdated; private final ProjectVersionLogger verLogger; + private final ProjectsFilter projectsFilter; protected final SharedRefDatabaseWrapper sharedRefDb; @@ -66,11 +68,13 @@ GitRepositoryManager gitRepositoryManager, SharedRefDatabaseWrapper sharedRefDb, GitReferenceUpdated gitReferenceUpdated, - ProjectVersionLogger verLogger) { + ProjectVersionLogger verLogger, + ProjectsFilter projectsFilter) { this.gitRepositoryManager = gitRepositoryManager; this.sharedRefDb = sharedRefDb; this.gitReferenceUpdated = gitReferenceUpdated; this.verLogger = verLogger; + this.projectsFilter = projectsFilter; } @Override @@ -78,7 +82,9 @@ logger.atFine().log("Processing event type: " + event.type); // Producer of the Event use RefUpdatedEvent to trigger the version update if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) { - updateProducerProjectVersionUpdate((RefUpdatedEvent) event); + if (projectsFilter.matches(event)) { + updateProducerProjectVersionUpdate((RefUpdatedEvent) event); + } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java index d7fafe0..c110f0f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
@@ -21,6 +21,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.googlesource.gerrit.plugins.multisite.LockWrapper; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException; @@ -46,6 +47,7 @@ private final LockWrapper.Factory lockWrapperFactory; protected final RefDatabase refDb; protected final SharedRefEnforcement refEnforcement; + protected final ProjectsFilter projectsFilter; public static interface Factory { RefUpdateValidator create(String projectName, RefDatabase refDb); @@ -82,6 +84,7 @@ ValidationMetrics validationMetrics, SharedRefEnforcement refEnforcement, LockWrapper.Factory lockWrapperFactory, + ProjectsFilter projectsFilter, @Assisted String projectName, @Assisted RefDatabase refDb) { this.sharedRefDb = sharedRefDb; @@ -90,6 +93,7 @@ this.refDb = refDb; this.projectName = projectName; this.refEnforcement = refEnforcement; + this.projectsFilter = projectsFilter; } public RefUpdate.Result executeRefUpdate( @@ -98,6 +102,7 @@ OneParameterFunction<ObjectId, Result> rollbackFunction) throws IOException { if (isProjectVersionUpdate(refUpdate.getName()) + || !isGlobalProject(projectName) || refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) { return refUpdateFunction.invoke(); } @@ -123,6 +128,12 @@ } } + protected Boolean isGlobalProject(String projectName) { + Boolean isGlobalProject = projectsFilter.matches(projectName); + logger.atFine().log("Is global project? " + isGlobalProject); + return isGlobalProject; + } + protected RefUpdate.Result doExecuteRefUpdate( RefUpdate refUpdate, NoParameterFunction<Result> refUpdateFunction, @@ -256,7 +267,7 @@ } protected Ref getCurrentRef(String refName) throws IOException { - return MoreObjects.firstNonNull(refDb.getRef(refName), nullRef(refName)); + return MoreObjects.firstNonNull(refDb.findRef(refName), nullRef(refName)); } public static class CloseableSet<T extends AutoCloseable> implements AutoCloseable {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java index 481d288..4a34b7a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -14,7 +14,6 @@ package com.googlesource.gerrit.plugins.multisite.validation; -import com.google.common.flogger.FluentLogger; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.server.git.GitRepositoryManager; @@ -33,8 +32,6 @@ import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter; public class ValidationModule extends FactoryModule { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private final Configuration cfg; public ValidationModule(Configuration cfg) {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 67e942f..a76545f 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -96,4 +96,28 @@ Ignore the alignment with the shared ref-database for AProject on refs/heads/feature. - Defaults: No rules = All projects are REQUIRED to be consistent on all refs. \ No newline at end of file + Defaults: No rules = All projects are REQUIRED to be consistent on all refs. + +```projects.pattern``` +: Specifies which projects events should be send via broker. It can be provided more + than once, and supports three formats: regular expressions, wildcard matching, and single + project matching. All three formats match case-sensitive. + + Values starting with a caret `^` are treated as regular + expressions. For the regular expressions details please follow + official [java documentation](https://docs.oracle.com/javase/tutorial/essential/regex/). + + Please note that regular expressions could also be used + with inverse match. + + Values that are not regular expressions and end in `*` are + treated as wildcard matches. Wildcards match projects whose + name agrees from the beginning until the trailing `*`. So + `foo/b*` would match the projects `foo/b`, `foo/bar`, and + `foo/baz`, but neither `foobar`, nor `bar/foo/baz`. + + Values that are neither regular expressions nor wildcards are + treated as single project matches. So `foo/bar` matches only + the project `foo/bar`, but no other project. + + By default, all projects are matched.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java new file mode 100644 index 0000000..e81c906 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java
@@ -0,0 +1,180 @@ +// 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.googlesource.gerrit.plugins.multisite; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Lists; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.ProjectEvent; +import com.google.gerrit.server.events.RefUpdatedEvent; +import com.google.gerrit.testing.GerritJUnit; +import com.googlesource.gerrit.plugins.multisite.Configuration.Projects; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ProjectsFilterTest { + + @Mock private Configuration configuration; + @Mock private Projects projects; + + private ProjectsFilter objectUnderTest; + + @Test + public void shouldMatchByExactProjectName() { + when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isFalse(); + } + + @Test + public void shouldMatchByWildcard() { + when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project*")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("2test_project"))).isFalse(); + } + + @Test + public void shouldMatchByRegex() { + when(projects.getPatterns()).thenReturn(Lists.newArrayList("^test_(project|project2)")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse(); + } + + @Test + public void shouldExcludeByRegex() { + when(projects.getPatterns()).thenReturn(Lists.newArrayList("^(?:(?!test_project3).)*$")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse(); + } + + @Test + public void shouldExcludeByMultipleProjectsRegexPattern() { + when(projects.getPatterns()) + .thenReturn(Lists.newArrayList("^(?:(?!(test_project3|test_project4)).)*$")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse(); + assertThat(objectUnderTest.matches(NameKey.parse("test_project4"))).isFalse(); + } + + @Test + public void shouldMatchWhenNoPatternProvided() { + when(projects.getPatterns()).thenReturn(Collections.emptyList()); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue(); + } + + @Test + public void shouldMatchProjectEvent() { + ProjectEvent event = mock(ProjectEvent.class); + when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project")); + when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(event)).isTrue(); + } + + @Test + public void shouldMatchRefUpdatedEvent() { + RefUpdatedEvent event = mock(RefUpdatedEvent.class); + when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project")); + when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(event)).isTrue(); + } + + @Test + public void shouldExcludedNonProjectEvents() { + Event event = mock(Event.class); + when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project)")); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + assertThat(objectUnderTest.matches(event)).isFalse(); + } + + @Test + public void shouldThrowExceptionWhenProjecNameIsNull() { + when(projects.getPatterns()).thenReturn(Collections.emptyList()); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + GerritJUnit.assertThrows( + IllegalArgumentException.class, () -> objectUnderTest.matches((NameKey) null)); + } + + @Test + public void shouldThrowExceptionWhenProjecNameIsEmpty() { + when(projects.getPatterns()).thenReturn(Collections.emptyList()); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + GerritJUnit.assertThrows( + IllegalArgumentException.class, () -> objectUnderTest.matches(NameKey.parse(""))); + } + + @Test + public void shouldThrowExceptionWhenEventIsNull() { + when(projects.getPatterns()).thenReturn(Collections.emptyList()); + when(configuration.projects()).thenReturn(projects); + + objectUnderTest = new ProjectsFilter(configuration); + + GerritJUnit.assertThrows( + IllegalArgumentException.class, () -> objectUnderTest.matches((Event) null)); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java new file mode 100644 index 0000000..67be583 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java
@@ -0,0 +1,48 @@ +// 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.googlesource.gerrit.plugins.multisite.cache; + +import static org.mockito.Mockito.verifyZeroInteractions; + +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.googlesource.gerrit.plugins.multisite.Configuration; +import java.util.concurrent.Executor; +import org.eclipse.jgit.lib.Config; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CacheEvictionHandlerTest { + + @Mock private Executor executorMock; + private CachePatternMatcher defaultCacheMatcher = + new CachePatternMatcher(new Configuration(new Config(), new Config())); + + @Test + public void shouldNotPublishAccountsCacheEvictions() { + + final CacheEvictionHandler<String, String> handler = + new CacheEvictionHandler<>(DynamicSet.emptySet(), executorMock, defaultCacheMatcher); + + handler.onRemoval( + "test", "accounts", RemovalNotification.create("test", "accounts", RemovalCause.EXPLICIT)); + + verifyZeroInteractions(executorMock); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java index 052a9ae..b35e862 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java
@@ -37,7 +37,6 @@ CachePatternMatcher matcher = new CachePatternMatcher(configurationMock); for (String cache : ImmutableList.of( - "accounts", "groups", "groups_byinclude", "groups_byname", @@ -55,6 +54,7 @@ } for (String cache : ImmutableList.of( + "accounts", "adv_bases", "change_kind", "change_notes",
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java index b86f8af..2a25c37 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
@@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -26,6 +27,7 @@ import com.google.gerrit.extensions.events.NewProjectCreatedListener; import com.google.gerrit.extensions.events.ProjectDeletedListener; import com.google.gerrit.extensions.registration.DynamicSet; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.cache.ProjectListUpdateHandler.ProjectListUpdateTask; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder; @@ -42,10 +44,14 @@ private ProjectListUpdateHandler handler; @Mock private ProjectListUpdateForwarder forwarder; + @Mock private ProjectsFilter projectsFilter; @Before public void setUp() { - handler = new ProjectListUpdateHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor()); + when(projectsFilter.matches(any(String.class))).thenReturn(true); + handler = + new ProjectListUpdateHandler( + asDynamicSet(forwarder), MoreExecutors.directExecutor(), projectsFilter); } private DynamicSet<ProjectListUpdateForwarder> asDynamicSet( @@ -87,6 +93,18 @@ } @Test + public void shouldNotForwardIfFilteredOutByProjectName() throws Exception { + when(projectsFilter.matches(any(String.class))).thenReturn(false); + String projectName = "projectToAdd"; + NewProjectCreatedListener.Event event = mock(NewProjectCreatedListener.Event.class); + when(event.getProjectName()).thenReturn(projectName); + handler.onNewProjectCreated(event); + verify(forwarder, never()) + .updateProjectList( + any(ProjectListUpdateTask.class), eq(new ProjectListUpdateEvent(projectName, true))); + } + + @Test public void testProjectUpdateTaskToString() throws Exception { String projectName = "someProjectName"; ProjectListUpdateTask task =
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java index 990a03f..bac0851 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
@@ -15,15 +15,19 @@ package com.googlesource.gerrit.plugins.multisite.event; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.google.common.util.concurrent.MoreExecutors; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.ProjectEvent; import com.google.gerrit.server.events.RefUpdatedEvent; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.event.EventHandler.EventTask; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder; @@ -39,10 +43,13 @@ private EventHandler eventHandler; @Mock private StreamEventForwarder forwarder; + @Mock private ProjectsFilter projectsFilter; @Before public void setUp() { - eventHandler = new EventHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor()); + when(projectsFilter.matches(any(ProjectEvent.class))).thenReturn(true); + eventHandler = + new EventHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor(), projectsFilter); } private DynamicSet<StreamEventForwarder> asDynamicSet(StreamEventForwarder forwarder) { @@ -53,7 +60,7 @@ @Test public void shouldForwardAnyProjectEvent() throws Exception { - Event event = mock(ProjectEvent.class); + ProjectEvent event = mock(ProjectEvent.class); eventHandler.onEvent(event); verify(forwarder).send(event); } @@ -73,6 +80,16 @@ } @Test + public void shouldNotForwardIfFilteredOutByProjectName() throws Exception { + when(projectsFilter.matches(any(ProjectEvent.class))).thenReturn(false); + + ProjectEvent event = mock(ProjectEvent.class); + + eventHandler.onEvent(event); + verify(forwarder, never()).send(event); + } + + @Test public void tesEventTaskToString() throws Exception { Event event = new RefUpdatedEvent(); EventTask task = eventHandler.new EventTask(event);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java index 747da79..90ae8da 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java
@@ -15,11 +15,10 @@ package com.googlesource.gerrit.plugins.multisite.forwarder; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.gerrit.entities.Account; import com.google.gerrit.extensions.registration.DynamicMap; import com.googlesource.gerrit.plugins.multisite.cache.Constants; @@ -30,19 +29,19 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; @RunWith(MockitoJUnitRunner.class) public class ForwardedCacheEvictionHandlerTest { @Rule public ExpectedException exception = ExpectedException.none(); @Mock private DynamicMap<Cache<?, ?>> cacheMapMock; - @Mock private Cache<?, ?> cacheMock; + private Cache<Object, Object> cacheUnderTest; private ForwardedCacheEvictionHandler handler; @Before public void setUp() throws Exception { handler = new ForwardedCacheEvictionHandler(cacheMapMock); + cacheUnderTest = CacheBuilder.newBuilder().build(); } @Test @@ -58,65 +57,22 @@ @Test public void testSuccessfulCacheEviction() throws Exception { CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(123)); - doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); + cacheUnderTest.put(entry.getKey(), new Object()); + doReturn(cacheUnderTest).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); handler.evict(entry); - verify(cacheMock).invalidate(entry.getKey()); + assertThat(cacheUnderTest.getIfPresent(entry.getKey())).isNull(); } @Test public void testSuccessfulProjectListCacheEviction() throws Exception { CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.PROJECT_LIST, null); - doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); + cacheUnderTest.put("foo", new Object()); + cacheUnderTest.put("bar", new Object()); + doReturn(cacheUnderTest).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); handler.evict(entry); - verify(cacheMock).invalidateAll(); - } - - @Test - public void shouldSetAndUnsetForwardedContext() throws Exception { - CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(456)); - doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); - - // this doAnswer is to allow to assert that context is set to forwarded - // while cache eviction is called. - doAnswer( - (Answer<Void>) - invocation -> { - assertThat(Context.isForwardedEvent()).isTrue(); - return null; - }) - .when(cacheMock) - .invalidate(entry.getKey()); - - assertThat(Context.isForwardedEvent()).isFalse(); - handler.evict(entry); - assertThat(Context.isForwardedEvent()).isFalse(); - - verify(cacheMock).invalidate(entry.getKey()); - } - - @Test - public void shouldSetAndUnsetForwardedContextEvenIfExceptionIsThrown() throws Exception { - CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(789)); - doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName()); - - doAnswer( - (Answer<Void>) - invocation -> { - assertThat(Context.isForwardedEvent()).isTrue(); - throw new RuntimeException(); - }) - .when(cacheMock) - .invalidate(entry.getKey()); - - assertThat(Context.isForwardedEvent()).isFalse(); - try { - handler.evict(entry); - } catch (RuntimeException e) { - } - assertThat(Context.isForwardedEvent()).isFalse(); - - verify(cacheMock).invalidate(entry.getKey()); + assertThat(cacheUnderTest.getIfPresent("foo")).isNull(); + assertThat(cacheUnderTest.getIfPresent("bar")).isNull(); } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java new file mode 100644 index 0000000..022dec5 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
@@ -0,0 +1,79 @@ +// 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.googlesource.gerrit.plugins.multisite.index; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; +import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder; +import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent; +import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexProjectTask; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class IndexEventHandlerTest { + + private IndexEventHandler eventHandler; + + @Mock private ProjectsFilter projectsFilter; + @Mock private IndexEventForwarder forwarder; + @Mock private ChangeCheckerImpl.Factory changeChecker; + + @Before + public void setUp() { + eventHandler = + new IndexEventHandler( + MoreExecutors.directExecutor(), + asDynamicSet(forwarder), + changeChecker, + projectsFilter, + new TestGroupChecker(true)); + } + + private DynamicSet<IndexEventForwarder> asDynamicSet(IndexEventForwarder forwarder) { + DynamicSet<IndexEventForwarder> result = new DynamicSet<>(); + result.add("multi-site", forwarder); + return result; + } + + @Test + public void shouldNotForwardProjectIndexedIfFilteredOutByProjectName() throws Exception { + when(projectsFilter.matches(any(String.class))).thenReturn(false); + + eventHandler.onProjectIndexed("test_project"); + verify(forwarder, never()) + .index(any(IndexProjectTask.class), eq(new ProjectIndexEvent("test_project"))); + } + + @Test + public void shouldNotForwardIndexChangeIfFilteredOutByProjectName() throws Exception { + int changeId = 1; + when(projectsFilter.matches(any(String.class))).thenReturn(false); + + eventHandler.onChangeIndexed("test_project", changeId); + verifyZeroInteractions(changeChecker); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java index b79740d..41bebc2 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
@@ -16,14 +16,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE; -import static org.mockito.Mockito.any; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.google.gerrit.entities.Project; import com.google.gerrit.metrics.DisabledMetricMaker; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement; @@ -66,12 +69,15 @@ @Mock SharedRefDatabaseWrapper sharedRefDatabase; @Mock SharedRefEnforcement tmpRefEnforcement; + + @Mock ProjectsFilter projectsFilter; + @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction; @Before public void setup() throws Exception { super.setUp(); - + when(projectsFilter.matches(anyString())).thenReturn(true); gitRepoSetup(); } @@ -142,6 +148,24 @@ (command) -> assertThat(command.getResult()).isEqualTo(ReceiveCommand.Result.LOCK_FAILURE)); } + @Test + public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception { + when(projectsFilter.matches(anyString())).thenReturn(false); + + String AN_OUT_OF_SYNC_REF = "refs/changes/01/1/1"; + BatchRefUpdate batchRefUpdate = + newBatchUpdate( + Collections.singletonList(new ReceiveCommand(A, B, AN_OUT_OF_SYNC_REF, UPDATE))); + BatchRefUpdateValidator batchRefUpdateValidator = + getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement); + + batchRefUpdateValidator.executeBatchUpdateWithValidation( + batchRefUpdate, () -> execute(batchRefUpdate), this::defaultRollback); + + verify(sharedRefDatabase, never()) + .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)); + } + private BatchRefUpdateValidator newDefaultValidator(String projectName) { return getRefValidatorForEnforcement(projectName, new DefaultSharedRefEnforcement()); } @@ -153,6 +177,7 @@ new ValidationMetrics(new DisabledMetricMaker()), sharedRefEnforcement, new DummyLockWrapper(), + projectsFilter, projectName, diskRepo.getRefDatabase()); }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java index e9651fb..4aa4ba1 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
@@ -15,6 +15,7 @@ package com.googlesource.gerrit.plugins.multisite.validation; import static java.util.Arrays.asList; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement; @@ -40,6 +42,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand.Result; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -58,6 +61,7 @@ @Mock RevWalk revWalk; @Mock ProgressMonitor progressMonitor; @Mock ValidationMetrics validationMetrics; + @Mock ProjectsFilter projectsFilter; @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction; private final Ref oldRef = @@ -94,6 +98,11 @@ return "branch_" + nameRule.getMethodName(); } + @Before + public void setup() { + when(projectsFilter.matches(anyString())).thenReturn(true); + } + @SuppressWarnings("deprecation") private void setMockRequiredReturnValues() throws IOException { @@ -170,6 +179,7 @@ validationMetrics, new DefaultSharedRefEnforcement(), new DummyLockWrapper(), + projectsFilter, projectName, refDb); }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java index 9e75c8f..029a4c6 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
@@ -16,11 +16,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.testing.GerritJUnit.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.Factory; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement; @@ -33,6 +36,7 @@ import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; +import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -49,6 +53,7 @@ @Mock SharedRefDatabaseWrapper sharedRefDb; @Mock ValidationMetrics validationMetrics; @Mock RefDatabase refDb; + @Mock ProjectsFilter projectsFilter; private final Ref oldRef = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_1); @@ -62,6 +67,11 @@ return "branch_" + nameRule.getMethodName(); } + @Before + public void setup() { + when(projectsFilter.matches(anyString())).thenReturn(true); + } + @Test public void newUpdateShouldValidateAndSucceed() throws Exception { @@ -92,7 +102,7 @@ } @Test - public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException { + public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() { doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef); @@ -106,8 +116,7 @@ } @Test - public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate() - throws IOException { + public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate() { doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef); doReturn(false) @@ -124,8 +133,7 @@ } @Test - public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate() - throws IOException { + public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate() { doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef); doReturn(false) @@ -159,7 +167,7 @@ } @Test - public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException { + public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() { doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef); @@ -184,6 +192,7 @@ validationMetrics, new DefaultSharedRefEnforcement(), new DummyLockWrapper(), + projectsFilter, projectName, refDb); return RefUpdateValidator;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java index 2eefb5a..d437a73 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -26,6 +26,7 @@ import com.google.gerrit.entities.Project; import com.google.gerrit.entities.RefNames; +import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.RefUpdatedEvent; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.project.ProjectConfig; @@ -33,6 +34,7 @@ import com.google.gerrit.testing.InMemoryTestEnvironment; import com.google.inject.Inject; import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.forwarder.Context; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture; @@ -63,6 +65,7 @@ @Mock SharedRefDatabaseWrapper sharedRefDb; @Mock GitReferenceUpdated gitReferenceUpdated; @Mock ProjectVersionLogger verLogger; + @Mock ProjectsFilter projectsFilter; @Inject private ProjectConfig.Factory projectConfigFactory; @Inject private InMemoryRepositoryManager repoManager; @@ -73,6 +76,7 @@ @Before public void setUp() throws Exception { + when(projectsFilter.matches(any(Event.class))).thenReturn(true); InMemoryRepository inMemoryRepo = repoManager.createRepository(A_TEST_PROJECT_NAME_KEY); project = projectConfigFactory.create(A_TEST_PROJECT_NAME_KEY); project.load(inMemoryRepo); @@ -105,7 +109,8 @@ when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .onEvent(refUpdatedEvent); Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); @@ -150,7 +155,8 @@ when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .onEvent(refUpdatedEvent); Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); @@ -190,7 +196,8 @@ when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .onEvent(refUpdatedEvent); Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); @@ -227,7 +234,8 @@ when(refUpdatedEvent.getRefName()).thenReturn(magicRefName); repo.branch(magicRefName).commit().create(); - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .onEvent(refUpdatedEvent); Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); @@ -242,7 +250,30 @@ when(refUpdatedEvent.getProjectNameKey()).thenReturn(Project.nameKey("aNonExistentProject")); when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) + .onEvent(refUpdatedEvent); + + Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); + assertThat(ref).isNull(); + + verifyZeroInteractions(verLogger); + } + + @Test + public void shouldNotUpdateProjectVersionWhenProjectFilteredOut() throws Exception { + when(projectsFilter.matches(any(Event.class))).thenReturn(false); + + Context.setForwardedEvent(false); + + Thread.sleep(1000L); + repo.branch("master").commit().create(); + + Thread.sleep(1000L); + repo.branch("master").update(masterCommit); + + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .onEvent(refUpdatedEvent); Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); @@ -257,7 +288,8 @@ .thenReturn(Optional.of("123")); Optional<Long> version = - new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + new ProjectVersionRefUpdate( + repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter) .getProjectRemoteVersion(A_TEST_PROJECT_NAME); assertThat(version.isPresent()).isTrue();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java index 323db63..b9b07bd 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.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.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; @@ -23,9 +24,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError; import com.google.gerrit.entities.Project; +import com.googlesource.gerrit.plugins.multisite.ProjectsFilter; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; +import com.googlesource.gerrit.plugins.multisite.SharedRefLogger; import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterFunction; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement; import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture; @@ -47,12 +49,16 @@ @Mock SharedRefDatabaseWrapper sharedRefDb; + @Mock SharedRefLogger sharedRefLogger; + @Mock RefDatabase localRefDb; @Mock ValidationMetrics validationMetrics; @Mock RefUpdate refUpdate; + @Mock ProjectsFilter projectsFilter; + @Mock OneParameterFunction<ObjectId, Result> rollbackFunction; String refName; @@ -69,21 +75,26 @@ newUpdateRef = newRef(refName, AN_OBJECT_ID_2); localRef = newRef(refName, AN_OBJECT_ID_3); - doReturn(localRef).when(localRefDb).getRef(refName); + doReturn(localRef).when(localRefDb).findRef(refName); doReturn(localRef).when(localRefDb).exactRef(refName); doReturn(newUpdateRef.getObjectId()).when(refUpdate).getNewObjectId(); doReturn(refName).when(refUpdate).getName(); lenient().doReturn(oldUpdateRef.getObjectId()).when(refUpdate).getOldObjectId(); doReturn(Result.FAST_FORWARD).when(rollbackFunction).invoke(any()); - refUpdateValidator = - new RefUpdateValidator( - sharedRefDb, - validationMetrics, - defaultRefEnforcement, - new DummyLockWrapper(), - A_TEST_PROJECT_NAME, - localRefDb); + doReturn(true).when(projectsFilter).matches(anyString()); + + refUpdateValidator = newRefUpdateValidator(sharedRefDb); + } + + @Test + public void validationShouldSucceedWhenSharedRefDbIsNoop() throws Exception { + SharedRefDatabaseWrapper noopSharedRefDbWrapper = new SharedRefDatabaseWrapper(sharedRefLogger); + + Result result = + newRefUpdateValidator(noopSharedRefDbWrapper) + .executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback); + assertThat(result).isEqualTo(RefUpdate.Result.NEW); } @Test @@ -119,7 +130,7 @@ doReturn(true) .when(sharedRefDb) .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, ObjectId.zeroId()); - doReturn(localRef).doReturn(null).when(localRefDb).getRef(refName); + doReturn(localRef).doReturn(null).when(localRefDb).findRef(refName); Result result = refUpdateValidator.executeRefUpdate( @@ -140,7 +151,7 @@ doReturn(true) .when(sharedRefDb) .compareAndPut(A_TEST_PROJECT_NAME_KEY, localNullRef, newUpdateRef.getObjectId()); - doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).getRef(refName); + doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).findRef(refName); Result result = refUpdateValidator.executeRefUpdate( @@ -206,20 +217,25 @@ } @Test - public void shouldRollbackRefUpdateWhenCompareAndPutIsFailing() throws Exception { - lenient() - .doReturn(false) - .when(sharedRefDb) - .isUpToDate(any(Project.NameKey.class), any(Ref.class)); - doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef); + public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception { + when(projectsFilter.matches(anyString())).thenReturn(false); - when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class))) - .thenThrow(GlobalRefDbSystemError.class); - when(rollbackFunction.invoke(any())).thenReturn(Result.LOCK_FAILURE); + refUpdateValidator.executeRefUpdate( + refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback); - refUpdateValidator.executeRefUpdate(refUpdate, () -> Result.NEW, rollbackFunction); + verify(sharedRefDb, never()) + .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)); + } - verify(rollbackFunction, times(1)).invoke(any()); + private RefUpdateValidator newRefUpdateValidator(SharedRefDatabaseWrapper refDbWrapper) { + return new RefUpdateValidator( + refDbWrapper, + validationMetrics, + defaultRefEnforcement, + new DummyLockWrapper(), + projectsFilter, + A_TEST_PROJECT_NAME, + localRefDb); } private Result defaultRollback(ObjectId objectId) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java index 7e4593b..f2b57a1 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
@@ -68,7 +68,7 @@ private TestRepository<InMemoryRepository> repo; @Before - public void setUp() throws Exception { + public void setupTestRepo() throws Exception { InMemoryRepository inMemoryRepo = gitRepositoryManager.createRepository(A_TEST_PROJECT_NAME_KEY); repo = new TestRepository<>(inMemoryRepo);