Merge branch 'stable-3.5' into stable-3.6
* stable-3.5:
Log stacktrace when replication fails
Change-Id: I51f1316cc242535cd8e79a2b35c2ee4f939b3ffc
diff --git a/example-setup/broker/Dockerfile b/example-setup/broker/Dockerfile
index 08eaba9..b79470c 100644
--- a/example-setup/broker/Dockerfile
+++ b/example-setup/broker/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.5.5-almalinux8
+FROM gerritcodereview/gerrit:3.6.3-almalinux8
USER root
@@ -12,7 +12,7 @@
# hence rename it with a 'z-' prefix because the Gerrit plugin loader starts the
# plugins in filename alphabetical order.
COPY --chown=gerrit:gerrit events-kafka.jar /var/gerrit/plugins/z-events-kafka.jar
-COPY --chown=gerrit:gerrit libevents-broker.jar /var/gerrit/lib/libevents-broker.jar
+COPY --chown=gerrit:gerrit events-broker.jar /var/gerrit/lib/events-broker.jar
COPY --chown=gerrit:gerrit entrypoint.sh /tmp/
COPY --chown=gerrit:gerrit configs/replication.config.template /var/gerrit/etc/
diff --git a/example-setup/http/Dockerfile b/example-setup/http/Dockerfile
index e9f8239..77fed72 100644
--- a/example-setup/http/Dockerfile
+++ b/example-setup/http/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.5.5-almalinux8
+FROM gerritcodereview/gerrit:3.6.3-almalinux8
USER root
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
index 1493c13..560f8ee 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
@@ -41,6 +41,7 @@
import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
import java.io.IOException;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -336,23 +337,33 @@
try {
long startedAt = context.getStartTime();
long delay = NANOSECONDS.toMillis(startedAt - createdAt);
- metrics.record(config.getName(), delay, retryCount);
git = gitManager.openRepository(projectName);
- runImpl();
- long elapsed = NANOSECONDS.toMillis(context.stop());
- Optional<Long> elapsedEnd2End =
- apiRequestMetrics
- .flatMap(metrics -> metrics.stop(config.getName()))
- .map(NANOSECONDS::toMillis);
- repLog.info(
- "[{}] {} replication from {} completed in {}ms, {}ms delay, {} retries{}",
- taskIdHex,
- replicationType,
- uri,
- elapsed,
- delay,
- retryCount,
- elapsedEnd2End.map(el -> String.format(", E2E %dms", el)).orElse(""));
+ List<RefSpec> fetchRefSpecs = runImpl();
+
+ if (fetchRefSpecs.isEmpty()) {
+ repLog.info(
+ "[{}] {} replication from {} finished but no refs were replicated, {}ms delay, {} retries",
+ taskIdHex,
+ replicationType,
+ uri,
+ delay,
+ retryCount);
+ } else {
+ metrics.record(config.getName(), delay, retryCount);
+ long elapsed = NANOSECONDS.toMillis(context.stop());
+ Optional<Long> elapsedEnd2End =
+ apiRequestMetrics
+ .flatMap(metrics -> metrics.stop(config.getName()))
+ .map(NANOSECONDS::toMillis);
+ repLog.info(
+ "[{}] Replication from {} completed in {}ms, {}ms delay, {} retries{}",
+ taskIdHex,
+ uri,
+ elapsed,
+ delay,
+ retryCount,
+ elapsedEnd2End.map(el -> String.format(", E2E %dms", el)).orElse(""));
+ }
} catch (RepositoryNotFoundException e) {
stateLog.error(
"["
@@ -428,7 +439,7 @@
repLog.info("[{}] Cannot replicate from {}. It was canceled while running", taskIdHex, uri, e);
}
- private void runImpl() throws IOException {
+ private List<RefSpec> runImpl() throws IOException {
Fetch fetch = fetchFactory.create(taskIdHex, uri, git);
List<RefSpec> fetchRefSpecs = getFetchRefSpecs();
@@ -445,7 +456,7 @@
delta.remove(inexistentRef);
if (delta.isEmpty()) {
repLog.warn("[{}] Empty replication task, skipping.", taskIdHex);
- return;
+ return Collections.emptyList();
}
runImpl();
@@ -453,6 +464,7 @@
notifyRefReplicatedIOException();
throw e;
}
+ return fetchRefSpecs;
}
public List<RefSpec> getFetchRefSpecs() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/OnStartStop.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/OnStartStop.java
index d8c4a8d..6457f8a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/OnStartStop.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/OnStartStop.java
@@ -18,6 +18,7 @@
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
+import com.google.gerrit.server.config.GerritIsReplica;
import com.google.gerrit.server.events.EventDispatcher;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
@@ -36,6 +37,7 @@
private final ReplicationState.Factory replicationStateFactory;
private final SourcesCollection sourcesCollection;
private final WorkQueue workQueue;
+ private boolean isReplica;
@Inject
protected OnStartStop(
@@ -45,7 +47,8 @@
DynamicItem<EventDispatcher> eventDispatcher,
ReplicationState.Factory replicationStateFactory,
SourcesCollection sourcesCollection,
- WorkQueue workQueue) {
+ WorkQueue workQueue,
+ @GerritIsReplica Boolean isReplica) {
this.srvInfo = srvInfo;
this.fetchAll = fetchAll;
this.config = config;
@@ -54,11 +57,13 @@
this.fetchAllFuture = Atomics.newReference();
this.sourcesCollection = sourcesCollection;
this.workQueue = workQueue;
+ this.isReplica = isReplica;
}
@Override
public void start() {
- if (srvInfo.getState() == ServerInformation.State.STARTUP
+ if (isReplica
+ && srvInfo.getState() == ServerInformation.State.STARTUP
&& config.isReplicateAllOnPluginStart()) {
ReplicationState state =
replicationStateFactory.create(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
index 72a6b74..005d383 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
@@ -15,6 +15,7 @@
package com.googlesource.gerrit.plugins.replication.pull;
import static com.googlesource.gerrit.plugins.replication.StartReplicationCapability.START_REPLICATION;
+import static com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability.CALL_FETCH_ACTION;
import com.google.common.eventbus.EventBus;
import com.google.gerrit.extensions.annotations.Exports;
@@ -44,8 +45,8 @@
import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
import com.googlesource.gerrit.plugins.replication.StartReplicationCapability;
+import com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
-import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiModule;
import com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupModule;
import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
@@ -82,13 +83,16 @@
.annotatedWith(Names.named(ReplicationQueueMetrics.REPLICATION_QUEUE_METRICS))
.toInstance(pluginMetricMaker);
+ bind(CapabilityDefinition.class)
+ .annotatedWith(Exports.named(CALL_FETCH_ACTION))
+ .to(FetchApiCapability.class);
+
install(new PullReplicationGroupModule());
bind(BearerTokenProvider.class).in(Scopes.SINGLETON);
bind(RevisionReader.class).in(Scopes.SINGLETON);
bind(ApplyObject.class);
install(new FactoryModuleBuilder().build(FetchJob.Factory.class));
install(new ApplyObjectCacheModule());
- install(new PullReplicationApiModule());
install(new FetchRefReplicatedEventModule());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index 6bafaa4..ef95596 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -181,12 +181,7 @@
event.getRefName(),
event.refUpdate.get().oldRev,
event.refUpdate.get().newRev);
- fire(
- event.refUpdate.get().project,
- ObjectId.fromString(event.refUpdate.get().newRev),
- event.getRefName(),
- event.eventCreatedOn,
- ZEROS_OBJECTID.equals(event.refUpdate.get().newRev));
+ fire(ReferenceUpdatedEvent.from(event));
}
}
}
@@ -215,42 +210,45 @@
return !refsFilter.match(refName);
}
- private void fire(
- String projectName,
- ObjectId objectId,
- String refName,
- long eventCreatedOn,
- boolean isDelete) {
+ private void fire(ReferenceUpdatedEvent event) {
ReplicationState state = new ReplicationState(new GitUpdateProcessing(dispatcher.get()));
- fire(Project.nameKey(projectName), objectId, refName, eventCreatedOn, isDelete, state);
+ fire(event, state);
state.markAllFetchTasksScheduled();
}
- private void fire(
- NameKey project,
- ObjectId objectId,
- String refName,
- long eventCreatedOn,
- boolean isDelete,
- ReplicationState state) {
+ private void fire(ReferenceUpdatedEvent event, ReplicationState state) {
if (!running) {
stateLog.warn(
- "Replication plugin did not finish startup before event, event replication is postponed",
+ String.format(
+ "Replication plugin did not finish startup before event, event replication is postponed"
+ + " for event %s",
+ event),
state);
- beforeStartupEventsQueue.add(
- ReferenceUpdatedEvent.create(project.get(), refName, objectId, eventCreatedOn, isDelete));
+ beforeStartupEventsQueue.add(event);
queueMetrics.incrementQueuedBeforStartup();
return;
}
ForkJoinPool fetchCallsPool = null;
try {
- fetchCallsPool = new ForkJoinPool(sources.get().getAll().size());
+ List<Source> allSources = sources.get().getAll();
+ int numSources = allSources.size();
+ if (numSources == 0) {
+ repLog.debug("No replication sources configured -> skipping fetch");
+ return;
+ }
+ fetchCallsPool = new ForkJoinPool(numSources);
final Consumer<Source> callFunction =
- callFunction(project, objectId, refName, eventCreatedOn, isDelete, state);
+ callFunction(
+ Project.nameKey(event.projectName()),
+ event.objectId(),
+ event.refName(),
+ event.eventCreatedOn(),
+ event.isDelete(),
+ state);
fetchCallsPool
- .submit(() -> sources.get().getAll().parallelStream().forEach(callFunction))
+ .submit(() -> allSources.parallelStream().forEach(callFunction))
.get(fetchCallsTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
stateLog.error(
@@ -523,7 +521,20 @@
private HttpResult initProject(
Project.NameKey project, URIish uri, FetchApiClient fetchClient, HttpResult result)
throws IOException, ClientProtocolException {
- HttpResult initProjectResult = fetchClient.initProject(project, uri);
+ RevisionData refsMetaConfigRevisionData =
+ revReaderProvider
+ .get()
+ .read(project, null, RefNames.REFS_CONFIG, 0)
+ .orElseThrow(
+ () ->
+ new IllegalStateException(
+ String.format(
+ "Project %s does not have %s", project, RefNames.REFS_CONFIG)));
+
+ List<RevisionData> refsMetaConfigDataList =
+ fetchWholeMetaHistory(project, RefNames.REFS_CONFIG, refsMetaConfigRevisionData);
+ HttpResult initProjectResult =
+ fetchClient.initProject(project, uri, System.currentTimeMillis(), refsMetaConfigDataList);
if (initProjectResult.isSuccessful()) {
result = fetchClient.callFetch(project, FetchOne.ALL_REFS, uri);
} else {
@@ -535,16 +546,12 @@
private void fireBeforeStartupEvents() {
Set<String> eventsReplayed = new HashSet<>();
- for (ReferenceUpdatedEvent event : beforeStartupEventsQueue) {
+ ReferenceUpdatedEvent event;
+ while ((event = beforeStartupEventsQueue.poll()) != null) {
String eventKey = String.format("%s:%s", event.projectName(), event.refName());
if (!eventsReplayed.contains(eventKey)) {
repLog.info("Firing pending task {}", event);
- fire(
- event.projectName(),
- event.objectId(),
- event.refName(),
- event.eventCreatedOn(),
- event.isDelete());
+ fire(event);
eventsReplayed.add(eventKey);
}
}
@@ -578,6 +585,15 @@
projectName, refName, objectId, eventCreatedOn, isDelete);
}
+ static ReferenceUpdatedEvent from(RefUpdatedEvent event) {
+ return ReferenceUpdatedEvent.create(
+ event.refUpdate.get().project,
+ event.getRefName(),
+ ObjectId.fromString(event.refUpdate.get().newRev),
+ event.eventCreatedOn,
+ ZEROS_OBJECTID.equals(event.refUpdate.get().newRev));
+ }
+
public abstract String projectName();
public abstract String refName();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfigParser.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfigParser.java
index a8799c2..d7ae063 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfigParser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfigParser.java
@@ -17,6 +17,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.config.GerritIsReplica;
+import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.ConfigParser;
import com.googlesource.gerrit.plugins.replication.RemoteConfiguration;
import java.net.URISyntaxException;
@@ -32,6 +34,13 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private boolean isReplica;
+
+ @Inject
+ SourceConfigParser(@GerritIsReplica Boolean isReplica) {
+ this.isReplica = isReplica;
+ }
+
/* (non-Javadoc)
* @see com.googlesource.gerrit.plugins.replication.ConfigParser#parseRemotes(org.eclipse.jgit.lib.Config)
*/
@@ -45,7 +54,7 @@
ImmutableList.Builder<RemoteConfiguration> sourceConfigs = ImmutableList.builder();
for (RemoteConfig c : allFetchRemotes(config)) {
- if (c.getURIs().isEmpty()) {
+ if (isReplica && c.getURIs().isEmpty()) {
continue;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
index 381e560..01d32fb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingLatestPatchSetException;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingParentObjectException;
@@ -35,6 +36,7 @@
import java.util.Objects;
import javax.servlet.http.HttpServletResponse;
+@Singleton
public class ApplyObjectAction implements RestModifyView<ProjectResource, RevisionInput> {
private final ApplyObjectCommand applyObjectCommand;
@@ -67,8 +69,8 @@
try {
repLog.info(
"Apply object API from {} for {}:{} - {}",
- resource.getNameKey(),
input.getLabel(),
+ resource.getNameKey(),
input.getRefName(),
input.getRevisionData());
@@ -76,8 +78,8 @@
deleteRefCommand.deleteRef(resource.getNameKey(), input.getRefName(), input.getLabel());
repLog.info(
"Apply object API - REF DELETED - from {} for {}:{} - {}",
- resource.getNameKey(),
input.getLabel(),
+ resource.getNameKey(),
input.getRefName(),
input.getRevisionData());
return Response.withStatusCode(HttpServletResponse.SC_NO_CONTENT, "");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
index c4c4e83..1088e40 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
@@ -68,8 +68,8 @@
repLog.info(
"Apply object API from {} for {}:{} - {}",
- resource.getNameKey(),
input.getLabel(),
+ resource.getNameKey(),
input.getRefName(),
Arrays.toString(input.getRevisionsData()));
@@ -77,8 +77,8 @@
deleteRefCommand.deleteRef(resource.getNameKey(), input.getRefName(), input.getLabel());
repLog.info(
"Apply object API - REF DELETED - from {} for {}:{}",
- resource.getNameKey(),
input.getLabel(),
+ resource.getNameKey(),
input.getRefName());
return Response.withStatusCode(HttpServletResponse.SC_NO_CONTENT, "");
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
index f897012..e49c8b6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
@@ -35,7 +35,6 @@
import com.googlesource.gerrit.plugins.replication.pull.ReplicationState;
import com.googlesource.gerrit.plugins.replication.pull.Source;
import com.googlesource.gerrit.plugins.replication.pull.SourcesCollection;
-import com.googlesource.gerrit.plugins.replication.pull.fetch.ApplyObject;
import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
import java.io.IOException;
import java.util.Optional;
@@ -49,7 +48,6 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PullReplicationStateLogger fetchStateLog;
- private final ApplyObject applyObject;
private final DynamicItem<EventDispatcher> eventDispatcher;
private final ProjectCache projectCache;
private final SourcesCollection sourcesCollection;
@@ -61,13 +59,11 @@
PullReplicationStateLogger fetchStateLog,
ProjectCache projectCache,
SourcesCollection sourcesCollection,
- ApplyObject applyObject,
PermissionBackend permissionBackend,
DynamicItem<EventDispatcher> eventDispatcher,
LocalGitRepositoryManagerProvider gitManagerProvider) {
this.fetchStateLog = fetchStateLog;
this.projectCache = projectCache;
- this.applyObject = applyObject;
this.eventDispatcher = eventDispatcher;
this.sourcesCollection = sourcesCollection;
this.permissionBackend = permissionBackend;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
index 04797bd..9e69f8d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.ioutil.HexFormat;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction.Input;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob.Factory;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.RemoteConfigurationMissingException;
@@ -38,6 +39,7 @@
import java.util.concurrent.TimeoutException;
import org.eclipse.jgit.errors.TransportException;
+@Singleton
public class FetchAction implements RestModifyView<ProjectResource, Input> {
private final FetchCommand command;
private final WorkQueue workQueue;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java
index 73a4ac5..27afcfd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java
@@ -17,7 +17,7 @@
import com.google.gerrit.extensions.config.CapabilityDefinition;
public class FetchApiCapability extends CapabilityDefinition {
- static final String CALL_FETCH_ACTION = "callFetchAction";
+ public static final String CALL_FETCH_ACTION = "callFetchAction";
@Override
public String getDescription() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommand.java
index b8249ae..5323425 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommand.java
@@ -97,7 +97,12 @@
if (fetchType == ReplicationType.ASYNC) {
state.markAllFetchTasksScheduled();
Future<?> future = source.get().schedule(name, refName, state, apiRequestMetrics);
- future.get(source.get().getTimeout(), TimeUnit.SECONDS);
+ int timeout = source.get().getTimeout();
+ if (timeout == 0) {
+ future.get();
+ } else {
+ future.get(timeout, TimeUnit.SECONDS);
+ }
} else {
Optional<FetchOne> maybeFetch =
source
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java
index 77d0e0b..7ca8805 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java
@@ -26,8 +26,10 @@
import com.google.gerrit.server.permissions.RefPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.UnauthorizedAuthException;
+@Singleton
public class FetchPreconditions {
private final String pluginName;
private final PermissionBackend permissionBackend;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
index 0f3e1e8..95082b8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
@@ -16,7 +16,6 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.httpd.AllRequestFilter;
-import com.google.gerrit.server.config.GerritIsReplica;
import com.google.inject.Inject;
import com.google.inject.Scopes;
import com.google.inject.name.Names;
@@ -24,12 +23,10 @@
import com.googlesource.gerrit.plugins.replication.pull.BearerTokenProvider;
public class HttpModule extends ServletModule {
- private boolean isReplica;
private final BearerTokenProvider bearerTokenProvider;
@Inject
- public HttpModule(@GerritIsReplica Boolean isReplica, BearerTokenProvider bearerTokenProvider) {
- this.isReplica = isReplica;
+ public HttpModule(BearerTokenProvider bearerTokenProvider) {
this.bearerTokenProvider = bearerTokenProvider;
}
@@ -49,12 +46,8 @@
.in(Scopes.SINGLETON);
});
- if (isReplica) {
- DynamicSet.bind(binder(), AllRequestFilter.class)
- .to(PullReplicationFilter.class)
- .in(Scopes.SINGLETON);
- } else {
- serveRegex("/init-project/.*$").with(ProjectInitializationAction.class);
- }
+ DynamicSet.bind(binder(), AllRequestFilter.class)
+ .to(PullReplicationFilter.class)
+ .in(Scopes.SINGLETON);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
index 2e1c5d4..adb333c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
@@ -27,11 +27,13 @@
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.replication.LocalFS;
import com.googlesource.gerrit.plugins.replication.pull.GerritConfigOps;
import java.util.Optional;
import org.eclipse.jgit.transport.URIish;
+@Singleton
class ProjectDeletionAction
implements RestModifyView<ProjectResource, ProjectDeletionAction.DeleteInput> {
private static final PluginPermission DELETE_PROJECT =
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index 8711379..4ed3160 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -14,25 +14,40 @@
package com.googlesource.gerrit.plugins.replication.pull.api;
+import static com.googlesource.gerrit.plugins.replication.pull.PullReplicationLogger.repLog;
import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.checkAcceptHeader;
import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.setResponse;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.common.net.MediaType;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.replication.LocalFS;
import com.googlesource.gerrit.plugins.replication.pull.GerritConfigOps;
+import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionsInput;
+import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingLatestPatchSetException;
+import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingParentObjectException;
+import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException;
+import com.googlesource.gerrit.plugins.replication.pull.api.util.PayloadSerDes;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -51,17 +66,23 @@
private final Provider<CurrentUser> userProvider;
private final PermissionBackend permissionBackend;
private final ProjectIndexer projectIndexer;
+ private final ApplyObjectCommand applyObjectCommand;
+ private final ProjectCache projectCache;
@Inject
ProjectInitializationAction(
GerritConfigOps gerritConfigOps,
Provider<CurrentUser> userProvider,
PermissionBackend permissionBackend,
- ProjectIndexer projectIndexer) {
+ ProjectIndexer projectIndexer,
+ ApplyObjectCommand applyObjectCommand,
+ ProjectCache projectCache) {
this.gerritConfigOps = gerritConfigOps;
this.userProvider = userProvider;
this.permissionBackend = permissionBackend;
this.projectIndexer = projectIndexer;
+ this.applyObjectCommand = applyObjectCommand;
+ this.projectCache = projectCache;
}
@Override
@@ -73,47 +94,151 @@
return;
}
- String path = httpServletRequest.getRequestURI();
- String projectName = Url.decode(path.substring(path.lastIndexOf('/') + 1));
+ String gitRepositoryName = getGitRepositoryName(httpServletRequest);
try {
- if (initProject(projectName)) {
+ boolean initProjectStatus;
+ String contentType = httpServletRequest.getContentType();
+ if (checkContentType(contentType, MediaType.JSON_UTF_8)) {
+ // init project request includes project configuration in JSON format.
+ initProjectStatus = initProjectWithConfiguration(httpServletRequest, gitRepositoryName);
+ } else if (checkContentType(contentType, MediaType.PLAIN_TEXT_UTF_8)) {
+ // init project request does not include project configuration.
+ initProjectStatus = initProject(gitRepositoryName);
+ } else {
+ setResponse(
+ httpServletResponse,
+ SC_BAD_REQUEST,
+ String.format(
+ "Invalid Content Type. Only %s or %s is supported.",
+ MediaType.JSON_UTF_8.toString(), MediaType.PLAIN_TEXT_UTF_8.toString()));
+ return;
+ }
+
+ if (initProjectStatus) {
setResponse(
httpServletResponse,
HttpServletResponse.SC_CREATED,
- "Project " + projectName + " initialized");
+ "Project " + gitRepositoryName + " initialized");
return;
}
- } catch (AuthException | PermissionBackendException e) {
+
setResponse(
httpServletResponse,
- HttpServletResponse.SC_FORBIDDEN,
- "User not authorized to create project " + projectName);
- return;
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Cannot initialize project " + gitRepositoryName);
+ } catch (BadRequestException | IllegalArgumentException e) {
+ logExceptionAndUpdateResponse(httpServletResponse, e, SC_BAD_REQUEST, gitRepositoryName);
+ } catch (RefUpdateException | MissingParentObjectException e) {
+ logExceptionAndUpdateResponse(httpServletResponse, e, SC_CONFLICT, gitRepositoryName);
+ } catch (AuthException | PermissionBackendException e) {
+ logExceptionAndUpdateResponse(httpServletResponse, e, SC_FORBIDDEN, gitRepositoryName);
}
-
- setResponse(
- httpServletResponse,
- HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
- "Cannot initialize project " + projectName);
}
- public boolean initProject(String projectName) throws AuthException, PermissionBackendException {
+ public boolean initProject(String gitRepositoryName)
+ throws AuthException, PermissionBackendException {
+ if (initProject(gitRepositoryName, true)) {
+ repLog.info("Init project API from {}", gitRepositoryName);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean initProjectWithConfiguration(
+ HttpServletRequest httpServletRequest, String gitRepositoryName)
+ throws AuthException, PermissionBackendException, IOException, BadRequestException,
+ MissingParentObjectException, RefUpdateException {
+
+ RevisionsInput input = PayloadSerDes.parseRevisionsInput(httpServletRequest);
+ validateInput(input);
+ if (!initProject(gitRepositoryName, false)) {
+ return false;
+ }
+
+ String projectName = gitRepositoryName.replace(".git", "");
+ try {
+ applyObjectCommand.applyObjects(
+ Project.nameKey(projectName),
+ input.getRefName(),
+ input.getRevisionsData(),
+ input.getLabel(),
+ input.getEventCreatedOn());
+ } catch (MissingLatestPatchSetException e) {
+ repLog.error(
+ "Init project API FAILED from {} for {} - configuration data cannot contain change meta refs: {}:{}",
+ input.getLabel(),
+ projectName,
+ input.getRefName(),
+ Arrays.toString(input.getRevisionsData()),
+ e);
+ throw new BadRequestException("Configuration data cannot contain change meta refs", e);
+ }
+ projectCache.onCreateProject(Project.nameKey(projectName));
+ repLog.info(
+ "Init project API from {} for {}:{} - {}",
+ input.getLabel(),
+ projectName,
+ input.getRefName(),
+ Arrays.toString(input.getRevisionsData()));
+ return true;
+ }
+
+ private boolean initProject(String gitRepositoryName, boolean needsProjectReindexing)
+ throws AuthException, PermissionBackendException {
// When triggered internally(for example by consuming stream events) user is not provided
// and internal user is returned. Project creation should be always allowed for internal user.
if (!userProvider.get().isInternalUser()) {
permissionBackend.user(userProvider.get()).check(GlobalPermission.CREATE_PROJECT);
}
- Optional<URIish> maybeUri = gerritConfigOps.getGitRepositoryURI(projectName);
+ Optional<URIish> maybeUri = gerritConfigOps.getGitRepositoryURI(gitRepositoryName);
if (!maybeUri.isPresent()) {
- logger.atSevere().log("Cannot initialize project '%s'", projectName);
+ logger.atSevere().log("Cannot initialize project '%s'", gitRepositoryName);
return false;
}
LocalFS localFS = new LocalFS(maybeUri.get());
- Project.NameKey projectNameKey = Project.NameKey.parse(projectName);
+ Project.NameKey projectNameKey = Project.NameKey.parse(gitRepositoryName);
if (localFS.createProject(projectNameKey, RefNames.HEAD)) {
- projectIndexer.index(projectNameKey);
+ if (needsProjectReindexing) {
+ projectIndexer.index(projectNameKey);
+ }
return true;
}
return false;
}
+
+ private void validateInput(RevisionsInput input) {
+
+ if (Strings.isNullOrEmpty(input.getLabel())) {
+ throw new IllegalArgumentException("Source label cannot be null or empty");
+ }
+
+ if (!Objects.equals(input.getRefName(), RefNames.REFS_CONFIG)) {
+ throw new IllegalArgumentException(
+ String.format("Ref-update refname should be %s", RefNames.REFS_CONFIG));
+ }
+ input.validate();
+ }
+
+ private String getGitRepositoryName(HttpServletRequest httpServletRequest) {
+ String path = httpServletRequest.getRequestURI();
+ return Url.decode(path.substring(path.lastIndexOf('/') + 1));
+ }
+
+ private void logExceptionAndUpdateResponse(
+ HttpServletResponse httpServletResponse,
+ Exception e,
+ int statusCode,
+ String gitRepositoryName)
+ throws IOException {
+ repLog.error("Init Project API FAILED for {}", gitRepositoryName, e);
+ setResponse(httpServletResponse, statusCode, e.getMessage());
+ }
+
+ private boolean checkContentType(String contentType, MediaType mediaType) {
+ try {
+ return MediaType.parse(contentType).is(mediaType);
+ } catch (Exception e) {
+ return false;
+ }
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java
deleted file mode 100644
index d1d28a6..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2020 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.pull.api;
-
-import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
-import static com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability.CALL_FETCH_ACTION;
-
-import com.google.gerrit.extensions.annotations.Exports;
-import com.google.gerrit.extensions.config.CapabilityDefinition;
-import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.inject.Scopes;
-
-public class PullReplicationApiModule extends RestApiModule {
- @Override
- protected void configure() {
- bind(FetchAction.class).in(Scopes.SINGLETON);
- bind(ApplyObjectAction.class).in(Scopes.SINGLETON);
- bind(ProjectDeletionAction.class).in(Scopes.SINGLETON);
- bind(UpdateHeadAction.class).in(Scopes.SINGLETON);
- post(PROJECT_KIND, "fetch").to(FetchAction.class);
- post(PROJECT_KIND, "apply-object").to(ApplyObjectAction.class);
- post(PROJECT_KIND, "apply-objects").to(ApplyObjectsAction.class);
- delete(PROJECT_KIND, "delete-project").to(ProjectDeletionAction.class);
- put(PROJECT_KIND, "HEAD").to(UpdateHeadAction.class);
-
- bind(FetchPreconditions.class).in(Scopes.SINGLETON);
- bind(CapabilityDefinition.class)
- .annotatedWith(Exports.named(CALL_FETCH_ACTION))
- .to(FetchApiCapability.class);
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java
index e54d408..d0e5c10 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java
@@ -16,16 +16,14 @@
import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_UNPROCESSABLE_ENTITY;
import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.checkAcceptHeader;
-import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.setResponse;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.api.projects.HeadInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -35,29 +33,25 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.httpd.AllRequestFilter;
import com.google.gerrit.httpd.restapi.RestApiServlet;
-import com.google.gerrit.json.OutputFormat;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
-import com.google.gerrit.server.restapi.project.ProjectsCollection;
-import com.google.gson.Gson;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gson.JsonParseException;
-import com.google.gson.stream.JsonReader;
import com.google.gson.stream.MalformedJsonException;
import com.google.inject.Inject;
-import com.google.inject.TypeLiteral;
+import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction.Input;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionsInput;
-import com.googlesource.gerrit.plugins.replication.pull.api.exception.InitProjectException;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.UnauthorizedAuthException;
-import java.io.BufferedReader;
-import java.io.EOFException;
+import com.googlesource.gerrit.plugins.replication.pull.api.util.PayloadSerDes;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -83,9 +77,9 @@
private ProjectInitializationAction projectInitializationAction;
private UpdateHeadAction updateHEADAction;
private ProjectDeletionAction projectDeletionAction;
- private ProjectsCollection projectsCollection;
- private Gson gson;
+ private ProjectCache projectCache;
private String pluginName;
+ private final Provider<CurrentUser> currentUserProvider;
@Inject
public PullReplicationFilter(
@@ -95,17 +89,18 @@
ProjectInitializationAction projectInitializationAction,
UpdateHeadAction updateHEADAction,
ProjectDeletionAction projectDeletionAction,
- ProjectsCollection projectsCollection,
- @PluginName String pluginName) {
+ ProjectCache projectCache,
+ @PluginName String pluginName,
+ Provider<CurrentUser> currentUserProvider) {
this.fetchAction = fetchAction;
this.applyObjectAction = applyObjectAction;
this.applyObjectsAction = applyObjectsAction;
this.projectInitializationAction = projectInitializationAction;
this.updateHEADAction = updateHEADAction;
this.projectDeletionAction = projectDeletionAction;
- this.projectsCollection = projectsCollection;
+ this.projectCache = projectCache;
this.pluginName = pluginName;
- this.gson = OutputFormat.JSON.newGsonBuilder().create();
+ this.currentUserProvider = currentUserProvider;
}
@Override
@@ -120,20 +115,26 @@
HttpServletRequest httpRequest = (HttpServletRequest) request;
try {
if (isFetchAction(httpRequest)) {
- writeResponse(httpResponse, doFetch(httpRequest));
+ failIfcurrentUserIsAnonymous();
+ PayloadSerDes.writeResponse(httpResponse, doFetch(httpRequest));
} else if (isApplyObjectAction(httpRequest)) {
- writeResponse(httpResponse, doApplyObject(httpRequest));
+ failIfcurrentUserIsAnonymous();
+ PayloadSerDes.writeResponse(httpResponse, doApplyObject(httpRequest));
} else if (isApplyObjectsAction(httpRequest)) {
- writeResponse(httpResponse, doApplyObjects(httpRequest));
+ failIfcurrentUserIsAnonymous();
+ PayloadSerDes.writeResponse(httpResponse, doApplyObjects(httpRequest));
} else if (isInitProjectAction(httpRequest)) {
+ failIfcurrentUserIsAnonymous();
if (!checkAcceptHeader(httpRequest, httpResponse)) {
return;
}
doInitProject(httpRequest, httpResponse);
} else if (isUpdateHEADAction(httpRequest)) {
- writeResponse(httpResponse, doUpdateHEAD(httpRequest));
+ failIfcurrentUserIsAnonymous();
+ PayloadSerDes.writeResponse(httpResponse, doUpdateHEAD(httpRequest));
} else if (isDeleteProjectAction(httpRequest)) {
- writeResponse(httpResponse, doDeleteProject(httpRequest));
+ failIfcurrentUserIsAnonymous();
+ PayloadSerDes.writeResponse(httpResponse, doDeleteProject(httpRequest));
} else {
chain.doFilter(request, response);
}
@@ -156,7 +157,7 @@
} catch (ResourceConflictException e) {
RestApiServlet.replyError(
httpRequest, httpResponse, SC_CONFLICT, e.getMessage(), e.caching(), e);
- } catch (InitProjectException | ResourceNotFoundException e) {
+ } catch (ResourceNotFoundException e) {
RestApiServlet.replyError(
httpRequest, httpResponse, SC_INTERNAL_SERVER_ERROR, e.getMessage(), e.caching(), e);
} catch (NoSuchElementException e) {
@@ -173,108 +174,67 @@
}
}
- private void doInitProject(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
- throws RestApiException, IOException, PermissionBackendException {
-
- IdString id = getInitProjectName(httpRequest).get();
- String projectName = id.get();
- if (projectInitializationAction.initProject(projectName)) {
- setResponse(
- httpResponse, HttpServletResponse.SC_CREATED, "Project " + projectName + " initialized");
- return;
+ private void failIfcurrentUserIsAnonymous() throws UnauthorizedAuthException {
+ CurrentUser currentUser = currentUserProvider.get();
+ if (currentUser instanceof AnonymousUser) {
+ throw new UnauthorizedAuthException();
}
- throw new InitProjectException(projectName);
+ }
+
+ private void doInitProject(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ throws IOException, ServletException {
+ projectInitializationAction.service(httpRequest, httpResponse);
}
@SuppressWarnings("unchecked")
private Response<String> doApplyObject(HttpServletRequest httpRequest)
throws RestApiException, IOException, PermissionBackendException {
- RevisionInput input = readJson(httpRequest, TypeLiteral.get(RevisionInput.class));
+ RevisionInput input = PayloadSerDes.parseRevisionInput(httpRequest);
IdString id = getProjectName(httpRequest).get();
- ProjectResource projectResource = projectsCollection.parse(TopLevelResource.INSTANCE, id);
- return (Response<String>) applyObjectAction.apply(projectResource, input);
+ return (Response<String>) applyObjectAction.apply(parseProjectResource(id), input);
}
@SuppressWarnings("unchecked")
private Response<String> doApplyObjects(HttpServletRequest httpRequest)
throws RestApiException, IOException, PermissionBackendException {
- RevisionsInput input = readJson(httpRequest, TypeLiteral.get(RevisionsInput.class));
+ RevisionsInput input = PayloadSerDes.parseRevisionsInput(httpRequest);
IdString id = getProjectName(httpRequest).get();
- ProjectResource projectResource = projectsCollection.parse(TopLevelResource.INSTANCE, id);
- return (Response<String>) applyObjectsAction.apply(projectResource, input);
+ return (Response<String>) applyObjectsAction.apply(parseProjectResource(id), input);
}
@SuppressWarnings("unchecked")
private Response<String> doUpdateHEAD(HttpServletRequest httpRequest) throws Exception {
- HeadInput input = readJson(httpRequest, TypeLiteral.get(HeadInput.class));
+ HeadInput input = PayloadSerDes.parseHeadInput(httpRequest);
IdString id = getProjectName(httpRequest).get();
- ProjectResource projectResource = projectsCollection.parse(TopLevelResource.INSTANCE, id);
- return (Response<String>) updateHEADAction.apply(projectResource, input);
+ return (Response<String>) updateHEADAction.apply(parseProjectResource(id), input);
}
@SuppressWarnings("unchecked")
private Response<String> doDeleteProject(HttpServletRequest httpRequest) throws Exception {
IdString id = getProjectName(httpRequest).get();
- ProjectResource projectResource = projectsCollection.parse(TopLevelResource.INSTANCE, id);
return (Response<String>)
- projectDeletionAction.apply(projectResource, new ProjectDeletionAction.DeleteInput());
+ projectDeletionAction.apply(
+ parseProjectResource(id), new ProjectDeletionAction.DeleteInput());
}
@SuppressWarnings("unchecked")
private Response<Map<String, Object>> doFetch(HttpServletRequest httpRequest)
throws IOException, RestApiException, PermissionBackendException {
- Input input = readJson(httpRequest, TypeLiteral.get(Input.class));
+ Input input = PayloadSerDes.parseInput(httpRequest);
IdString id = getProjectName(httpRequest).get();
- ProjectResource projectResource = projectsCollection.parse(TopLevelResource.INSTANCE, id);
- return (Response<Map<String, Object>>) fetchAction.apply(projectResource, input);
+ return (Response<Map<String, Object>>) fetchAction.apply(parseProjectResource(id), input);
}
- private <T> void writeResponse(HttpServletResponse httpResponse, Response<T> response)
- throws IOException {
- String responseJson = gson.toJson(response);
- if (response.statusCode() == SC_OK || response.statusCode() == SC_CREATED) {
-
- httpResponse.setContentType("application/json");
- httpResponse.setStatus(response.statusCode());
- PrintWriter writer = httpResponse.getWriter();
- writer.print(new String(RestApiServlet.JSON_MAGIC));
- writer.print(responseJson);
- } else {
- httpResponse.sendError(response.statusCode(), responseJson);
+ private ProjectResource parseProjectResource(IdString id) throws ResourceNotFoundException {
+ Optional<ProjectState> project = projectCache.get(Project.nameKey(id.get()));
+ if (project.isEmpty()) {
+ throw new ResourceNotFoundException(id);
}
- }
-
- private <T> T readJson(HttpServletRequest httpRequest, TypeLiteral<T> typeLiteral)
- throws IOException, BadRequestException {
-
- try (BufferedReader br = httpRequest.getReader();
- JsonReader json = new JsonReader(br)) {
- try {
- json.setLenient(true);
-
- try {
- json.peek();
- } catch (EOFException e) {
- throw new BadRequestException("Expected JSON object", e);
- }
-
- return gson.fromJson(json, typeLiteral.getType());
- } finally {
- try {
- // Reader.close won't consume the rest of the input. Explicitly consume the request
- // body.
- br.skip(Long.MAX_VALUE);
- } catch (Exception e) {
- // ignore, e.g. trying to consume the rest of the input may fail if the request was
- // cancelled
- logger.atFine().withCause(e).log("Exception during the parsing of the request json");
- }
- }
- }
+ return new ProjectResource(project.get(), currentUserProvider.get());
}
/**
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java
deleted file mode 100644
index 85a7729..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.pull.api.exception;
-
-import com.google.gerrit.extensions.restapi.RestApiException;
-
-public class InitProjectException extends RestApiException {
- private static final long serialVersionUID = 1L;
-
- public InitProjectException(String projectName) {
- super("Cannot create project " + projectName);
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/util/PayloadSerDes.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/util/PayloadSerDes.java
new file mode 100644
index 0000000..414af37
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/util/PayloadSerDes.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2023 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.pull.api.util;
+
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.api.projects.HeadInput;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonReader;
+import com.google.inject.TypeLiteral;
+import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction;
+import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput;
+import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionsInput;
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class PayloadSerDes {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final Gson gson = OutputFormat.JSON.newGsonBuilder().create();
+
+ public static RevisionInput parseRevisionInput(HttpServletRequest httpRequest)
+ throws BadRequestException, IOException {
+ return parse(httpRequest, TypeLiteral.get(RevisionInput.class));
+ }
+
+ public static RevisionsInput parseRevisionsInput(HttpServletRequest httpRequest)
+ throws BadRequestException, IOException {
+ return parse(httpRequest, TypeLiteral.get(RevisionsInput.class));
+ }
+
+ public static HeadInput parseHeadInput(HttpServletRequest httpRequest)
+ throws BadRequestException, IOException {
+ return parse(httpRequest, TypeLiteral.get(HeadInput.class));
+ }
+
+ public static FetchAction.Input parseInput(HttpServletRequest httpRequest)
+ throws BadRequestException, IOException {
+ return parse(httpRequest, TypeLiteral.get(FetchAction.Input.class));
+ }
+
+ public static <T> void writeResponse(HttpServletResponse httpResponse, Response<T> response)
+ throws IOException {
+ String responseJson = gson.toJson(response);
+ if (response.statusCode() == SC_OK || response.statusCode() == SC_CREATED) {
+
+ httpResponse.setContentType("application/json");
+ httpResponse.setStatus(response.statusCode());
+ PrintWriter writer = httpResponse.getWriter();
+ writer.print(new String(RestApiServlet.JSON_MAGIC));
+ writer.print(responseJson);
+ } else {
+ httpResponse.sendError(response.statusCode(), responseJson);
+ }
+ }
+
+ private static <T> T parse(HttpServletRequest httpRequest, TypeLiteral<T> typeLiteral)
+ throws IOException, BadRequestException {
+
+ try (BufferedReader br = httpRequest.getReader();
+ JsonReader json = new JsonReader(br)) {
+ try {
+ json.setLenient(true);
+
+ try {
+ json.peek();
+ } catch (EOFException e) {
+ throw new BadRequestException("Expected JSON object", e);
+ }
+
+ return gson.fromJson(json, typeLiteral.getType());
+ } finally {
+ try {
+ // Reader.close won't consume the rest of the input. Explicitly consume the request
+ // body.
+ br.skip(Long.MAX_VALUE);
+ } catch (Exception e) {
+ // ignore, e.g. trying to consume the rest of the input may fail if the request was
+ // cancelled
+ logger.atFine().withCause(e).log("Exception during the parsing of the request json");
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
index 1991260..f7ed4cb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
@@ -40,7 +40,22 @@
return callFetch(project, refName, targetUri, MILLISECONDS.toNanos(System.currentTimeMillis()));
}
- HttpResult initProject(Project.NameKey project, URIish uri) throws IOException;
+ /**
+ * Replicates the creation of a project, including the configuration stored in refs/meta/config.
+ *
+ * @param project The unique name of the project.
+ * @param uri The destination URI where the project and its configuration should be replicated to.
+ * @param eventCreatedOn The timestamp indicating when the init project event occurred.
+ * @param refsMetaConfigRevisionData A history of revisions for the refs/meta/config ref.
+ * @return An HTTP result object providing information about the replication process.
+ * @throws IOException If an I/O error occurs during the replication.
+ */
+ HttpResult initProject(
+ Project.NameKey project,
+ URIish uri,
+ long eventCreatedOn,
+ List<RevisionData> refsMetaConfigRevisionData)
+ throws IOException;
HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
index b606ba8..7607e4b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -23,6 +23,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.server.config.GerritInstanceId;
@@ -130,14 +131,29 @@
}
/* (non-Javadoc)
- * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#initProject(com.google.gerrit.entities.Project.NameKey, org.eclipse.jgit.transport.URIish)
+ * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#initProject(com.google.gerrit.entities.Project.NameKey, org.eclipse.jgit.transport.URIish, long, java.util.List<com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData>)
*/
@Override
- public HttpResult initProject(Project.NameKey project, URIish uri) throws IOException {
+ public HttpResult initProject(
+ NameKey project,
+ URIish uri,
+ long eventCreatedOn,
+ List<RevisionData> refsMetaConfigRevisionData)
+ throws IOException {
String url = formatInitProjectUrl(uri.toString(), project);
+
+ RevisionData[] inputData = new RevisionData[refsMetaConfigRevisionData.size()];
+ RevisionsInput input =
+ new RevisionsInput(
+ instanceId,
+ RefNames.REFS_CONFIG,
+ eventCreatedOn,
+ refsMetaConfigRevisionData.toArray(inputData));
+
HttpPut put = new HttpPut(url);
+ put.setEntity(new StringEntity(GSON.toJson(input)));
put.addHeader(new BasicHeader("Accept", MediaType.ANY_TEXT_TYPE.toString()));
- put.addHeader(new BasicHeader("Content-Type", MediaType.PLAIN_TEXT_UTF_8.toString()));
+ put.addHeader(new BasicHeader("Content-Type", MediaType.JSON_UTF_8.toString()));
return executeRequest(put, bearerTokenProvider.get(), uri);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
index 6bb1373..0ed262d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
@@ -68,11 +68,6 @@
git, refSpec.getSource(), commit, error::append)) {
throw new MissingLatestPatchSetException(name, refSpec.getSource(), error.toString());
}
-
- refHead = newObjectID = oi.insert(commitObject.getType(), commitObject.getContent());
-
- RevisionObjectData treeObject = revisionData.getTreeObject();
- oi.insert(treeObject.getType(), treeObject.getContent());
}
for (RevisionObjectData rev : revisionData.getBlobs()) {
@@ -83,6 +78,13 @@
refHead = newObjectID;
}
+ if (commitObject != null) {
+ RevisionObjectData treeObject = revisionData.getTreeObject();
+ oi.insert(treeObject.getType(), treeObject.getContent());
+
+ refHead = oi.insert(commitObject.getType(), commitObject.getContent());
+ }
+
oi.flush();
if (commitObject == null) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
index 8fac9ed..43ec9bf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
@@ -28,6 +28,8 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
@@ -107,7 +109,9 @@
if (credentialsProvider.supports(user, pass)
&& credentialsProvider.get(uri, user, pass)
&& uri.getScheme() != null
- && !"ssh".equalsIgnoreCase(uri.getScheme())) {
+ && !"ssh".equalsIgnoreCase(uri.getScheme())
+ && StringUtils.isNotEmpty(user.getValue())
+ && ArrayUtils.isNotEmpty(pass.getValue())) {
return uri.setUser(user.getValue()).setPass(String.valueOf(pass.getValue()));
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetchValidator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetchValidator.java
index 9a10898..434c0f0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetchValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetchValidator.java
@@ -35,7 +35,7 @@
@Override
public Void visit(AssistedInjectBinding<? extends FetchFactory> binding) {
- TypeLiteral<CGitFetch> nativeGitFetchType = new TypeLiteral<CGitFetch>() {};
+ TypeLiteral<CGitFetch> nativeGitFetchType = new TypeLiteral<>() {};
for (AssistedMethod method : binding.getAssistedMethods()) {
if (method.getImplementationType().equals(nativeGitFetchType)) {
String[] command = new String[] {"git", "--version"};
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/PermanentTransportException.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/PermanentTransportException.java
index acb68cf..0fa89b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/PermanentTransportException.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/PermanentTransportException.java
@@ -14,7 +14,7 @@
package com.googlesource.gerrit.plugins.replication.pull.fetch;
-import com.jcraft.jsch.JSchException;
+import org.apache.sshd.common.SshException;
import org.eclipse.jgit.errors.TransportException;
public class PermanentTransportException extends TransportException {
@@ -26,7 +26,8 @@
public static TransportException wrapIfPermanentTransportException(TransportException e) {
Throwable cause = e.getCause();
- if (cause instanceof JSchException && cause.getMessage().startsWith("UnknownHostKey:")) {
+ if (cause instanceof SshException
+ && cause.getMessage().startsWith("Failed (UnsupportedCredentialItem) to execute:")) {
return new PermanentTransportException("Terminal fetch failure", e);
}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 73c5c05..22b52e7 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -16,10 +16,6 @@
group that is granted the 'Pull Replication' capability (provided
by this plugin) or the 'Administrate Server' capability.
-When replicating hidden projects, the pull replication user needs to have
-the 'Administrate Server' capability or being added as the owner of each
-individual project that is supposed to be replicated.
-
Change Indexing
--------
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
index ee286bb..dd9f98e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
@@ -18,9 +18,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
+import com.google.gerrit.acceptance.TestMetricMaker;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.util.IdGenerator;
@@ -54,6 +54,7 @@
private final Project.NameKey PROJECT_NAME = Project.NameKey.parse(TEST_PROJECT_NAME);
private final String TEST_REF = "refs/heads/refForReplicationTask";
private final String URI_PATTERN = "http://test.com/" + TEST_PROJECT_NAME + ".git";
+ private final TestMetricMaker testMetricMaker = new TestMetricMaker();
@Mock private GitRepositoryManager grm;
@Mock private Repository repository;
@@ -72,8 +73,9 @@
@Before
public void setup() throws Exception {
+ testMetricMaker.reset();
FetchReplicationMetrics fetchReplicationMetrics =
- new FetchReplicationMetrics("pull-replication", new DisabledMetricMaker());
+ new FetchReplicationMetrics("pull-replication", testMetricMaker);
urIish = new URIish(URI_PATTERN);
grm = mock(GitRepositoryManager.class);
@@ -704,6 +706,51 @@
TEST_PROJECT_NAME, TEST_REF, urIish, ReplicationState.RefFetchResult.FAILED, null);
}
+ @Test
+ public void shouldNotRecordReplicationLatencyMetricIfAllRefsAreExcluded() throws Exception {
+ setupMocks(true);
+ String filteredRef = "refs/heads/filteredRef";
+ Set<String> refSpecs = Set.of(TEST_REF, filteredRef);
+ createTestStates(TEST_REF, 1);
+ createTestStates(filteredRef, 1);
+ setupFetchFactoryMock(
+ List.of(new FetchFactoryEntry.Builder().refSpecNameWithDefaults(TEST_REF).build()),
+ Optional.of(List.of(TEST_REF)));
+ objectUnderTest.addRefs(refSpecs);
+ objectUnderTest.setReplicationFetchFilter(replicationFilter);
+ ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
+ when(replicationFilter.get()).thenReturn(mockFilter);
+ when(mockFilter.filter(TEST_PROJECT_NAME, refSpecs)).thenReturn(Collections.emptySet());
+
+ objectUnderTest.run();
+
+ verify(pullReplicationApiRequestMetrics, never()).stop(any());
+ assertThat(testMetricMaker.getTimer("replication_latency")).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldRecordReplicationLatencyMetricWhenAtLeastOneRefWasReplicated()
+ throws Exception {
+ setupMocks(true);
+ String filteredRef = "refs/heads/filteredRef";
+ Set<String> refSpecs = Set.of(TEST_REF, filteredRef);
+ createTestStates(TEST_REF, 1);
+ createTestStates(filteredRef, 1);
+ setupFetchFactoryMock(
+ List.of(new FetchFactoryEntry.Builder().refSpecNameWithDefaults(TEST_REF).build()),
+ Optional.of(List.of(TEST_REF)));
+ objectUnderTest.addRefs(refSpecs);
+ objectUnderTest.setReplicationFetchFilter(replicationFilter);
+ ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
+ when(replicationFilter.get()).thenReturn(mockFilter);
+ when(mockFilter.filter(TEST_PROJECT_NAME, refSpecs)).thenReturn(Set.of(TEST_REF));
+
+ objectUnderTest.run();
+
+ verify(pullReplicationApiRequestMetrics).stop(any());
+ assertThat(testMetricMaker.getTimer("replication_latency")).isGreaterThan(0);
+ }
+
private void setupRequestScopeMock() {
when(scoper.scope(any()))
.thenAnswer(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PermanentFailureExceptionTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PermanentFailureExceptionTest.java
index 09a465c..fcb0702 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PermanentFailureExceptionTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PermanentFailureExceptionTest.java
@@ -18,7 +18,7 @@
import com.googlesource.gerrit.plugins.replication.pull.fetch.InexistentRefTransportException;
import com.googlesource.gerrit.plugins.replication.pull.fetch.PermanentTransportException;
-import com.jcraft.jsch.JSchException;
+import org.apache.sshd.common.SshException;
import org.eclipse.jgit.errors.TransportException;
import org.junit.Test;
@@ -29,7 +29,9 @@
assertThat(
PermanentTransportException.wrapIfPermanentTransportException(
new TransportException(
- "SSH error", new JSchException("UnknownHostKey: some.place"))))
+ "SSH error",
+ new SshException(
+ "Failed (UnsupportedCredentialItem) to execute: some.commands"))))
.isInstanceOf(PermanentTransportException.class);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigIT.java
index 5cf01b1..0721dd2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigIT.java
@@ -49,7 +49,8 @@
@UseLocalDisk
@TestPlugin(
name = "pull-replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+ httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
public class PullReplicationFanoutConfigIT extends LightweightPluginDaemonTest {
private static final Optional<String> ALL_PROJECTS = Optional.empty();
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java
index 18f7a0c..5717a8b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2020 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.
@@ -14,9 +14,45 @@
package com.googlesource.gerrit.plugins.replication.pull;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.GitUtil.pushOne;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.SkipProjectClone;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.events.HeadUpdatedListener;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.googlesource.gerrit.plugins.replication.AutoReloadConfigDecorator;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Ignore;
+import org.junit.Test;
@SkipProjectClone
@UseLocalDisk
@@ -25,4 +61,350 @@
sysModule =
"com.googlesource.gerrit.plugins.replication.pull.PullReplicationITAbstract$PullReplicationTestModule",
httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
-public class PullReplicationIT extends PullReplicationITAbstract {}
+public class PullReplicationIT extends PullReplicationSetupBase {
+
+ @Override
+ protected void setReplicationSource(
+ String remoteName, List<String> replicaSuffixes, Optional<String> project)
+ throws IOException {
+ List<String> fetchUrls =
+ buildReplicaURLs(replicaSuffixes, s -> gitPath.resolve("${name}" + s + ".git").toString());
+ config.setStringList("remote", remoteName, "url", fetchUrls);
+ config.setString("remote", remoteName, "apiUrl", adminRestSession.url());
+ config.setString("remote", remoteName, "fetch", "+refs/*:refs/*");
+ config.setInt("remote", remoteName, "timeout", 600);
+ config.setInt("remote", remoteName, "replicationDelay", TEST_REPLICATION_DELAY);
+ project.ifPresent(prj -> config.setString("remote", remoteName, "projects", prj));
+ config.setBoolean("gerrit", null, "autoReload", true);
+ config.save();
+ }
+
+ @Override
+ public void setUpTestPlugin() throws Exception {
+ setUpTestPlugin(false);
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateNewChangeRef() throws Exception {
+ testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+
+ Result pushResult = createChange();
+ RevCommit sourceCommit = pushResult.getCommit();
+ String sourceRef = pushResult.getPatchSet().refName();
+
+ ReplicationQueue pullReplicationQueue = getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ sourceRef,
+ ObjectId.zeroId().getName(),
+ sourceCommit.getId().getName(),
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
+
+ Ref targetBranchRef = getRef(repo, sourceRef);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateNewBranch() throws Exception {
+ String testProjectName = project + TEST_REPLICATION_SUFFIX;
+ createTestProject(testProjectName);
+
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+ String branchRevision = gApi.projects().name(testProjectName).branch(newBranch).get().revision;
+
+ ReplicationQueue pullReplicationQueue =
+ plugin.getSysInjector().getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ newBranch,
+ ObjectId.zeroId().getName(),
+ branchRevision,
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project);
+ Repository sourceRepo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, newBranch) != null);
+
+ Ref targetBranchRef = getRef(repo, newBranch);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId().getName()).isEqualTo(branchRevision);
+ }
+ }
+
+ @Test
+ @UseLocalDisk
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateForceUpdatedBranch() throws Exception {
+ boolean forcedPush = true;
+ String testProjectName = project + TEST_REPLICATION_SUFFIX;
+ NameKey testProjectNameKey = createTestProject(testProjectName);
+
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+
+ projectOperations
+ .project(testProjectNameKey)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(newBranch).group(REGISTERED_USERS).force(true))
+ .update();
+
+ String branchRevision = gApi.projects().name(testProjectName).branch(newBranch).get().revision;
+
+ ReplicationQueue pullReplicationQueue =
+ plugin.getSysInjector().getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ newBranch,
+ ObjectId.zeroId().getName(),
+ branchRevision,
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, newBranch) != null);
+
+ Ref targetBranchRef = getRef(repo, newBranch);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId().getName()).isEqualTo(branchRevision);
+ }
+
+ TestRepository<InMemoryRepository> testProject = cloneProject(testProjectNameKey);
+ fetch(testProject, RefNames.REFS_HEADS + "*:" + RefNames.REFS_HEADS + "*");
+ RevCommit amendedCommit = testProject.amendRef(newBranch).message("Amended commit").create();
+ PushResult pushResult =
+ pushOne(testProject, newBranch, newBranch, false, forcedPush, Collections.emptyList());
+ Collection<RemoteRefUpdate> pushedRefs = pushResult.getRemoteUpdates();
+ assertThat(pushedRefs).hasSize(1);
+ assertThat(pushedRefs.iterator().next().getStatus()).isEqualTo(Status.OK);
+
+ FakeGitReferenceUpdatedEvent forcedPushEvent =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ newBranch,
+ branchRevision,
+ amendedCommit.getId().getName(),
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(forcedPushEvent);
+
+ try (Repository repo = repoManager.openRepository(project);
+ Repository sourceRepo = repoManager.openRepository(project)) {
+ waitUntil(
+ () ->
+ checkedGetRef(repo, newBranch) != null
+ && checkedGetRef(repo, newBranch)
+ .getObjectId()
+ .getName()
+ .equals(amendedCommit.getId().getName()));
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateNewChangeRefCGitClient() throws Exception {
+ AutoReloadConfigDecorator autoReloadConfigDecorator =
+ getInstance(AutoReloadConfigDecorator.class);
+
+ config.setBoolean("replication", null, "useCGitClient", true);
+ config.save();
+
+ autoReloadConfigDecorator.reload();
+
+ testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+
+ Result pushResult = createChange();
+ RevCommit sourceCommit = pushResult.getCommit();
+ String sourceRef = pushResult.getPatchSet().refName();
+
+ ReplicationQueue pullReplicationQueue = getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ sourceRef,
+ ObjectId.zeroId().getName(),
+ sourceCommit.getId().getName(),
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
+
+ Ref targetBranchRef = getRef(repo, sourceRef);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateNewBranchCGitClient() throws Exception {
+ AutoReloadConfigDecorator autoReloadConfigDecorator =
+ getInstance(AutoReloadConfigDecorator.class);
+
+ config.setBoolean("replication", null, "useCGitClient", true);
+ config.save();
+
+ autoReloadConfigDecorator.reload();
+
+ String testProjectName = project + TEST_REPLICATION_SUFFIX;
+ createTestProject(testProjectName);
+
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+ String branchRevision = gApi.projects().name(testProjectName).branch(newBranch).get().revision;
+
+ ReplicationQueue pullReplicationQueue =
+ plugin.getSysInjector().getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ newBranch,
+ ObjectId.zeroId().getName(),
+ branchRevision,
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project);
+ Repository sourceRepo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, newBranch) != null);
+
+ Ref targetBranchRef = getRef(repo, newBranch);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId().getName()).isEqualTo(branchRevision);
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldCreateNewProject() throws Exception {
+ NameKey projectToCreate = Project.nameKey(project.get() + "_created");
+
+ setReplicationSource(TEST_REPLICATION_REMOTE, "", Optional.of(projectToCreate.get()));
+ config.save();
+ AutoReloadConfigDecorator autoReloadConfigDecorator =
+ getInstance(AutoReloadConfigDecorator.class);
+ autoReloadConfigDecorator.reload();
+ Source source =
+ getInstance(SourcesCollection.class).getByRemoteName(TEST_REPLICATION_REMOTE).get();
+
+ FetchApiClient client = getInstance(FetchApiClient.Factory.class).create(source);
+ client.initProject(
+ projectToCreate,
+ new URIish(source.getApis().get(0)),
+ System.currentTimeMillis(),
+ Collections.emptyList());
+
+ waitUntil(() -> repoManager.list().contains(projectToCreate));
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateProjectDeletion() throws Exception {
+ String projectToDelete = project.get();
+ setReplicationSource(TEST_REPLICATION_REMOTE, "", Optional.of(projectToDelete));
+ config.save();
+ AutoReloadConfigDecorator autoReloadConfigDecorator =
+ getInstance(AutoReloadConfigDecorator.class);
+ autoReloadConfigDecorator.reload();
+
+ ProjectDeletedListener.Event event =
+ new ProjectDeletedListener.Event() {
+ @Override
+ public String getProjectName() {
+ return projectToDelete;
+ }
+
+ @Override
+ public NotifyHandling getNotify() {
+ return NotifyHandling.NONE;
+ }
+ };
+ for (ProjectDeletedListener l : deletedListeners) {
+ l.onProjectDeleted(event);
+ }
+
+ waitUntil(() -> !repoManager.list().contains(project));
+ }
+
+ @Test
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ public void shouldReplicateHeadUpdate() throws Exception {
+ String testProjectName = project.get();
+ setReplicationSource(TEST_REPLICATION_REMOTE, "", Optional.of(testProjectName));
+ config.save();
+ AutoReloadConfigDecorator autoReloadConfigDecorator =
+ getInstance(AutoReloadConfigDecorator.class);
+ autoReloadConfigDecorator.reload();
+
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+
+ ReplicationQueue pullReplicationQueue =
+ plugin.getSysInjector().getInstance(ReplicationQueue.class);
+
+ HeadUpdatedListener.Event event = new FakeHeadUpdateEvent(master, newBranch, testProjectName);
+ pullReplicationQueue.onHeadUpdated(event);
+
+ waitUntil(
+ () -> {
+ try {
+ return gApi.projects().name(testProjectName).head().equals(newBranch);
+ } catch (RestApiException e) {
+ return false;
+ }
+ });
+ }
+
+ @Ignore
+ @GerritConfig(name = "gerrit.instanceId", value = TEST_REPLICATION_REMOTE)
+ @GerritConfig(name = "container.replica", value = "true")
+ public void shouldReplicateNewChangeRefToReplica() throws Exception {
+ testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+
+ Result pushResult = createChange();
+ RevCommit sourceCommit = pushResult.getCommit();
+ String sourceRef = pushResult.getPatchSet().refName();
+
+ ReplicationQueue pullReplicationQueue = getInstance(ReplicationQueue.class);
+ FakeGitReferenceUpdatedEvent event =
+ new FakeGitReferenceUpdatedEvent(
+ project,
+ sourceRef,
+ ObjectId.zeroId().getName(),
+ sourceCommit.getId().getName(),
+ TEST_REPLICATION_REMOTE);
+ pullReplicationQueue.onEvent(event);
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
+
+ Ref targetBranchRef = getRef(repo, sourceRef);
+ assertThat(targetBranchRef).isNotNull();
+ assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
+ }
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 1cffec7..a07aa55 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -33,6 +33,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
@@ -147,7 +148,8 @@
.when(fetchRestApiClient.callSendObjects(any(), anyString(), anyLong(), any(), any()))
.thenReturn(httpResult);
when(fetchRestApiClient.callFetch(any(), anyString(), any())).thenReturn(fetchHttpResult);
- when(fetchRestApiClient.initProject(any(), any())).thenReturn(successfulHttpResult);
+ when(fetchRestApiClient.initProject(any(), any(), anyLong(), any()))
+ .thenReturn(successfulHttpResult);
when(successfulHttpResult.isSuccessful()).thenReturn(true);
when(httpResult.isSuccessful()).thenReturn(true);
when(fetchHttpResult.isSuccessful()).thenReturn(true);
@@ -206,7 +208,7 @@
objectUnderTest.start();
objectUnderTest.onEvent(event);
- verify(fetchRestApiClient).initProject(any(), any());
+ verify(fetchRestApiClient).initProject(any(), any(), anyLong(), any());
}
@Test
@@ -219,7 +221,22 @@
objectUnderTest.start();
objectUnderTest.onEvent(event);
- verify(fetchRestApiClient, never()).initProject(any(), any());
+ verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
+ }
+
+ @Test
+ public void shouldNotCallInitProjectWhenProjectWithoutConfiguration() throws Exception {
+ Event event = new TestEvent("refs/changes/01/1/meta");
+ when(httpResult.isSuccessful()).thenReturn(false);
+ when(httpResult.isProjectMissing(any())).thenReturn(true);
+ when(source.isCreateMissingRepositories()).thenReturn(true);
+ when(revReader.read(any(), any(), eq(RefNames.REFS_CONFIG), anyInt()))
+ .thenReturn(Optional.empty());
+
+ objectUnderTest.start();
+ objectUnderTest.onEvent(event);
+
+ verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
}
@Test
@@ -369,7 +386,7 @@
}
@Test
- public void shouldCallDeleteWhenReplicateProjectDeletionsTrue() throws Exception {
+ public void shouldCallDeleteWhenReplicateProjectDeletionsTrue() {
when(source.wouldDeleteProject(any())).thenReturn(true);
String projectName = "testProject";
@@ -385,7 +402,7 @@
}
@Test
- public void shouldNotCallDeleteWhenProjectNotToDelete() throws Exception {
+ public void shouldNotCallDeleteWhenProjectNotToDelete() {
when(source.wouldDeleteProject(any())).thenReturn(false);
FakeProjectDeletedEvent event = new FakeProjectDeletedEvent("testProject");
@@ -397,7 +414,7 @@
}
@Test
- public void shouldScheduleUpdateHeadWhenWouldFetchProject() throws Exception {
+ public void shouldScheduleUpdateHeadWhenWouldFetchProject() {
when(source.wouldFetchProject(any())).thenReturn(true);
String projectName = "aProject";
@@ -413,7 +430,7 @@
}
@Test
- public void shouldNotScheduleUpdateHeadWhenNotWouldFetchProject() throws Exception {
+ public void shouldNotScheduleUpdateHeadWhenNotWouldFetchProject() {
when(source.wouldFetchProject(any())).thenReturn(false);
String projectName = "aProject";
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
index 0a3960f..f8110d1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
@@ -162,7 +162,7 @@
}
public ResponseHandler<Object> assertHttpResponseCode(int responseCode) {
- return new ResponseHandler<Object>() {
+ return new ResponseHandler<>() {
@Override
public Object handleResponse(HttpResponse response)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
index f1f9e44..fc1b02c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
@@ -102,7 +102,6 @@
fetchStateLog,
projectCache,
sourceCollection,
- applyObject,
permissionBackend,
eventDispatcherDataItem,
new LocalGitRepositoryManagerProvider(gitManager));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
index 10415e4..cf7515e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
@@ -94,10 +94,11 @@
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId")
@GerritConfig(name = "container.replica", value = "true")
- public void shouldReturnForbiddenForUserWithoutPermissionsOnReplica() throws Exception {
+ public void shouldReturnUnauthorizedForUserWithoutPermissionsOnReplica() throws Exception {
httpClientFactory
.create(source)
- .execute(createDeleteRequest(), assertHttpResponseCode(HttpServletResponse.SC_FORBIDDEN));
+ .execute(
+ createDeleteRequest(), assertHttpResponseCode(HttpServletResponse.SC_UNAUTHORIZED));
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
index c543969..0f13881 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
@@ -184,12 +184,13 @@
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId")
@GerritConfig(name = "container.replica", value = "true")
- public void shouldReturnForbiddenForUserWithoutPermissionsWhenNodeIsAReplica() throws Exception {
+ public void shouldReturnUnauthorizedForUserWithoutPermissionsWhenNodeIsAReplica()
+ throws Exception {
httpClientFactory
.create(source)
.execute(
createPutRequestWithHeaders(),
- assertHttpResponseCode(HttpServletResponse.SC_FORBIDDEN));
+ assertHttpResponseCode(HttpServletResponse.SC_UNAUTHORIZED));
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java
index 9e5ca8d..9492d49 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java
@@ -4,18 +4,25 @@
import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_UNPROCESSABLE_ENTITY;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.atLeastOnce;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import com.google.common.net.MediaType;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.*;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
-import com.google.gerrit.server.restapi.project.ProjectsCollection;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.util.Providers;
import java.io.*;
import java.nio.charset.StandardCharsets;
+import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -37,10 +44,12 @@
@Mock private ProjectInitializationAction projectInitializationAction;
@Mock private UpdateHeadAction updateHEADAction;
@Mock private ProjectDeletionAction projectDeletionAction;
- @Mock private ProjectsCollection projectsCollection;
- @Mock private ProjectResource projectResource;
+ @Mock private ProjectCache projectCache;
+ @Mock private ProjectState projectState;
@Mock private ServletOutputStream outputStream;
@Mock private PrintWriter printWriter;
+ @Mock private IdentifiedUser identifiedUserMock;
+ @Mock private AnonymousUser anonymousUserMock;
private final String PLUGIN_NAME = "pull-replication";
private final String PROJECT_NAME = "some-project";
private final String PROJECT_NAME_GIT = "some-project.git";
@@ -60,6 +69,10 @@
private final Response OK_RESPONSE = Response.ok();
private PullReplicationFilter createPullReplicationFilter() {
+ return createPullReplicationFilter(identifiedUserMock);
+ }
+
+ private PullReplicationFilter createPullReplicationFilter(CurrentUser currentUser) {
return new PullReplicationFilter(
fetchAction,
applyObjectAction,
@@ -67,8 +80,9 @@
projectInitializationAction,
updateHEADAction,
projectDeletionAction,
- projectsCollection,
- PLUGIN_NAME);
+ projectCache,
+ PLUGIN_NAME,
+ Providers.of(currentUser));
}
private void defineBehaviours(byte[] payload, String uri) throws Exception {
@@ -76,15 +90,14 @@
InputStream is = new ByteArrayInputStream(payload);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
when(request.getReader()).thenReturn(bufferedReader);
- when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
- .thenReturn(projectResource);
+ when(projectCache.get(Project.nameKey(PROJECT_NAME))).thenReturn(Optional.of(projectState));
when(response.getWriter()).thenReturn(printWriter);
}
private void verifyBehaviours() throws Exception {
verify(request, atLeastOnce()).getRequestURI();
verify(request).getReader();
- verify(projectsCollection).parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME));
+ verify(projectCache).get(Project.nameKey(PROJECT_NAME));
verify(response).getWriter();
verify(response).setContentType("application/json");
verify(response).setStatus(HttpServletResponse.SC_OK);
@@ -107,7 +120,7 @@
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
- verify(fetchAction).apply(eq(projectResource), any());
+ verify(fetchAction).apply(any(ProjectResource.class), any());
}
@Test
@@ -130,7 +143,7 @@
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
- verify(applyObjectAction).apply(eq(projectResource), any());
+ verify(applyObjectAction).apply(any(ProjectResource.class), any());
}
@Test
@@ -152,7 +165,7 @@
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
- verify(applyObjectsAction).apply(eq(projectResource), any());
+ verify(applyObjectsAction).apply(any(ProjectResource.class), any());
}
@Test
@@ -160,15 +173,11 @@
when(request.getRequestURI()).thenReturn(INIT_PROJECT_URI);
when(request.getHeader(ACCEPT)).thenReturn(MediaType.PLAIN_TEXT_UTF_8.toString());
- when(projectInitializationAction.initProject(PROJECT_NAME_GIT)).thenReturn(true);
- when(response.getWriter()).thenReturn(printWriter);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
- verify(request, times(5)).getRequestURI();
- verify(projectInitializationAction).initProject(eq(PROJECT_NAME_GIT));
- verify(response).getWriter();
+ verify(projectInitializationAction).service(request, response);
}
@Test
@@ -183,15 +192,14 @@
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
- verify(updateHEADAction).apply(eq(projectResource), any());
+ verify(updateHEADAction).apply(any(ProjectResource.class), any());
}
@Test
public void shouldFilterProjectDeletionAction() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
- when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
- .thenReturn(projectResource);
+ when(projectCache.get(Project.nameKey(PROJECT_NAME))).thenReturn(Optional.of(projectState));
when(projectDeletionAction.apply(any(), any())).thenReturn(OK_RESPONSE);
when(response.getWriter()).thenReturn(printWriter);
@@ -199,8 +207,8 @@
pullReplicationFilter.doFilter(request, response, filterChain);
verify(request, times(7)).getRequestURI();
- verify(projectsCollection).parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME));
- verify(projectDeletionAction).apply(eq(projectResource), any());
+ verify(projectCache).get(Project.nameKey(PROJECT_NAME));
+ verify(projectDeletionAction).apply(any(ProjectResource.class), any());
verify(response).getWriter();
verify(response).setContentType("application/json");
verify(response).setStatus(OK_RESPONSE.statusCode());
@@ -215,6 +223,17 @@
}
@Test
+ public void shouldGoNextInChainWhenAnonymousRequestUriDoesNotMatch() throws Exception {
+ when(request.getRequestURI()).thenReturn("any-url");
+ lenient().when(response.getOutputStream()).thenReturn(outputStream);
+
+ final PullReplicationFilter pullReplicationFilter =
+ createPullReplicationFilter(anonymousUserMock);
+ pullReplicationFilter.doFilter(request, response, filterChain);
+ verify(filterChain).doFilter(request, response);
+ }
+
+ @Test
public void shouldBe404WhenJsonIsMalformed() throws Exception {
byte[] payloadMalformedJson = "some-json-malformed".getBytes(StandardCharsets.UTF_8);
InputStream is = new ByteArrayInputStream(payloadMalformedJson);
@@ -230,24 +249,10 @@
}
@Test
- public void shouldBe500WhenProjectCannotBeInitiated() throws Exception {
- when(request.getRequestURI()).thenReturn(INIT_PROJECT_URI);
- when(request.getHeader(ACCEPT)).thenReturn(MediaType.PLAIN_TEXT_UTF_8.toString());
- when(projectInitializationAction.initProject(PROJECT_NAME_GIT)).thenReturn(false);
- when(response.getOutputStream()).thenReturn(outputStream);
-
- final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
- pullReplicationFilter.doFilter(request, response, filterChain);
-
- verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
-
- @Test
public void shouldBe500WhenResourceNotFound() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
- when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
- .thenReturn(projectResource);
+ when(projectCache.get(Project.nameKey(PROJECT_NAME))).thenReturn(Optional.of(projectState));
when(projectDeletionAction.apply(any(), any()))
.thenThrow(new ResourceNotFoundException("resource not found"));
when(response.getOutputStream()).thenReturn(outputStream);
@@ -280,6 +285,19 @@
}
@Test
+ public void shouldBe401WhenUserIsAnonymous() throws Exception {
+ byte[] payloadFetchAction = "{}".getBytes(StandardCharsets.UTF_8);
+
+ defineBehaviours(payloadFetchAction, FETCH_URI);
+ when(response.getOutputStream()).thenReturn(outputStream);
+
+ PullReplicationFilter pullReplicationFilter = createPullReplicationFilter(anonymousUserMock);
+ pullReplicationFilter.doFilter(request, response, filterChain);
+
+ verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+
+ @Test
public void shouldBe422WhenEntityCannotBeProcessed() throws Exception {
byte[] payloadFetchAction =
("{"
@@ -304,8 +322,7 @@
public void shouldBe409WhenThereIsResourceConflict() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
- when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
- .thenReturn(projectResource);
+ when(projectCache.get(Project.nameKey(PROJECT_NAME))).thenReturn(Optional.of(projectState));
when(projectDeletionAction.apply(any(), any()))
.thenThrow(new ResourceConflictException("Resource conflict"));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
index 25aa2a7..65ccdfb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
@@ -24,6 +24,7 @@
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
+import com.google.common.net.MediaType;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
@@ -37,6 +38,7 @@
import java.nio.ByteBuffer;
import java.util.Collections;
import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
@@ -94,6 +96,17 @@
+ "\",\"type\":2,\"content\":\"MTAwNjQ0IGJsb2IgYmIzODNmNTI0OWM2OGE0Y2M4YzgyYmRkMTIyOGI0YTg4ODNmZjZlOCAgICBmNzVhNjkwMDRhOTNiNGNjYzhjZTIxNWMxMjgwODYzNmMyYjc1Njc1\"},\"blobs\":[{\"sha1\":\""
+ blobObjectId
+ "\",\"type\":3,\"content\":\"ewogICJjb21tZW50cyI6IFsKICAgIHsKICAgICAgImtleSI6IHsKICAgICAgICAidXVpZCI6ICI5MGI1YWJmZl80ZjY3NTI2YSIsCiAgICAgICAgImZpbGVuYW1lIjogIi9DT01NSVRfTVNHIiwKICAgICAgICAicGF0Y2hTZXRJZCI6IDEKICAgICAgfSwKICAgICAgImxpbmVOYnIiOiA5LAogICAgICAiYXV0aG9yIjogewogICAgICAgICJpZCI6IDEwMDAwMDAKICAgICAgfSwKICAgICAgIndyaXR0ZW5PbiI6ICIyMDIxLTAxLTEzVDIyOjU3OjI4WiIsCiAgICAgICJzaWRlIjogMSwKICAgICAgIm1lc3NhZ2UiOiAidGVzdCBjb21tZW50IiwKICAgICAgInJhbmdlIjogewogICAgICAgICJzdGFydExpbmUiOiA5LAogICAgICAgICJzdGFydENoYXIiOiAyMSwKICAgICAgICAiZW5kTGluZSI6IDksCiAgICAgICAgImVuZENoYXIiOiAzNAogICAgICB9LAogICAgICAicmV2SWQiOiAiZjc1YTY5MDA0YTkzYjRjY2M4Y2UyMTVjMTI4MDg2MzZjMmI3NTY3NSIsCiAgICAgICJzZXJ2ZXJJZCI6ICI2OWVjMzhmMC0zNTBlLTRkOWMtOTZkNC1iYzk1NmYyZmFhYWMiLAogICAgICAidW5yZXNvbHZlZCI6IHRydWUKICAgIH0KICBdCn0\\u003d\"}]}}";
+
+ String expectedInitProjectWithConfigPayload =
+ "{\"label\":\"Replication\",\"ref_name\":\"refs/meta/config\",\"event_created_on\":"
+ + eventCreatedOn
+ + ",\"revisions_data\":[{\"commit_object\":{\"sha1\":\""
+ + commitObjectId
+ + "\",\"type\":1,\"content\":\"dHJlZSA3NzgxNGQyMTZhNmNhYjJkZGI5ZjI4NzdmYmJkMGZlYmRjMGZhNjA4CnBhcmVudCA5ODNmZjFhM2NmNzQ3MjVhNTNhNWRlYzhkMGMwNjEyMjEyOGY1YThkCmF1dGhvciBHZXJyaXQgVXNlciAxMDAwMDAwIDwxMDAwMDAwQDY5ZWMzOGYwLTM1MGUtNGQ5Yy05NmQ0LWJjOTU2ZjJmYWFhYz4gMTYxMDU3ODY0OCArMDEwMApjb21taXR0ZXIgR2Vycml0IENvZGUgUmV2aWV3IDxyb290QG1hY3plY2gtWFBTLTE1PiAxNjEwNTc4NjQ4ICswMTAwCgpVcGRhdGUgcGF0Y2ggc2V0IDEKClBhdGNoIFNldCAxOgoKKDEgY29tbWVudCkKClBhdGNoLXNldDogMQo\\u003d\"},\"tree_object\":{\"sha1\":\""
+ + treeObjectId
+ + "\",\"type\":2,\"content\":\"MTAwNjQ0IGJsb2IgYmIzODNmNTI0OWM2OGE0Y2M4YzgyYmRkMTIyOGI0YTg4ODNmZjZlOCAgICBmNzVhNjkwMDRhOTNiNGNjYzhjZTIxNWMxMjgwODYzNmMyYjc1Njc1\"},\"blobs\":[{\"sha1\":\""
+ + blobObjectId
+ + "\",\"type\":3,\"content\":\"ewogICJjb21tZW50cyI6IFsKICAgIHsKICAgICAgImtleSI6IHsKICAgICAgICAidXVpZCI6ICI5MGI1YWJmZl80ZjY3NTI2YSIsCiAgICAgICAgImZpbGVuYW1lIjogIi9DT01NSVRfTVNHIiwKICAgICAgICAicGF0Y2hTZXRJZCI6IDEKICAgICAgfSwKICAgICAgImxpbmVOYnIiOiA5LAogICAgICAiYXV0aG9yIjogewogICAgICAgICJpZCI6IDEwMDAwMDAKICAgICAgfSwKICAgICAgIndyaXR0ZW5PbiI6ICIyMDIxLTAxLTEzVDIyOjU3OjI4WiIsCiAgICAgICJzaWRlIjogMSwKICAgICAgIm1lc3NhZ2UiOiAidGVzdCBjb21tZW50IiwKICAgICAgInJhbmdlIjogewogICAgICAgICJzdGFydExpbmUiOiA5LAogICAgICAgICJzdGFydENoYXIiOiAyMSwKICAgICAgICAiZW5kTGluZSI6IDksCiAgICAgICAgImVuZENoYXIiOiAzNAogICAgICB9LAogICAgICAicmV2SWQiOiAiZjc1YTY5MDA0YTkzYjRjY2M4Y2UyMTVjMTI4MDg2MzZjMmI3NTY3NSIsCiAgICAgICJzZXJ2ZXJJZCI6ICI2OWVjMzhmMC0zNTBlLTRkOWMtOTZkNC1iYzk1NmYyZmFhYWMiLAogICAgICAidW5yZXNvbHZlZCI6IHRydWUKICAgIH0KICBdCn0\\u003d\"}]}]}";
String commitObject =
"tree "
+ treeObjectId
@@ -374,18 +387,26 @@
@Test
public void shouldCallInitProjectEndpoint() throws Exception {
-
- objectUnderTest.initProject(Project.nameKey("test_repo"), new URIish(api));
+ objectUnderTest.initProject(
+ Project.nameKey("test_repo"),
+ new URIish(api),
+ eventCreatedOn,
+ Collections.singletonList(createSampleRevisionData()));
verify(httpClient, times(1)).execute(httpPutCaptor.capture(), any());
-
HttpPut httpPut = httpPutCaptor.getValue();
+ String payload =
+ CharStreams.toString(
+ new InputStreamReader(httpPut.getEntity().getContent(), Charsets.UTF_8));
assertThat(httpPut.getURI().getHost()).isEqualTo("gerrit-host");
+ assertThat(httpPut.getHeaders(HttpHeaders.CONTENT_TYPE)[0].getValue())
+ .isEqualTo(MediaType.JSON_UTF_8.toString());
assertThat(httpPut.getURI().getPath())
.isEqualTo(
String.format(
"%s/plugins/pull-replication/init-project/test_repo.git",
urlAuthenticationPrefix()));
+ assertThat(payload).isEqualTo(expectedInitProjectWithConfigPayload);
assertAuthentication(httpPut);
}