Merge branch 'stable-3.5' into stable-3.6 * stable-3.5: Fix pull-replication plugin startup which was always failing Add description for pending tasks metric Add description for in_flight metric Fixed typo in metric description Change-Id: Ib8517e700ed248863878e6fa37258a71cbedb8b3
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 5421b21..a6b5ab7 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; @@ -329,22 +330,32 @@ 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, - 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, + 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( "[" @@ -418,7 +429,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(); @@ -435,11 +446,12 @@ delta.remove(inexistentRef); if (delta.isEmpty()) { repLog.warn("[{}] Empty replication task, skipping.", taskIdHex); - return; + return Collections.emptyList(); } runImpl(); } + return fetchRefSpecs; } private 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 5cf8bb6..8dacc9a 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
@@ -20,6 +20,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; @@ -38,6 +39,7 @@ private final ReplicationState.Factory replicationStateFactory; private final SourcesCollection sourcesCollection; private final WorkQueue workQueue; + private boolean isReplica; @Inject protected OnStartStop( @@ -47,7 +49,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; @@ -56,11 +59,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..1ba47de 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,42 @@ 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", 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( @@ -539,12 +534,7 @@ 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 +568,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 72f0266..d535ac9 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
@@ -26,6 +26,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.MissingParentObjectException; import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException; @@ -33,6 +34,7 @@ import java.util.Objects; import javax.servlet.http.HttpServletResponse; +@Singleton public class ApplyObjectAction implements RestModifyView<ProjectResource, RevisionInput> { private final ApplyObjectCommand applyObjectCommand;
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 fdb4f8f..9817f2c 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; @@ -37,6 +38,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +@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 dd06875..991ef07 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
@@ -91,7 +91,12 @@ try { state.markAllFetchTasksScheduled(); Future<?> future = source.get().schedule(name, refName, state, fetchType, apiRequestMetrics); - future.get(source.get().getTimeout(), TimeUnit.SECONDS); + int timeout = source.get().getTimeout(); + if (timeout == 0) { + future.get(); + } else { + future.get(timeout, TimeUnit.SECONDS); + } } catch (ExecutionException | IllegalStateException | TimeoutException
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/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..ca90c02 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
@@ -26,6 +26,7 @@ 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,19 +36,22 @@ 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.gerrit.server.project.ProjectState; import com.google.gson.Gson; 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.Provider; import com.google.inject.TypeLiteral; import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction.Input; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput; @@ -83,9 +87,10 @@ private ProjectInitializationAction projectInitializationAction; private UpdateHeadAction updateHEADAction; private ProjectDeletionAction projectDeletionAction; - private ProjectsCollection projectsCollection; + private ProjectCache projectCache; private Gson gson; private String pluginName; + private final Provider<CurrentUser> currentUserProvider; @Inject public PullReplicationFilter( @@ -95,17 +100,19 @@ 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,19 +127,25 @@ HttpServletRequest httpRequest = (HttpServletRequest) request; try { if (isFetchAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doFetch(httpRequest)); } else if (isApplyObjectAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doApplyObject(httpRequest)); } else if (isApplyObjectsAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doApplyObjects(httpRequest)); } else if (isInitProjectAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); if (!checkAcceptHeader(httpRequest, httpResponse)) { return; } doInitProject(httpRequest, httpResponse); } else if (isUpdateHEADAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doUpdateHEAD(httpRequest)); } else if (isDeleteProjectAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doDeleteProject(httpRequest)); } else { chain.doFilter(request, response); @@ -173,6 +186,13 @@ } } + private void failIfcurrentUserIsAnonymous() throws UnauthorizedAuthException { + CurrentUser currentUser = currentUserProvider.get(); + if (currentUser instanceof AnonymousUser) { + throw new UnauthorizedAuthException(); + } + } + private void doInitProject(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws RestApiException, IOException, PermissionBackendException { @@ -191,9 +211,8 @@ throws RestApiException, IOException, PermissionBackendException { RevisionInput input = readJson(httpRequest, TypeLiteral.get(RevisionInput.class)); 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") @@ -201,26 +220,24 @@ throws RestApiException, IOException, PermissionBackendException { RevisionsInput input = readJson(httpRequest, TypeLiteral.get(RevisionsInput.class)); 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)); 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") @@ -228,9 +245,16 @@ throws IOException, RestApiException, PermissionBackendException { Input input = readJson(httpRequest, TypeLiteral.get(Input.class)); 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 ProjectResource parseProjectResource(IdString id) throws ResourceNotFoundException { + Optional<ProjectState> project = projectCache.get(Project.nameKey(id.get())); + if (project.isEmpty()) { + throw new ResourceNotFoundException(id); + } + return new ProjectResource(project.get(), currentUserProvider.get()); } private <T> void writeResponse(HttpServletResponse httpResponse, Response<T> response)
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 24898e6..d3e45da 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; @@ -101,7 +103,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/FetchGitUpdateProcessingTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchGitUpdateProcessingTest.java index 68044b4..5a26054 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchGitUpdateProcessingTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchGitUpdateProcessingTest.java
@@ -47,8 +47,7 @@ } @Test - public void headRefReplicatedInGitUpdateProcessing() - throws URISyntaxException, PermissionBackendException { + public void headRefReplicatedInGitUpdateProcessing() throws PermissionBackendException { FetchRefReplicatedEvent expectedEvent = new FetchRefReplicatedEvent( "someProject",
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 817876d..d5d4546 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; @@ -55,6 +55,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; @@ -73,8 +74,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); @@ -705,6 +707,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 b9ea0c8..4ede1ae 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
@@ -50,7 +50,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..29bf7e4 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,346 @@ 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))); + + 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 9dc0b47..e40b90c 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
@@ -376,7 +376,7 @@ } @Test - public void shouldCallDeleteWhenReplicateProjectDeletionsTrue() throws IOException { + public void shouldCallDeleteWhenReplicateProjectDeletionsTrue() { when(source.wouldDeleteProject(any())).thenReturn(true); String projectName = "testProject"; @@ -392,7 +392,7 @@ } @Test - public void shouldNotCallDeleteWhenProjectNotToDelete() throws IOException { + public void shouldNotCallDeleteWhenProjectNotToDelete() { when(source.wouldDeleteProject(any())).thenReturn(false); FakeProjectDeletedEvent event = new FakeProjectDeletedEvent("testProject"); @@ -404,7 +404,7 @@ } @Test - public void shouldScheduleUpdateHeadWhenWouldFetchProject() throws IOException { + public void shouldScheduleUpdateHeadWhenWouldFetchProject() { when(source.wouldFetchProject(any())).thenReturn(true); String projectName = "aProject"; @@ -420,7 +420,7 @@ } @Test - public void shouldNotScheduleUpdateHeadWhenNotWouldFetchProject() throws IOException { + 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 20c1fea..e638653 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
@@ -163,7 +163,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/FetchCommandTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommandTest.java index c093719..de8036e 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommandTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchCommandTest.java
@@ -125,7 +125,7 @@ @Test public void shouldUpdateStateWhenInterruptedException() throws InterruptedException, ExecutionException, TimeoutException { - when(future.get(anyLong(), eq(TimeUnit.SECONDS))).thenThrow(new InterruptedException()); + when(future.get()).thenThrow(new InterruptedException()); when(source.schedule(projectName, REF_NAME_TO_FETCH, state, SYNC, Optional.empty())) .thenReturn(future); @@ -140,8 +140,7 @@ @Test public void shouldUpdateStateWhenExecutionException() throws InterruptedException, ExecutionException, TimeoutException { - when(future.get(anyLong(), eq(TimeUnit.SECONDS))) - .thenThrow(new ExecutionException(new Exception())); + when(future.get()).thenThrow(new ExecutionException(new Exception())); when(source.schedule(projectName, REF_NAME_TO_FETCH, state, SYNC, Optional.empty())) .thenReturn(future); @@ -159,6 +158,7 @@ when(future.get(anyLong(), eq(TimeUnit.SECONDS))).thenThrow(new TimeoutException()); when(source.schedule(projectName, REF_NAME_TO_FETCH, state, SYNC, Optional.empty())) .thenReturn(future); + when(source.getTimeout()).thenReturn(1); TimeoutException e = assertThrows(
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..5ede668 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
@@ -5,17 +5,25 @@ 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 +45,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 +70,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 +81,9 @@ projectInitializationAction, updateHEADAction, projectDeletionAction, - projectsCollection, - PLUGIN_NAME); + projectCache, + PLUGIN_NAME, + Providers.of(currentUser)); } private void defineBehaviours(byte[] payload, String uri) throws Exception { @@ -76,15 +91,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 +121,7 @@ pullReplicationFilter.doFilter(request, response, filterChain); verifyBehaviours(); - verify(fetchAction).apply(eq(projectResource), any()); + verify(fetchAction).apply(any(ProjectResource.class), any()); } @Test @@ -130,7 +144,7 @@ pullReplicationFilter.doFilter(request, response, filterChain); verifyBehaviours(); - verify(applyObjectAction).apply(eq(projectResource), any()); + verify(applyObjectAction).apply(any(ProjectResource.class), any()); } @Test @@ -152,7 +166,7 @@ pullReplicationFilter.doFilter(request, response, filterChain); verifyBehaviours(); - verify(applyObjectsAction).apply(eq(projectResource), any()); + verify(applyObjectsAction).apply(any(ProjectResource.class), any()); } @Test @@ -183,15 +197,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 +212,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 +228,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); @@ -246,8 +270,7 @@ 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 +303,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 +340,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"));