blob: a6ccbec5e611f4536b2a96679054e7b2babfdf02 [file] [log] [blame]
// Copyright (C) 2019 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.replication;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.googlesource.gerrit.plugins.replication.PushResultProcessing.NO_OP;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import java.io.IOException;
import java.time.Duration;
import java.util.function.Supplier;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Test;
@UseLocalDisk
@TestPlugin(
name = "replication",
sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
public class ReplicationIT extends ReplicationDaemon {
private static final Duration TEST_TIMEOUT =
Duration.ofSeconds(
(TEST_REPLICATION_DELAY_SECONDS + TEST_REPLICATION_RETRY_MINUTES * 60) + 1);
@Inject private DynamicSet<ProjectDeletedListener> deletedListeners;
@Test
public void shouldReplicateNewProject() throws Exception {
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Project.NameKey sourceProject = createTestProject("foo");
WaitUtil.waitUntil(
() -> nonEmptyProjectExists(new Project.NameKey(sourceProject + "replica.git")),
TEST_NEW_PROJECT_TIMEOUT);
ProjectInfo replicaProject = gApi.projects().name(sourceProject + "replica").get();
assertThat(replicaProject).isNotNull();
}
@Test
public void shouldReplicateProjectDeletion() throws Exception {
String projectNameDeleted = "project-deleted";
Project.NameKey replicaProject = createTestProject(projectNameDeleted + "replica");
setReplicationDestination("foo", "replica", ALL_PROJECTS);
setProjectDeletionReplication("foo", true);
reloadConfig();
ProjectDeletedListener.Event event =
new ProjectDeletedListener.Event() {
@Override
public String getProjectName() {
return name(projectNameDeleted);
}
@Override
public NotifyHandling getNotify() {
return NotifyHandling.NONE;
}
};
for (ProjectDeletedListener l : deletedListeners) {
l.onProjectDeleted(event);
}
waitUntil(() -> !nonEmptyProjectExists(replicaProject));
}
@Test
public void shouldReplicateNewChangeRef() throws Exception {
Project.NameKey targetProject = createTestProject("projectreplica");
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Result pushResult = createChange();
RevCommit sourceCommit = pushResult.getCommit();
String sourceRef = pushResult.getPatchSet().getRefName();
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
Ref targetBranchRef = getRef(repo, sourceRef);
assertThat(targetBranchRef).isNotNull();
assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
}
}
@Test
public void shouldReplicateNewBranch() throws Exception {
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Project.NameKey targetProject = createTestProject("projectreplica");
String newBranch = "refs/heads/mybranch";
String master = "refs/heads/master";
BranchInput input = new BranchInput();
input.revision = master;
gApi.projects().name(project.get()).branch(newBranch).create(input);
try (Repository repo = repoManager.openRepository(targetProject);
Repository sourceRepo = repoManager.openRepository(project)) {
waitUntil(() -> checkedGetRef(repo, newBranch) != null);
Ref masterRef = getRef(sourceRepo, master);
Ref targetBranchRef = getRef(repo, newBranch);
assertThat(targetBranchRef).isNotNull();
assertThat(targetBranchRef.getObjectId()).isEqualTo(masterRef.getObjectId());
}
}
@Test
public void shouldReplicateNewBranchToTwoRemotes() throws Exception {
Project.NameKey targetProject1 = createTestProject("projectreplica1");
Project.NameKey targetProject2 = createTestProject("projectreplica2");
setReplicationDestination("foo1", "replica1", ALL_PROJECTS);
setReplicationDestination("foo2", "replica2", ALL_PROJECTS);
reloadConfig();
Result pushResult = createChange();
RevCommit sourceCommit = pushResult.getCommit();
String sourceRef = pushResult.getPatchSet().getRefName();
try (Repository repo1 = repoManager.openRepository(targetProject1);
Repository repo2 = repoManager.openRepository(targetProject2)) {
waitUntil(
() ->
(checkedGetRef(repo1, sourceRef) != null && checkedGetRef(repo2, sourceRef) != null));
Ref targetBranchRef1 = getRef(repo1, sourceRef);
assertThat(targetBranchRef1).isNotNull();
assertThat(targetBranchRef1.getObjectId()).isEqualTo(sourceCommit.getId());
Ref targetBranchRef2 = getRef(repo2, sourceRef);
assertThat(targetBranchRef2).isNotNull();
assertThat(targetBranchRef2.getObjectId()).isEqualTo(sourceCommit.getId());
}
}
@Test
public void shouldMatchTemplatedURL() throws Exception {
Project.NameKey targetProject = createTestProject("projectreplica");
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
String newRef = "refs/heads/newForTest";
ObjectId newRefTip = createNewBranchWithoutPush("refs/heads/master", newRef);
String urlMatch = gitPath.resolve("${name}" + "replica" + ".git").toString();
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
.scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newRef) != null);
Ref targetBranchRef = getRef(repo, newRef);
assertThat(targetBranchRef).isNotNull();
assertThat(targetBranchRef.getObjectId()).isEqualTo(newRefTip);
}
}
@Test
public void shouldMatchRealURL() throws Exception {
Project.NameKey targetProject = createTestProject("projectreplica");
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
String newRef = "refs/heads/newForTest";
ObjectId newRefTip = createNewBranchWithoutPush("refs/heads/master", newRef);
String urlMatch = gitPath.resolve(project + "replica" + ".git").toString();
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
.scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newRef) != null);
Ref targetBranchRef = getRef(repo, newRef);
assertThat(targetBranchRef).isNotNull();
assertThat(targetBranchRef.getObjectId()).isEqualTo(newRefTip);
}
}
@Test
public void shouldReplicateHeadUpdate() throws Exception {
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Project.NameKey targetProject = createTestProject("projectreplica");
String newHead = "refs/heads/newhead";
String master = "refs/heads/master";
BranchInput input = new BranchInput();
input.revision = master;
gApi.projects().name(project.get()).branch(newHead).create(input);
gApi.projects().name(project.get()).head(newHead);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newHead) != null);
Ref targetProjectHead = getRef(repo, Constants.HEAD);
assertThat(targetProjectHead).isNotNull();
assertThat(targetProjectHead.getTarget().getName()).isEqualTo(newHead);
}
}
@Test
public void shouldNotDrainTheQueueWhenReloading() throws Exception {
// Setup repo to replicate
Project.NameKey targetProject = createTestProject("projectreplica");
String remoteName = "doNotDrainQueue";
setReplicationDestination(remoteName, "replica", ALL_PROJECTS);
Result pushResult = createChange();
shutdownConfig();
pushResult.getCommit();
String sourceRef = pushResult.getPatchSet().getRefName();
assertThrows(
InterruptedException.class,
() -> {
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
}
});
}
@Test
public void shouldDrainTheQueueWhenReloading() throws Exception {
// Setup repo to replicate
Project.NameKey targetProject = createTestProject("projectreplica");
String remoteName = "drainQueue";
setReplicationDestination(remoteName, "replica", ALL_PROJECTS);
config.setInt("remote", remoteName, "drainQueueAttempts", 2);
config.save();
reloadConfig();
Result pushResult = createChange();
shutdownConfig();
RevCommit sourceCommit = pushResult.getCommit();
String sourceRef = pushResult.getPatchSet().getRefName();
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
Ref targetBranchRef = getRef(repo, sourceRef);
assertThat(targetBranchRef).isNotNull();
assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
}
}
private Ref getRef(Repository repo, String branchName) throws IOException {
return repo.getRefDatabase().exactRef(branchName);
}
private void setProjectDeletionReplication(String remoteName, boolean replicateProjectDeletion)
throws IOException {
config.setBoolean("remote", remoteName, "replicateProjectDeletions", replicateProjectDeletion);
config.save();
}
private void waitUntil(Supplier<Boolean> waitCondition) throws InterruptedException {
WaitUtil.waitUntil(waitCondition, TEST_TIMEOUT);
}
private void shutdownConfig() {
plugin.getSysInjector().getInstance(AutoReloadConfigDecorator.class).shutdown();
}
private ObjectId createNewBranchWithoutPush(String fromBranch, String newBranch)
throws Exception {
try (Repository repo = repoManager.openRepository(project);
RevWalk walk = new RevWalk(repo)) {
Ref ref = repo.exactRef(fromBranch);
RevCommit tip = null;
if (ref != null) {
tip = walk.parseCommit(ref.getObjectId());
}
RefUpdate update = repo.updateRef(newBranch);
update.setNewObjectId(tip);
update.update(walk);
return update.getNewObjectId();
}
}
}