blob: 109cc7ce02a90e42d9cdcf8510b0d20d98172ac5 [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.replicationstatus;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.extensions.restapi.Url.encode;
import static com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.config.GlobalPluginConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.gerrit.server.config.SitePaths;
import com.google.gson.Gson;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.RefReplicatedEvent;
import com.googlesource.gerrit.plugins.replication.ReplicationScheduledEvent;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Map;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.Before;
import org.junit.Test;
@TestPlugin(
name = "replication-status",
sysModule = "com.googlesource.gerrit.plugins.replicationstatus.Module")
@UseLocalDisk
public class ReplicationStatusIT extends LightweightPluginDaemonTest {
private static final String REF_MASTER = Constants.R_HEADS + Constants.MASTER;
private static final String REMOTE_TAGRET_NODE = "some.remote.host";
private static final String REMOTE_TARGET_URL_TEMPLATE = "ssh://some.remote.host/git/${name}.git";
private static final String REMOTE_NAME = "some-remote-host";
private static final Gson gson = newGson();
@Inject protected SitePaths sitePaths;
@Inject private ProjectOperations projectOperations;
private EventHandler eventHandler;
@Before
public void setUp() throws IOException {
eventHandler = plugin.getSysInjector().getInstance(EventHandler.class);
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldBeOKSuccessForAdminUsers() throws Exception {
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(emptyReplicationStatus(project, REMOTE_TAGRET_NODE));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldBeOKSuccessForProjectOwners() throws Exception {
makeProjectOwner(user, project);
RestResponse result = userRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(emptyReplicationStatus(project, REMOTE_TAGRET_NODE));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldBeForbiddenForNonProjectOwners() throws Exception {
RestResponse result = userRestSession.get(endpoint(project, REMOTE_NAME));
result.assertForbidden();
assertThat(result.getEntityContent()).contains("Administrate Server or Project owner required");
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldBeForbiddenForAnonymousUsers() throws Exception {
RestResponse result = anonymousRestSession.get(endpoint(project, REMOTE_NAME));
result.assertForbidden();
assertThat(result.getEntityContent()).contains("Administrate Server or Project owner required");
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldNotReportStatusOfReplicationsGeneratedOnDifferentNodes() throws Exception {
eventHandler.onEvent(
successReplicatedEvent("testInstanceId-2", System.currentTimeMillis(), REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(emptyReplicationStatus(project, REMOTE_TAGRET_NODE));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldReturnSuccessfulProjectReplicationStatus() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(
successReplicatedEvent("testInstanceId-1", eventCreatedOn, REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(successReplicationStatus(REMOTE_TAGRET_NODE, project, eventCreatedOn));
}
@Test
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldReturnScheduledProjectReplicationStatus() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(scheduledEvent(null, eventCreatedOn, REF_MASTER, REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(scheduledReplicationStatus(REMOTE_TAGRET_NODE, project, eventCreatedOn));
}
@Test
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldConsumeEventsThatHaveNoInstanceId() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(successReplicatedEvent(null, eventCreatedOn, REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(successReplicationStatus(REMOTE_TAGRET_NODE, project, eventCreatedOn));
}
@Test
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldNotConsumeEventsWhenNodeInstanceIdIsNullButEventHasIt() throws Exception {
eventHandler.onEvent(
successReplicatedEvent("testInstanceId-2", System.currentTimeMillis(), REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(emptyReplicationStatus(project, REMOTE_TAGRET_NODE));
}
@Test
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldConsumeEventsWhenBothNodeAndEventHaveNoInstanceId() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(successReplicatedEvent(null, eventCreatedOn, REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(successReplicationStatus(REMOTE_TAGRET_NODE, project, eventCreatedOn));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldShowFailedInPayloadWhenRefCouldntBeReplicated() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(
failedReplicatedEvent("testInstanceId-1", eventCreatedOn, REMOTE_TAGRET_NODE));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
.isEqualTo(failedReplicationStatus(REMOTE_TAGRET_NODE, project, eventCreatedOn));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
value = REMOTE_TARGET_URL_TEMPLATE)
public void shouldBeOKSuccessForNonExistentRemoteName() throws Exception {
String nonExistingRemote = "non-existing-remote";
RestResponse result = adminRestSession.get(endpoint(project, nonExistingRemote));
result.assertOK();
assertThat(contentWithoutMagicJson(result)).isEqualTo(emptyReplicationStatus(project));
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId-1")
@GlobalPluginConfig(
pluginName = "replication",
name = "remote.some-remote-host.url",
values = {REMOTE_TARGET_URL_TEMPLATE, "ssh://some.remote.host2/git/${name}.git"})
public void shouldReturnSuccessfulProjectReplicationStatusForMultipeURLs() throws Exception {
long eventCreatedOn = System.currentTimeMillis();
eventHandler.onEvent(
successReplicatedEvent("testInstanceId-1", eventCreatedOn, REMOTE_TAGRET_NODE));
eventHandler.onEvent(
successReplicatedEvent("testInstanceId-1", eventCreatedOn, "some.remote.host2"));
RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
String resultJson = contentWithoutMagicJson(result);
Map<String, RemoteReplicationStatus> statuses =
ImmutableMap.of(
REMOTE_TAGRET_NODE,
RemoteReplicationStatus.create(
ImmutableMap.of(
REF_MASTER,
ReplicationStatus.create(
ReplicationStatus.ReplicationStatusResult.SUCCEEDED, eventCreatedOn))),
"some.remote.host2",
RemoteReplicationStatus.create(
ImmutableMap.of(
REF_MASTER,
ReplicationStatus.create(
ReplicationStatus.ReplicationStatusResult.SUCCEEDED, eventCreatedOn))));
assertThat(resultJson)
.isEqualTo(
projectReplicationStatus(
statuses, project, ProjectReplicationStatus.ProjectReplicationStatusResult.OK));
}
private String contentWithoutMagicJson(RestResponse response) throws IOException {
return response.getEntityContent().substring(RestApiServlet.JSON_MAGIC.length);
}
private AccountGroup.UUID createGroup(String name, TestAccount member) throws RestApiException {
GroupInput groupInput = new GroupInput();
groupInput.name = name(name);
groupInput.members = Collections.singletonList(String.valueOf(member.id().get()));
return AccountGroup.uuid(gApi.groups().create(groupInput).get().id);
}
private void makeProjectOwner(TestAccount user, Project.NameKey project) throws RestApiException {
projectOperations
.project(project)
.forUpdate()
.add(
allow(Permission.OWNER)
.ref(AccessSection.ALL)
.group(createGroup("projectOwners", user)))
.update();
}
private RefReplicatedEvent replicatedEvent(
@Nullable String instanceId,
long when,
String ref,
String remote,
RefPushResult status,
RemoteRefUpdate.Status refStatus) {
RefReplicatedEvent replicatedEvent =
new RefReplicatedEvent(project.get(), ref, remote, status, refStatus);
replicatedEvent.instanceId = instanceId;
replicatedEvent.eventCreatedOn = when;
return replicatedEvent;
}
private ReplicationScheduledEvent scheduledEvent(
@Nullable String instanceId, long when, String ref, String remote) {
ReplicationScheduledEvent scheduledEvent =
new ReplicationScheduledEvent(project.get(), ref, remote);
scheduledEvent.instanceId = instanceId;
scheduledEvent.eventCreatedOn = when;
return scheduledEvent;
}
private RefReplicatedEvent successReplicatedEvent(
@Nullable String instanceId, long when, String remoteUrl) {
return replicatedEvent(
instanceId,
when,
REF_MASTER,
remoteUrl,
RefPushResult.SUCCEEDED,
RemoteRefUpdate.Status.OK);
}
private RefReplicatedEvent failedReplicatedEvent(
@Nullable String instanceId, long when, String remoteUrl) {
return replicatedEvent(
instanceId,
when,
REF_MASTER,
remoteUrl,
RefPushResult.FAILED,
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD);
}
private static String endpoint(Project.NameKey project, String remote) {
return String.format(
"/projects/%s/remotes/%s/replication-status", project.get(), encode(remote));
}
private String emptyReplicationStatus(Project.NameKey project, String remoteUrl) {
return emptyReplicationStatus(
ImmutableMap.of(remoteUrl, RemoteReplicationStatus.create(Collections.emptyMap())),
project);
}
private String emptyReplicationStatus(Project.NameKey project) {
return emptyReplicationStatus(ImmutableMap.of(), project);
}
private String emptyReplicationStatus(
Map<String, RemoteReplicationStatus> statuses, Project.NameKey project) {
return gson.toJson(
ProjectReplicationStatus.create(
statuses, ProjectReplicationStatus.ProjectReplicationStatusResult.OK, project.get()));
}
private String successReplicationStatus(String remote, Project.NameKey project, long when)
throws URISyntaxException {
return successReplicationStatus(
remote, project, when, ReplicationStatus.ReplicationStatusResult.SUCCEEDED);
}
private String scheduledReplicationStatus(String remote, Project.NameKey project, long when)
throws URISyntaxException {
return successReplicationStatus(
remote, project, when, ReplicationStatus.ReplicationStatusResult.SCHEDULED);
}
private String successReplicationStatus(
String remote,
Project.NameKey project,
long when,
ReplicationStatus.ReplicationStatusResult replicationStatusResult)
throws URISyntaxException {
return projectReplicationStatus(
remote,
project,
when,
ProjectReplicationStatus.ProjectReplicationStatusResult.OK,
replicationStatusResult);
}
private String failedReplicationStatus(String remote, Project.NameKey project, long when)
throws URISyntaxException {
return projectReplicationStatus(
remote,
project,
when,
ProjectReplicationStatus.ProjectReplicationStatusResult.FAILED,
ReplicationStatus.ReplicationStatusResult.FAILED);
}
private String projectReplicationStatus(
String remoteUrl,
Project.NameKey project,
long when,
ProjectReplicationStatus.ProjectReplicationStatusResult projectReplicationStatusResult,
ReplicationStatus.ReplicationStatusResult replicationStatusResult) {
return projectReplicationStatus(
ImmutableMap.of(
remoteUrl,
RemoteReplicationStatus.create(
ImmutableMap.of(
REF_MASTER, ReplicationStatus.create(replicationStatusResult, when)))),
project,
projectReplicationStatusResult);
}
private String projectReplicationStatus(
Map<String, RemoteReplicationStatus> statuses,
Project.NameKey project,
ProjectReplicationStatus.ProjectReplicationStatusResult projectReplicationStatusResult) {
return gson.toJson(
ProjectReplicationStatus.create(statuses, projectReplicationStatusResult, project.get()));
}
}