blob: 13afc0d9c63ab3f148957ffc654f1a00cd8078ab [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.googlesource.gerrit.plugins.replication;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.WaitUtil;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.server.events.EventDispatcher;
import com.google.gerrit.server.events.ProjectEvent;
import com.google.gerrit.server.events.RefEvent;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationDoneEvent;
import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationFailedEvent;
import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationScheduledEvent;
import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationSucceededEvent;
import com.googlesource.gerrit.plugins.replication.events.RefReplicatedEvent;
import com.googlesource.gerrit.plugins.replication.events.RefReplicationDoneEvent;
import com.googlesource.gerrit.plugins.replication.events.ReplicationScheduledEvent;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
@UseLocalDisk
@Sandboxed
@TestPlugin(
name = "replication",
sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
public class ReplicationEventsIT extends ReplicationDaemon {
private static final Duration TEST_POST_EVENT_TIMEOUT = Duration.ofSeconds(1);
@Inject private DynamicSet<ProjectDeletedListener> deletedListeners;
@Inject private DynamicItem<EventDispatcher> eventDispatcher;
private TestDispatcher testDispatcher;
@Before
public void setup() throws Exception {
initConfig();
setReplicationDestination(
"remote1",
"suffix1",
Optional.of("not-used-project")); // Simulates a full replication.config initialization
setUpTestPlugin();
testDispatcher = new TestDispatcher();
eventDispatcher.set(testDispatcher, eventDispatcher.getPluginName());
}
@Test
public void replicateNewChangeSendsEvents() throws Exception {
Project.NameKey targetProject = createTestProject(project + "replica");
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Result pushResult = createChange();
String sourceRef = pushResult.getPatchSet().refName();
String metaRef = pushResult.getChange().notes().getRefName();
BranchNameKey changeBranch = BranchNameKey.create(project, sourceRef);
BranchNameKey metaBranch = BranchNameKey.create(project, metaRef);
assertThat(testDispatcher.getEvents(changeBranch, ReplicationScheduledEvent.class)).hasSize(1);
assertThat(testDispatcher.getEvents(metaBranch, ReplicationScheduledEvent.class)).hasSize(1);
isPushCompleted(targetProject, sourceRef, TEST_PUSH_TIMEOUT);
isPushCompleted(targetProject, metaRef, TEST_PUSH_TIMEOUT);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicatedEvent.class), metaRef);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicatedEvent.class), sourceRef);
assertThat(testDispatcher.getEvents(RefReplicatedEvent.class).size()).isEqualTo(2);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicationDoneEvent.class), metaRef);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicationDoneEvent.class), sourceRef);
assertThat(testDispatcher.getEvents(RefReplicationDoneEvent.class).size()).isEqualTo(2);
}
@Test
public void replicateNewBranchSendsEvents() throws Exception {
setReplicationDestination("foo", "replica", ALL_PROJECTS);
reloadConfig();
Project.NameKey targetProject = createTestProject(project + "replica");
String newBranch = "refs/heads/mybranch";
BranchNameKey branchName = BranchNameKey.create(project, newBranch);
String master = "refs/heads/master";
BranchInput input = new BranchInput();
input.revision = master;
gApi.projects().name(project.get()).branch(newBranch).create(input);
assertThat(testDispatcher.getEvents(branchName, ReplicationScheduledEvent.class)).hasSize(1);
isPushCompleted(targetProject, newBranch, TEST_PUSH_TIMEOUT);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicatedEvent.class), newBranch);
assertThat(testDispatcher.getEvents(RefReplicatedEvent.class).size()).isEqualTo(1);
waitForRefEvent(() -> testDispatcher.getEvents(RefReplicationDoneEvent.class), newBranch);
assertThat(testDispatcher.getEvents(RefReplicationDoneEvent.class).size()).isEqualTo(1);
}
@Test
public void shouldEmitProjectDeletionEventsForOneRemote() throws Exception {
String projectName = project.get();
setReplicationTarget("replica", project.get());
reloadConfig();
for (ProjectDeletedListener l : deletedListeners) {
l.onProjectDeleted(projectDeletedEvent(projectName));
}
List<ProjectDeletionReplicationScheduledEvent> scheduledEvents =
testDispatcher.getEvents(project, ProjectDeletionReplicationScheduledEvent.class);
assertThat(scheduledEvents).hasSize(1);
assertThatAnyMatch(
scheduledEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class), 1);
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class),
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class), 1);
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class),
e -> project.equals(e.getProjectNameKey()));
}
@Test
public void shouldEmitProjectDeletionEventsForMultipleRemotesWhenSucceeding() throws Exception {
String projectName = project.get();
setReplicationTarget("replica1", projectName);
setReplicationTarget("replica2", projectName);
reloadConfig();
for (ProjectDeletedListener l : deletedListeners) {
l.onProjectDeleted(projectDeletedEvent(projectName));
}
List<ProjectDeletionReplicationScheduledEvent> scheduledEvents =
testDispatcher.getEvents(project, ProjectDeletionReplicationScheduledEvent.class);
assertThat(scheduledEvents).hasSize(2);
assertThatAnyMatch(
scheduledEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica1.git"));
assertThatAnyMatch(
scheduledEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica2.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class), 2);
List<ProjectDeletionReplicationSucceededEvent> successEvents =
testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class);
assertThatAnyMatch(
successEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica1.git"));
assertThatAnyMatch(
successEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica2.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class), 1);
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class),
e -> project.equals(e.getProjectNameKey()));
}
@Test
public void shouldEmitProjectDeletionEventsForMultipleRemotesWhenFailing() throws Exception {
String projectName = project.get();
setReplicationTarget("replica1", projectName);
setReplicationDestination(
"not-existing-replica", "not-existing-replica", Optional.of(projectName));
setProjectDeletionReplication("not-existing-replica", true);
reloadConfig();
for (ProjectDeletedListener l : deletedListeners) {
l.onProjectDeleted(projectDeletedEvent(projectName));
}
List<ProjectDeletionReplicationScheduledEvent> scheduledEvents =
testDispatcher.getEvents(project, ProjectDeletionReplicationScheduledEvent.class);
assertThat(scheduledEvents).hasSize(2);
assertThatAnyMatch(
scheduledEvents,
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica1.git"));
assertThatAnyMatch(
scheduledEvents,
e ->
project.equals(e.getProjectNameKey())
&& e.getTargetUri().endsWith("not-existing-replica.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class), 1);
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationFailedEvent.class), 1);
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationSucceededEvent.class),
e -> project.equals(e.getProjectNameKey()) && e.getTargetUri().endsWith("replica1.git"));
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationFailedEvent.class),
e ->
project.equals(e.getProjectNameKey())
&& e.getTargetUri().endsWith("not-existing-replica.git"));
waitForProjectEvent(
() -> testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class), 1);
assertThatAnyMatch(
testDispatcher.getEvents(project, ProjectDeletionReplicationDoneEvent.class),
e -> project.equals(e.getProjectNameKey()));
}
private <T extends RefEvent> void waitForRefEvent(Supplier<List<T>> events, String refName)
throws InterruptedException {
WaitUtil.waitUntil(
() -> events.get().stream().filter(e -> refName.equals(e.getRefName())).count() == 1,
TEST_POST_EVENT_TIMEOUT);
}
private <T extends ProjectEvent> void waitForProjectEvent(Supplier<List<T>> events, int count)
throws InterruptedException {
WaitUtil.waitUntil(() -> events.get().size() == count, TEST_POST_EVENT_TIMEOUT);
}
private Project.NameKey setReplicationTarget(String replica, String ofProject) throws Exception {
Project.NameKey replicaProject = createTestProject(String.format("%s%s", ofProject, replica));
setReplicationDestination(replica, replica, Optional.of(ofProject));
setProjectDeletionReplication(replica, true);
return replicaProject;
}
private <T extends ProjectEvent> void assertThatAnyMatch(List<T> events, Predicate<T> p) {
assertThat(events.stream().anyMatch(p)).isTrue();
}
}