Merge branch 'stable-3.4' into stable-3.5

* stable-3.4:
  Return empty list when prefix is not matched in suggest
  Add metric to count when ref size is larger that maxApiPayloadSize
  Improve fetch logging by adding replication task id and refs list
  Change the base docker image in the broker topology.
  Update docker http/broker topologies.
  Pull-replication does not require DELETE refs permissions.

Adjusted Gerrit version in Dockerfile

Change-Id: Ic2350cba90027b7d648475f968eb1e355934ab3d
diff --git a/example-setup/broker/configs/replication.config.template b/example-setup/broker/configs/replication.config.template
index d81662f..d464586 100644
--- a/example-setup/broker/configs/replication.config.template
+++ b/example-setup/broker/configs/replication.config.template
@@ -1,6 +1,6 @@
 [gerrit]
     autoReload = true
-    replicateOnStartup = false
+    replicateOnStartup = $REPLICATE_ON_STARTUP
 [replication]
     excludeRefs = ^refs/users/\\d\\d/\\d+/edit-\\d+/\\d+$
     lockErrorMaxRetries = 5
diff --git a/example-setup/broker/docker-compose.yaml b/example-setup/broker/docker-compose.yaml
index 4f7f99f..705aea6 100644
--- a/example-setup/broker/docker-compose.yaml
+++ b/example-setup/broker/docker-compose.yaml
@@ -10,6 +10,7 @@
       - DEBUG_PORT=5005
       - BROKER_HOST=broker
       - BROKER_PORT=9092
+      - REPLICATE_ON_STARTUP=false
     ports:
       - "8080:8080"
       - "29418:29418"
@@ -26,6 +27,7 @@
       - DEBUG_PORT=5006
       - BROKER_HOST=broker
       - BROKER_PORT=9092
+      - REPLICATE_ON_STARTUP=true
     ports:
       - "8081:8080"
       - "29419:29418"
diff --git a/example-setup/http/configs/replication.config.template b/example-setup/http/configs/replication.config.template
index 7e81055..6768146 100644
--- a/example-setup/http/configs/replication.config.template
+++ b/example-setup/http/configs/replication.config.template
@@ -1,6 +1,6 @@
 [gerrit]
     autoReload = true
-    replicateOnStartup = false
+    replicateOnStartup = $REPLICATE_ON_STARTUP
 [replication]
     excludeRefs = ^refs/users/\\d\\d/\\d+/edit-\\d+/\\d+$
     lockErrorMaxRetries = 5
diff --git a/example-setup/http/docker-compose.yaml b/example-setup/http/docker-compose.yaml
index af90341..ccb6b86 100644
--- a/example-setup/http/docker-compose.yaml
+++ b/example-setup/http/docker-compose.yaml
@@ -8,6 +8,7 @@
       - REMOTE=replica-1
       - REMOTE_URL=gerrit2
       - DEBUG_PORT=5005
+      - REPLICATE_ON_STARTUP=false
     ports:
       - "8080:8080"
       - "29418:29418"
@@ -20,6 +21,7 @@
       - REMOTE=primary
       - REMOTE_URL=gerrit1
       - DEBUG_PORT=5006
+      - REPLICATE_ON_STARTUP=true
     ports:
       - "8081:8080"
       - "29419:29418"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
index 78745bb..d41dd8f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.replication.pull;
 
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.metrics.Counter0;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
 import com.google.gerrit.metrics.MetricMaker;
@@ -28,6 +29,8 @@
   private final Timer1<String> executionTime;
   private final Timer1<String> end2EndTime;
 
+  private final Counter0 maxApiPayloadSizeReachedCounter;
+
   @Inject
   ApplyObjectMetrics(@PluginName String pluginName, MetricMaker metricMaker) {
     Field<String> field =
@@ -53,6 +56,13 @@
                 .setCumulative()
                 .setUnit(Description.Units.MILLISECONDS),
             field);
+    maxApiPayloadSizeReachedCounter =
+        metricMaker.newCounter(
+            "apply_object_max_api_payload_reached",
+            new Description(
+                    "Number of apply object operation with payload larger than maxApiPayloadSize")
+                .setRate()
+                .setUnit("errors"));
   }
 
   /**
@@ -74,4 +84,9 @@
   public Timer1.Context<String> startEnd2End(String name) {
     return end2EndTime.start(name);
   }
+
+  /** Increment metric when ref size is larger than maxApiPayloadSize. */
+  public void incrementMaxPayloadSizeReached() {
+    maxApiPayloadSizeReachedCounter.increment();
+  }
 }
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 d8da65e..551b36c 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
@@ -91,6 +91,7 @@
   private final int maxLockRetries;
   private int lockRetryCount;
   private final int id;
+  private String taskIdHex;
   private final long createdAt;
   private final FetchReplicationMetrics metrics;
   private final AtomicBoolean canceledWhileRunning;
@@ -119,6 +120,7 @@
     lockRetryCount = 0;
     maxLockRetries = pool.getLockErrorMaxRetries();
     id = ig.next();
+    taskIdHex = HexFormat.fromInt(id);
     stateLog = sl;
     createdAt = System.nanoTime();
     metrics = m;
@@ -158,7 +160,7 @@
 
   @Override
   public String toString() {
-    String print = "[" + HexFormat.fromInt(id) + "] fetch " + uri;
+    String print = "[" + taskIdHex + "] fetch " + uri;
 
     if (retryCount > 0) {
       print = "(retry " + retryCount + ") " + print;
@@ -288,13 +290,19 @@
     if (!pool.requestRunway(this)) {
       if (!canceled) {
         repLog.info(
-            "Rescheduling replication to {} to avoid collision with an in-flight fetch.", uri);
+            "Rescheduling [{}] replication to {} to avoid collision with an in-flight fetch.",
+            taskIdHex,
+            uri);
         pool.reschedule(this, Source.RetryReason.COLLISION);
       }
       return;
     }
 
-    repLog.info("Replication from {} started...", uri);
+    repLog.info(
+        "Replication [{}] from {} started for refs [{}] ...",
+        taskIdHex,
+        uri,
+        String.join(",", getRefs()));
     Timer1.Context<String> context = metrics.start(config.getName());
     try {
       long startedAt = context.getStartTime();
@@ -308,7 +316,8 @@
               .flatMap(metrics -> metrics.stop(config.getName()))
               .map(NANOSECONDS::toMillis);
       repLog.info(
-          "Replication from {} completed in {}ms, {}ms delay, {} retries{}",
+          "Replication [{}] from {} completed in {}ms, {}ms delay, {} retries{}",
+          taskIdHex,
           uri,
           elapsed,
           delay,
@@ -324,7 +333,8 @@
       // does not exist.  In this case NoRemoteRepositoryException is not
       // raised.
       String msg = e.getMessage();
-      repLog.error("Cannot replicate {}; Remote repository error: {}", projectName, msg);
+      repLog.error(
+          "Cannot replicate [{}] {}; Remote repository error: {}", taskIdHex, projectName, msg);
     } catch (NotSupportedException e) {
       stateLog.error("Cannot replicate from " + uri, e, getStatesAsArray());
     } catch (TransportException e) {
@@ -333,7 +343,7 @@
         lockRetryCount++;
         // The LockFailureException message contains both URI and reason
         // for this failure.
-        repLog.error("Cannot replicate from {}: {}", uri, e.getMessage());
+        repLog.error("Cannot replicate [{}] from {}: {}", taskIdHex, uri, e.getMessage());
 
         // The remote fetch operation should be retried.
         if (lockRetryCount <= maxLockRetries) {
@@ -344,16 +354,17 @@
           }
         } else {
           repLog.error(
-              "Giving up after {} occurrences of this error: {} during replication from {}",
+              "Giving up after {} occurrences of this error: {} during replication from [{}] {}",
               lockRetryCount,
               e.getMessage(),
+              taskIdHex,
               uri);
         }
       } else {
         if (canceledWhileRunning.get()) {
           logCanceledWhileRunningException(e);
         } else {
-          repLog.error("Cannot replicate from {}", uri, e);
+          repLog.error("Cannot replicate [{}] from {}", taskIdHex, uri, e);
           // The remote fetch operation should be retried.
           pool.reschedule(this, Source.RetryReason.TRANSPORT_ERROR);
         }
@@ -371,7 +382,7 @@
   }
 
   private void logCanceledWhileRunningException(TransportException e) {
-    repLog.info("Cannot replicate from {}. It was canceled while running", uri, e);
+    repLog.info("Cannot replicate [{}] from {}. It was canceled while running", taskIdHex, uri, e);
   }
 
   private void runImpl() throws IOException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
index db46b23..cd6a0ea 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
@@ -55,9 +55,13 @@
   private GitRepositoryManager gitRepositoryManager;
   private Long maxRefSize;
   private final int maxDepth;
+  private ApplyObjectMetrics metrics;
 
   @Inject
-  public RevisionReader(GitRepositoryManager gitRepositoryManager, ReplicationConfig cfg) {
+  public RevisionReader(
+      GitRepositoryManager gitRepositoryManager,
+      ReplicationConfig cfg,
+      ApplyObjectMetrics metrics) {
     this.gitRepositoryManager = gitRepositoryManager;
     this.maxRefSize =
         cfg.getConfig()
@@ -65,6 +69,7 @@
     this.maxDepth =
         cfg.getConfig()
             .getInt("replication", CONFIG_MAX_API_HISTORY_DEPTH, DEFAULT_MAX_API_HISTORY_DEPTH);
+    this.metrics = metrics;
   }
 
   public Optional<RevisionData> read(
@@ -146,6 +151,7 @@
 
       return Optional.of(new RevisionData(parentObjectIds, commitRev, treeRev, blobs));
     } catch (LargeObjectException e) {
+      metrics.incrementMaxPayloadSizeReached();
       repLog.trace(
           "Ref {} size for project {} is greater than configured '{}'",
           refName,
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 d86287a..d4b7819 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
@@ -25,7 +25,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.permissions.RefPermission;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
@@ -93,12 +92,6 @@
       URIish sourceUri = source.getURI(name);
 
       try {
-        projectState.get().checkStatePermitsWrite();
-        permissionBackend
-            .currentUser()
-            .project(projectState.get().getNameKey())
-            .ref(refName)
-            .check(RefPermission.DELETE);
 
         Context.setLocalEvent(true);
         deleteRef(name, refName);