Merge remote-tracking branch 'origin/stable-2.16' into stable-3.0 * origin/stable-2.16: Improve readability of the refs filtering during replication Reload local version when in memory ref is not up to date Remove EventConsumerIT test Change-Id: I963f190f06f979c65043a7e7e1c8f6f50ee65433
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java index 42f7194..b666c02 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
@@ -15,76 +15,141 @@ package com.googlesource.gerrit.plugins.multisite.validation; import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException; +import com.google.common.base.Preconditions; +import com.google.common.flogger.FluentLogger; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter; +import java.io.IOException; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class MultisiteReplicationPushFilter implements ReplicationPushFilter { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final String REF_META_SUFFIX = "/meta"; + public static final int MIN_WAIT_BEFORE_RELOAD_LOCAL_VERSION_MS = 1000; + public static final int RANDOM_WAIT_BEFORE_RELOAD_LOCAL_VERSION_MS = 1000; + static final String REPLICATION_LOG_NAME = "replication_log"; static final Logger repLog = LoggerFactory.getLogger(REPLICATION_LOG_NAME); private final SharedRefDatabaseWrapper sharedRefDb; + private final GitRepositoryManager gitRepositoryManager; @Inject - public MultisiteReplicationPushFilter(SharedRefDatabaseWrapper sharedRefDb) { + public MultisiteReplicationPushFilter( + SharedRefDatabaseWrapper sharedRefDb, GitRepositoryManager gitRepositoryManager) { this.sharedRefDb = sharedRefDb; + this.gitRepositoryManager = gitRepositoryManager; } @Override public List<RemoteRefUpdate> filter(String projectName, List<RemoteRefUpdate> remoteUpdatesList) { Set<String> outdatedChanges = new HashSet<>(); - List<RemoteRefUpdate> filteredRefUpdates = - remoteUpdatesList.stream() - .filter( - refUpdate -> { - String ref = refUpdate.getSrcRef(); - try { - if (sharedRefDb.isUpToDate( - new Project.NameKey(projectName), - new ObjectIdRef.Unpeeled( - Ref.Storage.NETWORK, ref, refUpdate.getNewObjectId()))) { - return true; + try (Repository repository = + gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) { + List<RemoteRefUpdate> filteredRefUpdates = + remoteUpdatesList.stream() + .filter( + refUpdate -> { + boolean refUpToDate = isUpToDateWithRetry(projectName, repository, refUpdate); + if (!refUpToDate) { + repLog.warn( + "{} is not up-to-date with the shared-refdb and thus will NOT BE replicated", + refUpdate); + if (refUpdate.getSrcRef().endsWith(REF_META_SUFFIX)) { + outdatedChanges.add(getRootChangeRefPrefix(refUpdate.getSrcRef())); + } } - repLog.warn( - "{} is not up-to-date with the shared-refdb and thus will NOT BE replicated", - refUpdate); - } catch (GlobalRefDbLockException e) { - repLog.warn( - "{} is locked on shared-refdb and thus will NOT BE replicated", refUpdate); - } - if (ref.endsWith(REF_META_SUFFIX)) { - outdatedChanges.add(getRootChangeRefPrefix(ref)); - } - return false; - }) - .collect(Collectors.toList()); + return refUpToDate; + }) + .collect(Collectors.toList()); - return filteredRefUpdates.stream() - .filter( - refUpdate -> { - if (outdatedChanges.contains(changePrefix(refUpdate.getSrcRef()))) { - repLog.warn( - "{} belongs to an outdated /meta ref and thus will NOT BE replicated", - refUpdate); - return false; - } - return true; - }) - .collect(Collectors.toList()); + return filteredRefUpdates.stream() + .filter( + refUpdate -> { + if (outdatedChanges.contains(changePrefix(refUpdate.getSrcRef()))) { + repLog.warn( + "{} belongs to an outdated /meta ref and thus will NOT BE replicated", + refUpdate); + return false; + } + return true; + }) + .collect(Collectors.toList()); + + } catch (IOException ioe) { + String message = String.format("Error while opening project: '%s'", projectName); + repLog.error(message); + logger.atSevere().withCause(ioe).log(message); + return Collections.emptyList(); + } + } + + private boolean isUpToDateWithRetry( + String projectName, Repository repository, RemoteRefUpdate refUpdate) { + String ref = refUpdate.getSrcRef(); + try { + if (sharedRefDb.isUpToDate( + new Project.NameKey(projectName), + new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, ref, refUpdate.getNewObjectId()))) { + return true; + } + + randomSleepForMitigatingConditionWhereLocalRefHaveJustBeenChanged( + projectName, refUpdate, ref); + + return sharedRefDb.isUpToDate( + new Project.NameKey(projectName), + new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, ref, getNotNullExactRef(repository, ref))); + } catch (GlobalRefDbLockException gle) { + String message = + String.format("%s is locked on shared-refdb and thus will NOT BE replicated", ref); + repLog.error(message); + logger.atSevere().withCause(gle).log(message); + return false; + } catch (IOException ioe) { + String message = + String.format("Error while extracting ref '%s' for project '%s'", ref, projectName); + repLog.error(message); + logger.atSevere().withCause(ioe).log(message); + return false; + } + } + + private void randomSleepForMitigatingConditionWhereLocalRefHaveJustBeenChanged( + String projectName, RemoteRefUpdate refUpdate, String ref) { + int randomSleepTimeMsec = + MIN_WAIT_BEFORE_RELOAD_LOCAL_VERSION_MS + + new Random().nextInt(RANDOM_WAIT_BEFORE_RELOAD_LOCAL_VERSION_MS); + repLog.debug( + String.format( + "'%s' is not up-to-date for project '%s' [local='%s']. Reload local ref in '%d ms' and re-check", + ref, projectName, refUpdate.getNewObjectId(), randomSleepTimeMsec)); + try { + Thread.sleep(randomSleepTimeMsec); + } catch (InterruptedException ie) { + String message = + String.format("Error while waiting for next check for '%s', ref '%s'", projectName, ref); + repLog.error(message); + logger.atWarning().withCause(ie).log(message); + } } private String changePrefix(String changeRef) { @@ -106,4 +171,10 @@ return changeMetaRef; } + + private ObjectId getNotNullExactRef(Repository repository, String refName) throws IOException { + Ref ref = repository.exactRef(refName); + Preconditions.checkNotNull(ref); + return ref.getObjectId(); + } }
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 e2de613..fb8eb40 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
@@ -18,37 +18,61 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase; import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException; import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.testing.InMemoryRepositoryManager; +import com.google.gerrit.testing.InMemoryTestEnvironment; +import com.google.inject.Inject; import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger; import com.googlesource.gerrit.plugins.multisite.validation.MultisiteReplicationPushFilter; -import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class MultisiteReplicationPushFilterTest { +public class MultisiteReplicationPushFilterTest extends LocalDiskRepositoryTestCase + implements RefFixture { + + @Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment(); @Mock SharedRefDatabaseWrapper sharedRefDatabaseMock; - String project = "fooProject"; - Project.NameKey projectName = new Project.NameKey(project); + @Inject private InMemoryRepositoryManager gitRepositoryManager; + + String project = A_TEST_PROJECT_NAME; + Project.NameKey projectName = A_TEST_PROJECT_NAME_KEY; + + private TestRepository<InMemoryRepository> repo; + + @Before + public void setUp() throws Exception { + InMemoryRepository inMemoryRepo = + gitRepositoryManager.createRepository(A_TEST_PROJECT_NAME_KEY); + repo = new TestRepository<>(inMemoryRepo); + } @Test public void shouldReturnAllRefUpdatesWhenAllUpToDate() throws Exception { @@ -57,7 +81,7 @@ doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any()); MultisiteReplicationPushFilter pushFilter = - new MultisiteReplicationPushFilter(sharedRefDatabaseMock); + new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager); List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates); assertThat(filteredRefUpdates).containsExactlyElementsIn(refUpdates); @@ -71,13 +95,42 @@ SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(outdatedRef.getSrcRef()); MultisiteReplicationPushFilter pushFilter = - new MultisiteReplicationPushFilter(sharedRefDatabase); + new MultisiteReplicationPushFilter(sharedRefDatabase, gitRepositoryManager); List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates); assertThat(filteredRefUpdates).containsExactly(refUpToDate); } @Test + public void shouldLoadLocalVersionAndNotFilter() throws Exception { + RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated"); + List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated); + doReturn(false).doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any()); + + MultisiteReplicationPushFilter pushFilter = + new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager); + List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates); + + assertThat(filteredRefUpdates).containsExactly(temporaryOutdated); + verify(sharedRefDatabaseMock, times(2)).isUpToDate(any(), any()); + } + + @Test + public void shouldLoadLocalVersionAndFilter() throws Exception { + RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated"); + repo.branch("refs/heads/temporaryOutdated").commit().create(); + List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated); + doReturn(false).doReturn(false).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any()); + + MultisiteReplicationPushFilter pushFilter = + new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager); + List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates); + + assertThat(filteredRefUpdates).isEmpty(); + verify(sharedRefDatabaseMock, times(2)).isUpToDate(any(), any()); + } + + @Test public void shouldFilterOutAllOutdatedChangesRef() throws Exception { RemoteRefUpdate refUpToDate = refUpdate("refs/heads/uptodate"); RemoteRefUpdate refChangeUpToDate = refUpdate("refs/changes/25/1225/2"); @@ -88,7 +141,7 @@ SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(changeMetaRef.getSrcRef()); MultisiteReplicationPushFilter pushFilter = - new MultisiteReplicationPushFilter(sharedRefDatabase); + new MultisiteReplicationPushFilter(sharedRefDatabase, gitRepositoryManager); List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates); assertThat(filteredRefUpdates).containsExactly(refUpToDate, refChangeUpToDate); @@ -145,9 +198,10 @@ new DisabledSharedRefLogger()); } - private RemoteRefUpdate refUpdate(String refName) throws IOException { + private RemoteRefUpdate refUpdate(String refName) throws Exception { ObjectId srcObjId = ObjectId.fromString("0000000000000000000000000000000000000001"); Ref srcRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, refName, srcObjId); + repo.branch(refName).commit().create(); return new RemoteRefUpdate(null, srcRef, "origin", false, "origin", srcObjId); } }