Request by remote name instead of remote URL
The replication-status currently accepts a remote-url as a URL segment.
This can be tedious from a client point of view because the fully
qualified replication-url might not be known plus user cannot ask for
multiple urls which are logically grouped. To mitigate this situation
use remote name instead of remote url.
Bug: Issue 14803
Change-Id: Ibd6c2a943204cacc035b74fa34e34220417274c3
diff --git a/README.md b/README.md
index a5c70e0..c3966f0 100644
--- a/README.md
+++ b/README.md
@@ -9,19 +9,25 @@
The cache information is then exposed via a project's resource REST endpoint:
```bash
-curl -v --user <user> '<gerrit-server>/a/projects/<project-name>/remotes/<remote-url>/replication-status'
+curl -v --user <user> '<gerrit-server>/a/projects/<project-name>/remotes/<remote-name>/replication-status'
```
* <project-name>: an (url-encoded) project repository
-* <remote-url>: an (url-encoded) remote URL for the replication
+* <remote-name>: an (url-encoded) remote name for the replication
For instance, to assess the replication status of the project `some/project` to
the
-`https://github.com/some/project.git` URL, the following endpoint should be
-called:
+`https://github.com/some/project.git` URL with the replication configuration:
+
+```
+[remote "github-replication"]
+ url = https://github.com/${name}.git
+```
+
+the following endpoint should be called:
```bash
-curl -v --user <user> '<gerrit-server>/a/projects/some%2Fproject/remotes/https%3A%2F%2Fgithub.com%2Fsome%2Fproject.git/replication-status'
+curl -v --user <user> '<gerrit-server>/a/projects/some%2Fproject/remotes/github-replication/replication-status'
```
A payload, similar to this may be returned:
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ConfigParser.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ConfigParser.java
new file mode 100644
index 0000000..c9d7ab8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ConfigParser.java
@@ -0,0 +1,78 @@
+// 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.flogger.FluentLogger;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+class ConfigParser {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ List<RemoteURLConfiguration> parseRemotes(Config config) throws ConfigInvalidException {
+
+ if (config.getSections().isEmpty()) {
+ logger.atWarning().log("Replication config does not exist or it's empty");
+ return Collections.emptyList();
+ }
+
+ ImmutableList.Builder<RemoteURLConfiguration> confs = ImmutableList.builder();
+ for (RemoteConfig c : allRemotes(config)) {
+ if (c.getURIs().isEmpty()) {
+ continue;
+ }
+
+ RemoteURLConfiguration remoteURLConfiguration = new RemoteURLConfiguration(c, config);
+
+ if (!remoteURLConfiguration.isSingleProjectMatch()) {
+ for (URIish u : c.getURIs()) {
+ if (u.getPath() == null || !u.getPath().contains("${name}")) {
+ throw new ConfigInvalidException(
+ String.format(
+ "remote.%s.url \"%s\" lacks ${name} placeholder in %s",
+ c.getName(), u, config));
+ }
+ }
+ }
+
+ confs.add(remoteURLConfiguration);
+ }
+
+ return confs.build();
+ }
+
+ private static List<RemoteConfig> allRemotes(Config cfg) throws ConfigInvalidException {
+ Set<String> names = cfg.getSubsections("remote");
+ List<RemoteConfig> result = Lists.newArrayListWithCapacity(names.size());
+ for (String name : names) {
+ try {
+ result.add(new RemoteConfig(cfg, name));
+ } catch (URISyntaxException e) {
+ throw new ConfigInvalidException(
+ String.format("remote %s has invalid URL in %s", name, cfg), e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/Module.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/Module.java
index 9109e55..dff59cc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/Module.java
@@ -16,13 +16,23 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.events.EventListener;
+import com.google.inject.Inject;
class Module extends LifecycleModule {
+
+ private final SitePaths site;
+
+ @Inject
+ public Module(SitePaths site) {
+ this.site = site;
+ }
+
@Override
protected void configure() {
DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
- install(new ReplicationStatusApiModule());
+ install(new ReplicationStatusApiModule(site));
install(new ReplicationStatusCacheModule());
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/RemoteURLConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/RemoteURLConfiguration.java
new file mode 100644
index 0000000..de97d73
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/RemoteURLConfiguration.java
@@ -0,0 +1,66 @@
+// 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 com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.googlesource.gerrit.plugins.replication.ReplicationFilter;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.transport.RemoteConfig;
+
+class RemoteURLConfiguration {
+
+ private final ImmutableList<String> urls;
+ private final ImmutableList<String> projects;
+ private final String name;
+ private final String remoteNameStyle;
+
+ RemoteURLConfiguration(RemoteConfig remoteConfig, Config cfg) {
+ name = remoteConfig.getName();
+ urls = ImmutableList.copyOf(cfg.getStringList("remote", name, "url"));
+ projects = ImmutableList.copyOf(cfg.getStringList("remote", name, "projects"));
+ remoteNameStyle =
+ MoreObjects.firstNonNull(cfg.getString("remote", name, "remoteNameStyle"), "slash");
+ }
+
+ ImmutableList<String> getUrls() {
+ return urls;
+ }
+
+ boolean isSingleProjectMatch() {
+ boolean ret = (projects.size() == 1);
+ if (ret) {
+ String projectMatch = projects.get(0);
+ if (ReplicationFilter.getPatternType(projectMatch)
+ != ReplicationFilter.PatternType.EXACT_MATCH) {
+ // projectMatch is either regular expression, or wild-card.
+ //
+ // Even though they might refer to a single project now, they need not
+ // after new projects have been created. Hence, we do not treat them as
+ // matching a single project.
+ ret = false;
+ }
+ }
+ return ret;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ String getRemoteNameStyle() {
+ return remoteNameStyle;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusAction.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusAction.java
index 7691897..9fc5ada 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusAction.java
@@ -14,10 +14,15 @@
package com.googlesource.gerrit.plugins.replicationstatus;
+import static com.googlesource.gerrit.plugins.replication.PushResultProcessing.resolveNodeName;
+import static com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig.replaceName;
import static com.googlesource.gerrit.plugins.replicationstatus.ReplicationStatus.CACHE_NAME;
import com.google.common.cache.Cache;
+import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -30,26 +35,44 @@
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replicationstatus.ProjectReplicationStatus.ProjectReplicationStatusResult;
import java.io.IOException;
-import java.util.HashMap;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FilenameUtils;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.URIish;
class ReplicationStatusAction implements RestReadView<ReplicationStatusProjectRemoteResource> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
private final Cache<ReplicationStatus.Key, ReplicationStatus> replicationStatusCache;
+ private final List<RemoteURLConfiguration> remoteConfigurations;
@Inject
ReplicationStatusAction(
PermissionBackend permissionBackend,
GitRepositoryManager repoManager,
- @Named(CACHE_NAME) Cache<ReplicationStatus.Key, ReplicationStatus> replicationStatusCache) {
+ @Named(CACHE_NAME) Cache<ReplicationStatus.Key, ReplicationStatus> replicationStatusCache,
+ ReplicationConfig replicationConfig,
+ ConfigParser configParser)
+ throws ConfigInvalidException {
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.replicationStatusCache = replicationStatusCache;
+ this.remoteConfigurations = configParser.parseRemotes(replicationConfig.getConfig());
}
@Override
@@ -58,30 +81,23 @@
ResourceConflictException, IOException {
Project.NameKey projectNameKey = resource.getProjectNameKey();
- String remoteURL = resource.getRemoteUrl();
+ String remoteName = resource.getRemote();
+ Optional<RemoteURLConfiguration> remoteConfig =
+ remoteConfigurations.stream()
+ .filter(config -> config.getName().equals(remoteName))
+ .findFirst();
checkIsOwnerOrAdmin(projectNameKey);
- ProjectReplicationStatus.ProjectReplicationStatusResult overallStatus =
- ProjectReplicationStatus.ProjectReplicationStatusResult.OK;
- Map<String, RemoteReplicationStatus> remoteStatuses = new HashMap<>();
try (Repository git = repoManager.openRepository(projectNameKey)) {
+ List<Ref> refs = git.getRefDatabase().getRefs();
+ Map<String, RemoteReplicationStatus> remoteStatuses =
+ remoteConfig
+ .map(config -> getRemoteReplicationStatuses(config, projectNameKey, refs))
+ .orElse(Collections.emptyMap());
- Map<String, ReplicationStatus> refStatuses = new HashMap<>();
- for (Ref r : git.getRefDatabase().getRefs()) {
- ReplicationStatus replicationStatus =
- replicationStatusCache.getIfPresent(
- ReplicationStatus.Key.create(projectNameKey, remoteURL, r.getName()));
-
- if (replicationStatus != null) {
- refStatuses.put(r.getName(), replicationStatus);
- if (replicationStatus.isFailure()) {
- overallStatus = ProjectReplicationStatus.ProjectReplicationStatusResult.FAILED;
- }
- }
- }
- remoteStatuses.put(remoteURL, RemoteReplicationStatus.create(refStatuses));
-
+ ProjectReplicationStatus.ProjectReplicationStatusResult overallStatus =
+ getOverallStatus(remoteStatuses);
ProjectReplicationStatus projectStatus =
ProjectReplicationStatus.create(remoteStatuses, overallStatus, projectNameKey.get());
@@ -93,6 +109,106 @@
}
}
+ private Map<String, RemoteReplicationStatus> getRemoteReplicationStatuses(
+ RemoteURLConfiguration config, NameKey projectNameKey, List<Ref> refs) {
+ return config.getUrls().stream()
+ .map(url -> getTargetURL(projectNameKey, config, url))
+ .filter(Optional::isPresent)
+ .map(
+ targetUrl ->
+ Maps.immutableEntry(
+ targetUrl.get(),
+ RemoteReplicationStatus.create(
+ getRefStatuses(projectNameKey, refs, targetUrl.get()))))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private ProjectReplicationStatusResult getOverallStatus(
+ Map<String, RemoteReplicationStatus> remoteStatuses) {
+ return remoteStatuses.values().stream()
+ .flatMap(status -> status.status().values().stream())
+ .filter(ReplicationStatus::isFailure)
+ .findFirst()
+ .map(status -> ProjectReplicationStatus.ProjectReplicationStatusResult.FAILED)
+ .orElse(ProjectReplicationStatus.ProjectReplicationStatusResult.OK);
+ }
+
+ private Map<String, ReplicationStatus> getRefStatuses(
+ Project.NameKey projectNameKey, List<Ref> refs, String uri) {
+ return refs.stream()
+ .map(ref -> ReplicationStatus.Key.create(projectNameKey, uri, ref.getName()))
+ .map(
+ key ->
+ Maps.immutableEntry(
+ key.ref(), Optional.ofNullable(replicationStatusCache.getIfPresent(key))))
+ .filter(e -> e.getValue().isPresent())
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get()));
+ }
+
+ private Optional<String> getTargetURL(
+ Project.NameKey projectNameKey, RemoteURLConfiguration config, String url) {
+ try {
+ return Optional.of(resolveNodeName(getURI(config, new URIish(url), projectNameKey)));
+ } catch (URISyntaxException e) {
+ logger.atSevere().withCause(e).log(
+ "Cannot resolve target URI for template: %s and project name: %s", url, projectNameKey);
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * This method was copied from replication plugin where the method is in protected scope {@link
+ * com.googlesource.gerrit.plugins.replication.Destination#getURI(URIish, NameKey)}
+ */
+ @SuppressWarnings("javadoc")
+ private URIish getURI(RemoteURLConfiguration config, URIish template, Project.NameKey project) {
+ String name = project.get();
+ if (needsUrlEncoding(template)) {
+ name = encode(name);
+ }
+ String remoteNameStyle = config.getRemoteNameStyle();
+ if (remoteNameStyle.equals("dash")) {
+ name = name.replace("/", "-");
+ } else if (remoteNameStyle.equals("underscore")) {
+ name = name.replace("/", "_");
+ } else if (remoteNameStyle.equals("basenameOnly")) {
+ name = FilenameUtils.getBaseName(name);
+ } else if (!remoteNameStyle.equals("slash")) {
+ logger.atFine().log("Unknown remoteNameStyle: %s, falling back to slash", remoteNameStyle);
+ }
+ String replacedPath = replaceName(template.getPath(), name, config.isSingleProjectMatch());
+ return (replacedPath != null) ? template.setPath(replacedPath) : template;
+ }
+
+ /**
+ * This method was copied from replication plugin where the method is in protected scope {@link
+ * com.googlesource.gerrit.plugins.replication.Destination#needsUrlEncoding(URIish)}
+ */
+ @SuppressWarnings("javadoc")
+ private static boolean needsUrlEncoding(URIish uri) {
+ return "http".equalsIgnoreCase(uri.getScheme())
+ || "https".equalsIgnoreCase(uri.getScheme())
+ || "amazon-s3".equalsIgnoreCase(uri.getScheme());
+ }
+
+ /**
+ * This method was copied from replication plugin where the method is in protected scope {@link
+ * com.googlesource.gerrit.plugins.replication.Destination#encode(String)}
+ */
+ @SuppressWarnings("javadoc")
+ private static String encode(String str) {
+ try {
+ // Some cleanup is required. The '/' character is always encoded as %2F
+ // however remote servers will expect it to be not encoded as part of the
+ // path used to the repository. Space is incorrectly encoded as '+' for this
+ // context. In the path part of a URI space should be %20, but in form data
+ // space is '+'. Our cleanup replace fixes these two issues.
+ return URLEncoder.encode(str, "UTF-8").replaceAll("%2[fF]", "/").replace("+", "%20");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void checkIsOwnerOrAdmin(Project.NameKey project) throws AuthException {
if (!permissionBackend.currentUser().testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
&& !permissionBackend
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusApiModule.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusApiModule.java
index adecc6d..7674c5b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusApiModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusApiModule.java
@@ -19,9 +19,21 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Scopes;
+import com.googlesource.gerrit.plugins.replication.FanoutReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import java.nio.file.Files;
class ReplicationStatusApiModule extends RestApiModule {
+
+ private final SitePaths site;
+
+ public ReplicationStatusApiModule(SitePaths site) {
+ this.site = site;
+ }
+
@Override
protected void configure() {
bind(ReplicationStatusAction.class).in(Scopes.SINGLETON);
@@ -29,5 +41,15 @@
child(PROJECT_KIND, "remotes").to(ReplicationStatusProjectRemoteCollection.class);
get(REPLICATION_STATUS_PROJECT_REMOTE_KIND, "replication-status")
.to(ReplicationStatusAction.class);
+
+ bind(ConfigParser.class).in(Scopes.SINGLETON);
+ bind(ReplicationConfig.class).to(getReplicationConfigClass()).in(Scopes.SINGLETON);
+ }
+
+ private Class<? extends ReplicationConfig> getReplicationConfigClass() {
+ if (Files.exists(site.etc_dir.resolve("replication"))) {
+ return FanoutReplicationConfig.class;
+ }
+ return ReplicationFileBasedConfig.class;
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteCollection.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteCollection.java
index a8c0b0c..ad41189 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteCollection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteCollection.java
@@ -46,9 +46,9 @@
public ReplicationStatusProjectRemoteResource parse(ProjectResource parent, IdString id)
throws ResourceNotFoundException, Exception {
Project.NameKey projectNameKey = parent.getNameKey();
- String remoteURL = id.get();
+ String remoteName = id.get();
- return new ReplicationStatusProjectRemoteResource(projectNameKey, remoteURL, remoteURL);
+ return new ReplicationStatusProjectRemoteResource(projectNameKey, remoteName);
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteResource.java b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteResource.java
index 77b81cd..b7ba722 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteResource.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusProjectRemoteResource.java
@@ -26,13 +26,10 @@
private final Project.NameKey projectNameKey;
private final String remote;
- private final String remoteURL;
- public ReplicationStatusProjectRemoteResource(
- Project.NameKey projectNameKey, String remote, String remoteURL) {
+ public ReplicationStatusProjectRemoteResource(Project.NameKey projectNameKey, String remote) {
this.projectNameKey = projectNameKey;
this.remote = remote;
- this.remoteURL = remoteURL;
}
public Project.NameKey getProjectNameKey() {
@@ -42,8 +39,4 @@
public String getRemote() {
return remote;
}
-
- public String getRemoteUrl() {
- return remoteURL;
- }
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusIT.java b/src/test/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusIT.java
index 2049f53..109cc7c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replicationstatus/ReplicationStatusIT.java
@@ -24,7 +24,9 @@
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;
@@ -42,6 +44,7 @@
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;
@@ -50,9 +53,12 @@
@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 = "ssh://some.remote.host";
+ 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();
@@ -68,27 +74,41 @@
@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));
+ RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
- assertThat(contentWithoutMagicJson(result)).isEqualTo(emptyReplicationStatus(project, REMOTE));
+ 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));
+ RestResponse result = userRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
- assertThat(contentWithoutMagicJson(result)).isEqualTo(emptyReplicationStatus(project, REMOTE));
+ 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));
+ RestResponse result = userRestSession.get(endpoint(project, REMOTE_NAME));
result.assertForbidden();
assertThat(result.getEntityContent()).contains("Administrate Server or Project owner required");
@@ -96,8 +116,12 @@
@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));
+ RestResponse result = anonymousRestSession.get(endpoint(project, REMOTE_NAME));
result.assertForbidden();
assertThat(result.getEntityContent()).contains("Administrate Server or Project owner required");
@@ -105,87 +129,170 @@
@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));
+ successReplicatedEvent("testInstanceId-2", System.currentTimeMillis(), REMOTE_TAGRET_NODE));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
- assertThat(contentWithoutMagicJson(result)).isEqualTo(emptyReplicationStatus(project, REMOTE));
+ 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));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ 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, project, eventCreatedOn));
+ .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));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ 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, project, eventCreatedOn));
+ .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));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ eventHandler.onEvent(successReplicatedEvent(null, eventCreatedOn, REMOTE_TAGRET_NODE));
+ RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
- .isEqualTo(successReplicationStatus(REMOTE, project, eventCreatedOn));
+ .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));
+ successReplicatedEvent("testInstanceId-2", System.currentTimeMillis(), REMOTE_TAGRET_NODE));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
- assertThat(contentWithoutMagicJson(result)).isEqualTo(emptyReplicationStatus(project, REMOTE));
+ 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));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ eventHandler.onEvent(successReplicatedEvent(null, eventCreatedOn, REMOTE_TAGRET_NODE));
+ RestResponse result = adminRestSession.get(endpoint(project, REMOTE_NAME));
result.assertOK();
assertThat(contentWithoutMagicJson(result))
- .isEqualTo(successReplicationStatus(REMOTE, project, eventCreatedOn));
+ .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));
- RestResponse result = adminRestSession.get(endpoint(project, REMOTE));
+ 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, project, eventCreatedOn));
+ .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 {
@@ -264,13 +371,21 @@
"/projects/%s/remotes/%s/replication-status", project.get(), encode(remote));
}
- private String emptyReplicationStatus(Project.NameKey project, String remoteUrl)
- throws URISyntaxException {
+ 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(
- ImmutableMap.of(remoteUrl, RemoteReplicationStatus.create(Collections.emptyMap())),
- ProjectReplicationStatus.ProjectReplicationStatusResult.OK,
- project.get()));
+ statuses, ProjectReplicationStatus.ProjectReplicationStatusResult.OK, project.get()));
}
private String successReplicationStatus(String remote, Project.NameKey project, long when)
@@ -314,16 +429,22 @@
Project.NameKey project,
long when,
ProjectReplicationStatus.ProjectReplicationStatusResult projectReplicationStatusResult,
- ReplicationStatus.ReplicationStatusResult replicationStatusResult)
- throws URISyntaxException {
+ 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(
- ImmutableMap.of(
- remoteUrl,
- RemoteReplicationStatus.create(
- ImmutableMap.of(
- REF_MASTER, ReplicationStatus.create(replicationStatusResult, when)))),
- projectReplicationStatusResult,
- project.get()));
+ ProjectReplicationStatus.create(statuses, projectReplicationStatusResult, project.get()));
}
}