Merge branch 'stable-3.9' into stable-3.10

* stable-3.9:
  Update base OS to AlmaLinux 9.3 and Java 17
  Get global-refdb.jar from Gerrit-CI
  Bump Gerrit to 3.9
  Add missing syslog-sidecar in docker-compose.yaml
  Removing obsolete version attribute in docker-compose.yaml

Change-Id: Idfb1f121413f7f93f7949af7af39bce8a939348e
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index 55f7375..b86df20 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -61,6 +61,7 @@
 
   // common parameters to cache and index sections
   static final String THREAD_POOL_SIZE_KEY = "threadPoolSize";
+  static final String INITIAL_DELAY = "initialDelay";
   static final String BATCH_THREAD_POOL_SIZE_KEY = "batchThreadPoolSize";
   static final int DEFAULT_THREAD_POOL_SIZE = 4;
 
@@ -565,6 +566,7 @@
   public static class Index extends Forwarding {
     static final int DEFAULT_MAX_TRIES = 2;
     static final Duration DEFAULT_RETRY_INTERVAL = Duration.ofSeconds(30);
+    static final Duration DEFAULT_INITIAL_DELAY = Duration.ofMillis(0);
 
     static final String INDEX_SECTION = "index";
     static final String MAX_TRIES_KEY = "maxTries";
@@ -573,6 +575,7 @@
     static final boolean DEFAULT_SYNCHRONIZE_FORCED = true;
 
     private final int threadPoolSize;
+    private final long initialDelayMsec;
     private final int batchThreadPoolSize;
     private final Duration retryInterval;
     private final int maxTries;
@@ -581,6 +584,8 @@
     private Index(Config cfg) {
       super(cfg, INDEX_SECTION);
       threadPoolSize = getInt(cfg, INDEX_SECTION, THREAD_POOL_SIZE_KEY, DEFAULT_THREAD_POOL_SIZE);
+      initialDelayMsec =
+          getDuration(cfg, INDEX_SECTION, INITIAL_DELAY, DEFAULT_INITIAL_DELAY).toMillis();
       batchThreadPoolSize = getInt(cfg, INDEX_SECTION, BATCH_THREAD_POOL_SIZE_KEY, threadPoolSize);
       retryInterval = getDuration(cfg, INDEX_SECTION, RETRY_INTERVAL_KEY, DEFAULT_RETRY_INTERVAL);
       maxTries = getMaxTries(cfg, INDEX_SECTION, MAX_TRIES_KEY, DEFAULT_MAX_TRIES);
@@ -592,6 +597,10 @@
       return threadPoolSize;
     }
 
+    public long initialDelayMsec() {
+      return initialDelayMsec;
+    }
+
     public int batchThreadPoolSize() {
       return batchThreadPoolSize;
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ExecutorProvider.java
index 3d3212b..d48df46 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ExecutorProvider.java
@@ -17,14 +17,25 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Provider;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 public abstract class ExecutorProvider
     implements Provider<ScheduledExecutorService>, LifecycleListener {
-  private ScheduledExecutorService executor;
+  private ScheduledWithDelayExecutorService executor;
 
-  protected ExecutorProvider(WorkQueue workQueue, int threadPoolSize, String threadNamePrefix) {
-    executor = workQueue.createQueue(threadPoolSize, threadNamePrefix);
+  protected ExecutorProvider(
+      WorkQueue workQueue, int threadPoolSize, String threadNamePrefix, long scheduleDelayMsec) {
+    executor =
+        new ScheduledWithDelayExecutorService(
+            workQueue.createQueue(threadPoolSize, threadNamePrefix), scheduleDelayMsec);
   }
 
   @Override
@@ -42,4 +53,109 @@
   public ScheduledExecutorService get() {
     return executor;
   }
+
+  private static class ScheduledWithDelayExecutorService implements ScheduledExecutorService {
+    private final ScheduledExecutorService executor;
+    private final long scheduleDelayMsec;
+
+    ScheduledWithDelayExecutorService(
+        ScheduledExecutorService executorService, long scheduleDelayMsec) {
+      this.executor = executorService;
+      this.scheduleDelayMsec = scheduleDelayMsec;
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+      return executor.schedule(
+          callable, unit.toMillis(delay) + scheduleDelayMsec, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+      return executor.schedule(
+          command, unit.toMillis(delay) + scheduleDelayMsec, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(
+        Runnable command, long initialDelay, long period, TimeUnit unit) {
+      return executor.scheduleAtFixedRate(command, initialDelay, period, unit);
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(
+        Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      return executor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return executor.awaitTermination(timeout, unit);
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException {
+      return executor.invokeAll(tasks);
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException {
+      return executor.invokeAll(tasks, timeout, unit);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException, ExecutionException {
+      return executor.invokeAny(tasks);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException, ExecutionException, TimeoutException {
+      return executor.invokeAny(tasks, timeout, unit);
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return executor.isShutdown();
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return executor.isTerminated();
+    }
+
+    @Override
+    public void shutdown() {
+      executor.shutdown();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      return executor.shutdownNow();
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      return executor.submit(task);
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      return executor.submit(task);
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      return executor.submit(task, result);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+      executor.execute(command);
+    }
+  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
index 6fa743f..a328694 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
@@ -17,7 +17,10 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderServletModule;
 import com.ericsson.gerrit.plugins.highavailability.health.HealthServletModule;
 import com.ericsson.gerrit.plugins.highavailability.websession.file.FileBasedWebsessionModule;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.inject.Inject;
+import com.google.inject.Scopes;
 import com.google.inject.servlet.ServletModule;
 
 class HttpModule extends ServletModule {
@@ -37,5 +40,8 @@
     if (config.websession().synchronize()) {
       install(new FileBasedWebsessionModule());
     }
+    DynamicSet.bind(binder(), AllRequestFilter.class)
+        .to(XGerritInstanceFilter.class)
+        .in(Scopes.SINGLETON);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
index 74db7d2..8f37d9c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
@@ -15,7 +15,6 @@
 package com.ericsson.gerrit.plugins.highavailability;
 
 import com.gerritforge.gerrit.globalrefdb.validation.BatchRefUpdateValidator;
-import com.gerritforge.gerrit.globalrefdb.validation.LockWrapper;
 import com.gerritforge.gerrit.globalrefdb.validation.Log4jSharedRefLogger;
 import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
@@ -55,7 +54,6 @@
 
     bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
     bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
-    factory(LockWrapper.Factory.class);
 
     bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/XGerritInstanceFilter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/XGerritInstanceFilter.java
new file mode 100644
index 0000000..a268231
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/XGerritInstanceFilter.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 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.ericsson.gerrit.plugins.highavailability;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.gerrit.server.config.GerritInstanceName;
+import com.google.inject.Inject;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+public class XGerritInstanceFilter extends AllRequestFilter {
+  private static final String X_GERRIT_INSTANCE = "X-Gerrit-Instance";
+
+  private final String instanceId;
+  private final String instanceName;
+
+  @Inject
+  XGerritInstanceFilter(
+      @Nullable @GerritInstanceId String instanceId, @GerritInstanceName String instanceName) {
+    this.instanceId = instanceId;
+    this.instanceName = instanceName;
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    if (response instanceof HttpServletResponse) {
+      ((HttpServletResponse) response)
+          .addHeader(X_GERRIT_INSTANCE, firstNonNull(instanceId, instanceName));
+    }
+    chain.doFilter(request, response);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
index ac102ff..a787d3a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -90,13 +90,13 @@
           return true;
         }
 
-        log.atWarning().log(
+        log.atFine().log(
             "Change %s seems too old compared to the event timestamp (event-Ts=%s >> change-Ts=%s)",
             id, indexEvent, checker);
         return false;
       }
 
-      log.atWarning().log(
+      log.atFine().log(
           "Change %s not present yet in local Git repository (event=%s)", id, indexEvent);
       return false;
 
@@ -113,7 +113,7 @@
 
   private void reindex(ChangeNotes notes) {
     notes.reload();
-    indexer.index(notes);
+    indexer.reindexIfStale(notes.getProjectName(), notes.getChangeId());
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedBatchIndexExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedBatchIndexExecutorProvider.java
index a3a7e64..f82a42d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedBatchIndexExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedBatchIndexExecutorProvider.java
@@ -31,6 +31,9 @@
   @Inject
   ForwardedBatchIndexExecutorProvider(WorkQueue workQueue, Configuration config) {
     super(
-        workQueue, config.index().batchThreadPoolSize(), FORWARDED_BATCH_INDEX_EVENT_THREAD_PREFIX);
+        workQueue,
+        config.index().batchThreadPoolSize(),
+        FORWARDED_BATCH_INDEX_EVENT_THREAD_PREFIX,
+        config.index().initialDelayMsec());
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedIndexExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedIndexExecutorProvider.java
index da623df..d7dd40f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedIndexExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ForwardedIndexExecutorProvider.java
@@ -29,6 +29,10 @@
 
   @Inject
   ForwardedIndexExecutorProvider(WorkQueue workQueue, Configuration config) {
-    super(workQueue, config.index().threadPoolSize(), FORWARDED_INDEX_EVENT_THREAD_PREFIX);
+    super(
+        workQueue,
+        config.index().threadPoolSize(),
+        FORWARDED_INDEX_EVENT_THREAD_PREFIX,
+        config.index().initialDelayMsec());
   }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index d89f7b3..8de1c96 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -280,6 +280,15 @@
     stale and needs to be investigated and manually reindexed.
     Defaults to 2.
 
+```index.initialDelay```
+:   The initial delay, internally converted in milliseconds, of triggering the
+    indexing operation after the indexing even has been received.
+    Typically needed when there is a well-known latency of propagation of the updates
+    across the nodes sharing the same NFS volume.
+    Value is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval).
+    Defaults to 0 milliseconds, meaning that indexing happens immediately when the indexing
+    event is received.
+
 ```index.retryInterval```
 :   The interval of time in milliseconds between the subsequent auto-retries.
     Value is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval).
diff --git a/src/test/docker/docker-compose.yaml b/src/test/docker/docker-compose.yaml
index 8524963..4e8a457 100644
--- a/src/test/docker/docker-compose.yaml
+++ b/src/test/docker/docker-compose.yaml
@@ -7,6 +7,8 @@
     container_name: nfs-server
     environment:
       NFS_LOG_LEVEL: DEBUG
+      NFS_DISABLE_VERSION_3: true
+      NFS_VERSION: 4.2
     hostname: nfs-server
     healthcheck:
       test: ["CMD-SHELL", "sleep 10"] # required, otherwise the gerrit service will fail to start with a "connection refused" error
@@ -16,27 +18,43 @@
     ports:
       - 2049:2049
     networks:
-      nfs-server-bridge:
+      gerrit-net:
         ipv4_address: 192.168.1.5
     volumes:
-      - nfs-server-volume:/var/gerrit/git
+      - nfs-server-volume:/var/gerrit
+
+  zookeeper-refdb:
+    image: zookeeper
+    ports:
+      - "2181:2181"
+    networks:
+      - gerrit-net
+    healthcheck:
+      test: ["CMD-SHELL", "./bin/zkServer.sh", "status"] # required, otherwise the gerrit service will fail to start with a "connection refused" error
+      interval: 1s
+      timeout: 1m
+      retries: 10
+
   gerrit-01:
     build: gerrit
     privileged: true
     depends_on:
       nfs-server:
         condition: service_healthy
+      zookeeper-refdb:
+        condition: service_healthy
     ports:
       - "8081:8080"
       - "29411:29418"
     networks:
-      nfs-server-bridge: null
+      - gerrit-net
     volumes:
       - /dev/urandom:/dev/random
-      - git-volume:/var/gerrit/git
       - shareddir:/var/gerrit/shareddir
+      - ./etc/jgit.config:/var/gerrit/etc/jgit.config.orig
       - ./etc/gerrit.config:/var/gerrit/etc/gerrit.config.orig
       - ./etc/high-availability.gerrit-01.config:/var/gerrit/etc/high-availability.config.orig
+      - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig
     environment:
       - HOSTNAME=localhost
 
@@ -47,7 +65,7 @@
       - "8082:8080"
       - "29412:29418"
     networks:
-      nfs-server-bridge: null
+      - gerrit-net
     depends_on:
       gerrit-01:
         condition: service_started
@@ -55,10 +73,11 @@
         condition: service_healthy
     volumes:
       - /dev/urandom:/dev/random
-      - git-volume:/var/gerrit/git
       - shareddir:/var/gerrit/shareddir
+      - ./etc/jgit.config:/var/gerrit/etc/jgit.config.orig
       - ./etc/gerrit.config:/var/gerrit/etc/gerrit.config.orig
       - ./etc/high-availability.gerrit-02.config:/var/gerrit/etc/high-availability.config.orig
+      - ./etc/zookeeper-refdb.config:/var/gerrit/etc/zookeeper-refdb.config.orig
     environment:
       - HOSTNAME=localhost
       - WAIT_FOR=gerrit-01:8080
@@ -68,10 +87,10 @@
     ports:
       - "80:80"
       - "29418:29418"
-    networks:
-      nfs-server-bridge: null
     volumes:
       - syslog-sidecar:/syslog-sidecar
+    volumes_from:
+      - syslog-sidecar
     depends_on:
       - syslog-sidecar
       - gerrit-01
@@ -80,10 +99,10 @@
   syslog-sidecar:
     build: docker-syslog-ng-stdout
     networks:
-      nfs-server-bridge: null
+      - gerrit-net
 
 networks:
-  nfs-server-bridge:
+  gerrit-net:
     ipam:
       driver: default
       config:
@@ -93,9 +112,3 @@
   shareddir:
   nfs-server-volume:
   syslog-sidecar:
-  git-volume:
-    driver: "local"
-    driver_opts:
-      type: nfs
-      o: "addr=192.168.1.5,rw"
-      device: ":/var/gerrit/git"
diff --git a/src/test/docker/etc/gerrit.config b/src/test/docker/etc/gerrit.config
index 90a4057..2a08db6 100644
--- a/src/test/docker/etc/gerrit.config
+++ b/src/test/docker/etc/gerrit.config
@@ -2,6 +2,8 @@
 	basePath = git
 	canonicalWebUrl = http://gerrit:8080/
 	serverId = f7696647-8efd-41b1-bd60-d321bc071ea9
+	installDbModule = com.ericsson.gerrit.plugins.highavailability.ValidationModule
+	installModule = com.gerritforge.gerrit.globalrefdb.validation.LibModule
 [index]
 	type = LUCENE
 [auth]
diff --git a/src/test/docker/etc/high-availability.gerrit-01.config b/src/test/docker/etc/high-availability.gerrit-01.config
index a21f05c..d8619c1 100644
--- a/src/test/docker/etc/high-availability.gerrit-01.config
+++ b/src/test/docker/etc/high-availability.gerrit-01.config
@@ -6,3 +6,6 @@
 
 [peerInfo "static"]
   url = http://gerrit-02:8080
+
+[ref-database]
+  enabled = true
diff --git a/src/test/docker/etc/high-availability.gerrit-02.config b/src/test/docker/etc/high-availability.gerrit-02.config
index d05c7ec..54cc1f2 100644
--- a/src/test/docker/etc/high-availability.gerrit-02.config
+++ b/src/test/docker/etc/high-availability.gerrit-02.config
@@ -5,4 +5,7 @@
   strategy = static
 
 [peerInfo "static"]
-  url = http://gerrit-01:8080
\ No newline at end of file
+  url = http://gerrit-01:8080
+
+[ref-database]
+  enabled = true
diff --git a/src/test/docker/etc/jgit.config b/src/test/docker/etc/jgit.config
new file mode 100644
index 0000000..6585556
--- /dev/null
+++ b/src/test/docker/etc/jgit.config
@@ -0,0 +1,5 @@
+[core]
+    supportsAtomicFileCreation = false
+    trustFolderStat = false
+    trustLooseRefStat = after_open
+    trustPackedRefsStat = after_open
diff --git a/src/test/docker/etc/zookeeper-refdb.config b/src/test/docker/etc/zookeeper-refdb.config
new file mode 100644
index 0000000..d3cdb80
--- /dev/null
+++ b/src/test/docker/etc/zookeeper-refdb.config
@@ -0,0 +1,4 @@
+[ref-database "zookeeper"]
+  connectString = "zookeeper-refdb:2181"
+  rootNode = "gerrit/HA"
+  transactionLockTimeoutMs = 1000
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile
index 060057d..c863c13 100644
--- a/src/test/docker/gerrit/Dockerfile
+++ b/src/test/docker/gerrit/Dockerfile
@@ -8,9 +8,10 @@
     sudo \
     passwd \
     gettext \
+    nfs-utils \
     && yum -y clean all
 
-ENV GERRIT_VERSION=3.9
+ENV GERRIT_VERSION=3.10
 
 # Add gerrit user
 RUN adduser -p -m --uid 1000 gerrit --home-dir /home/gerrit
@@ -33,10 +34,17 @@
     "https://gerrit-ci.gerritforge.com/job/plugin-high-availability-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/high-availability/high-availability.jar" \
     /var/gerrit/plugins/high-availability.jar
 
+RUN mkdir -p /var/gerrit/lib && \
+    ln -sf /var/gerrit/plugins/high-availability.jar /var/gerrit/lib/high-availability.jar
+
 ADD --chown=gerrit \
     "https://gerrit-ci.gerritforge.com/job/module-global-refdb-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/global-refdb/global-refdb.jar" \
     /var/gerrit/lib/global-refdb.jar
 
+ADD --chown=gerrit \
+    "https://gerrit-ci.gerritforge.com/job/plugin-zookeeper-refdb-bazel-stable-$GERRIT_VERSION/lastSuccessfulBuild/artifact/bazel-bin/plugins/zookeeper-refdb/zookeeper-refdb.jar" \
+    /var/gerrit/plugins/zookeeper-refdb.jar
+
 ADD --chown=gerrit:gerrit ./wait-for-it.sh /bin
 
 # Change user
diff --git a/src/test/docker/gerrit/entrypoint.sh b/src/test/docker/gerrit/entrypoint.sh
index 2d4e387..77d9eac 100755
--- a/src/test/docker/gerrit/entrypoint.sh
+++ b/src/test/docker/gerrit/entrypoint.sh
@@ -6,10 +6,16 @@
 fi
 
 chown -R gerrit /var/gerrit/etc
+sudo -u gerrit cp /var/gerrit/etc/jgit.config.orig /var/gerrit/etc/jgit.config
 sudo -u gerrit cp /var/gerrit/etc/gerrit.config.orig /var/gerrit/etc/gerrit.config
 sudo -u gerrit cp /var/gerrit/etc/high-availability.config.orig /var/gerrit/etc/high-availability.config
+sudo -u gerrit cp /var/gerrit/etc/zookeeper-refdb.config.orig /var/gerrit/etc/zookeeper-refdb.config
 
 
+echo "Mount NFS ..."
+mkdir /var/gerrit/git && chown gerrit:gerrit /var/gerrit/git
+mount -t nfs4 -o actimeo=120 nfs-server:/git /var/gerrit/git
+
 echo "Init gerrit..."
 sudo -u gerrit java -jar /tmp/gerrit.war init -d /var/gerrit --batch --install-all-plugins
 chown -R gerrit: /var/gerrit/shareddir
diff --git a/src/test/docker/haproxy/haproxy.cfg b/src/test/docker/haproxy/haproxy.cfg
index e86cdce..07a6746 100644
--- a/src/test/docker/haproxy/haproxy.cfg
+++ b/src/test/docker/haproxy/haproxy.cfg
@@ -45,7 +45,7 @@
     timeout connect 10s
     timeout server 5m
     server gerrit_ssh_01 gerrit-01:29418 check port 8080 inter 10s fall 3 rise 2
-    server gerrit-ssh_02 gerrit-02:29418 check port 8080 inter 10s fall 3 rise 2 backup
+    server gerrit-ssh_02 gerrit-02:29418 check port 8080 inter 10s fall 3 rise 2
 
 backend gerrit_http_nodes
     mode http
@@ -55,7 +55,7 @@
     option httpchk GET /config/server/version HTTP/1.0
     http-check expect status 200
     server gerrit_01 gerrit-01:8080 check
-    server gerrit_02 gerrit-02:8080 check backup
+    server gerrit_02 gerrit-02:8080 check
 
 backend gerrit_http_nodes_balanced
     mode http
diff --git a/src/test/docker/nfs/Dockerfile b/src/test/docker/nfs/Dockerfile
index 19744f3..c24ee9f 100644
--- a/src/test/docker/nfs/Dockerfile
+++ b/src/test/docker/nfs/Dockerfile
@@ -1,4 +1,4 @@
-FROM erichough/nfs-server
+FROM gerritforge/docker-nfs-server:3.11.3
 
 COPY exports.txt /etc/exports
 
@@ -6,8 +6,8 @@
 # server and the client, hence we are creating it explicitly in both.
 RUN adduser --disabled-password --gecos "" --uid 1000 gerrit
 
-# /var/gerrit/git is the shared directory
-RUN mkdir --parents /var/gerrit/git
-
 RUN chown gerrit:gerrit /var/lib/nfs
-RUN chown gerrit:gerrit /var/gerrit/git
\ No newline at end of file
+
+COPY gerrit-git-mount-entrypoint.sh /usr/local/bin
+
+ENTRYPOINT /usr/local/bin/gerrit-git-mount-entrypoint.sh
diff --git a/src/test/docker/nfs/exports.txt b/src/test/docker/nfs/exports.txt
index c31d586..3e56b08 100644
--- a/src/test/docker/nfs/exports.txt
+++ b/src/test/docker/nfs/exports.txt
@@ -1 +1,2 @@
+/var/gerrit/ 192.168.1.0/24(rw,fsid=0,no_subtree_check,insecure)
 /var/gerrit/git 192.168.1.0/24(rw,no_subtree_check,insecure)
\ No newline at end of file
diff --git a/src/test/docker/nfs/gerrit-git-mount-entrypoint.sh b/src/test/docker/nfs/gerrit-git-mount-entrypoint.sh
new file mode 100755
index 0000000..ca7afcc
--- /dev/null
+++ b/src/test/docker/nfs/gerrit-git-mount-entrypoint.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+MOUNT_POINT=/var/gerrit/git
+
+echo "Creating mountpoint $MOUNT_POINT"
+
+mkdir --parents $MOUNT_POINT
+chown gerrit:gerrit $MOUNT_POINT
+
+echo "ls -al $MOUNT_POINT ..."
+ls -al $MOUNT_POINT
+
+echo "Running NFS server ... "
+echo "======================="
+/usr/local/bin/entrypoint.sh
\ No newline at end of file
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index e158099..b51e1a0 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -59,7 +59,6 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Websession.DEFAULT_CLEANUP_INTERVAL;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Websession.WEBSESSION_SECTION;
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration.PeerInfoStrategy;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
index 427985a..cc6939c 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -32,6 +32,7 @@
 import com.ericsson.gerrit.plugins.highavailability.index.ForwardedIndexExecutorProvider;
 import com.ericsson.gerrit.plugins.highavailability.index.ForwardedIndexFailsafeExecutorProvider;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.OneOffRequestContext;
@@ -61,6 +62,7 @@
 
   @Mock private ChangeIndexer indexerMock;
   @Mock private ChangeNotes changeNotes;
+  @Mock private Project.NameKey projectName;
 
   @Mock(answer = RETURNS_DEEP_STUBS)
   private Configuration configMock;
@@ -92,14 +94,15 @@
   public void changeIsIndexedWhenUpToDate() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()).get(10, SECONDS);
-    verify(indexerMock, times(1)).index(any(ChangeNotes.class));
+    verify(indexerMock, times(1)).reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
   }
 
   @Test
   public void changeIsStillIndexedEvenWhenOutdated() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_OUTDATED);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.of(new IndexEvent())).get(10, SECONDS);
-    verify(indexerMock, atLeast(1)).index(any(ChangeNotes.class));
+    verify(indexerMock, atLeast(1))
+        .reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
   }
 
   @Test
@@ -127,13 +130,13 @@
                   return null;
                 })
         .when(indexerMock)
-        .index(any(ChangeNotes.class));
+        .reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()).get(10, SECONDS);
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ChangeNotes.class));
+    verify(indexerMock, times(1)).reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
   }
 
   @Test
@@ -146,7 +149,7 @@
                   throw new IOException("someMessage");
                 })
         .when(indexerMock)
-        .index(any(ChangeNotes.class));
+        .reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     ExecutionException thrown =
@@ -157,7 +160,7 @@
     assertThat(thrown.getCause()).hasMessageThat().isEqualTo("someMessage");
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ChangeNotes.class));
+    verify(indexerMock, times(1)).reindexIfStale(any(Project.NameKey.class), any(Change.Id.class));
   }
 
   private void setupChangeAccessRelatedMocks(boolean changeExists, boolean changeIsUpToDate)
@@ -166,7 +169,8 @@
       when(changeCheckerFactoryMock.create(TEST_CHANGE_ID)).thenReturn(changeCheckerPresentMock);
       when(changeCheckerPresentMock.getChangeNotes()).thenReturn(Optional.of(changeNotes));
     }
-
+    when(changeNotes.getChangeId()).thenReturn(id);
+    when(changeNotes.getProjectName()).thenReturn(projectName);
     when(changeCheckerPresentMock.isChangeUpToDate(any())).thenReturn(changeIsUpToDate);
   }
 }