Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  Specify cache.threads = 0 in the multi-site config documentation

Change-Id: I7672ca7a64829c24641cd212f19929f22eff3f72
diff --git a/BUILD b/BUILD
index 0284dc6..d7bd3d8 100644
--- a/BUILD
+++ b/BUILD
@@ -12,6 +12,7 @@
     manifest_entries = [
         "Gerrit-PluginName: multi-site",
         "Gerrit-Module: com.googlesource.gerrit.plugins.multisite.PluginModule",
+        "Gerrit-HttpModule: com.googlesource.gerrit.plugins.multisite.http.HttpModule",
         "Implementation-Title: multi-site plugin",
         "Implementation-URL: https://review.gerrithub.io/admin/repos/GerritForge/plugins_multi-site",
     ],
@@ -53,3 +54,41 @@
         "//plugins/replication",
     ],
 )
+
+filegroup(
+    name = "e2e_multi_site_test_dir",
+    srcs = [
+        "e2e-tests",
+    ],
+)
+
+filegroup(
+    name = "e2e_multi_site_setup_local_env_dir",
+    srcs = [
+        "setup_local_env",
+    ],
+)
+
+sh_test(
+    name = "e2e_multi_site_tests",
+    srcs = [
+        "e2e-tests/test.sh",
+    ],
+    data = [
+        "//plugins/multi-site",
+        "//plugins/multi-site:e2e_multi_site_test_dir",
+        "//plugins/multi-site:e2e_multi_site_setup_local_env_dir",
+        "external_plugin_deps.bzl",
+    ] + glob(["setup_local_env/**/*"]) + glob(["e2e-tests/**/*"]),
+    args = [
+        "--multisite-lib-file $(location //plugins/multi-site)",
+        "--healthcheck-interval 5s",
+        "--healthcheck-timeout 10s",
+        "--healthcheck-retries 30",
+        "--location '$(location //plugins/multi-site:e2e_multi_site_test_dir)'",
+        "--local-env '$(location //plugins/multi-site:e2e_multi_site_setup_local_env_dir)'",
+    ],
+    tags = [
+        "e2e-multi-site",
+    ],
+)
diff --git a/README.md b/README.md
index 0bf8486..79094d3 100644
--- a/README.md
+++ b/README.md
@@ -14,12 +14,10 @@
 **NOTE**: The multi-site plugin will not start if Gerrit is not yet migrated
 to NoteDb.
 
-Currently, the only mode supported is one primary read/write master
-and multiple read-only masters but eventually the plan is to support multiple
-read/write masters. The read/write master is handling any traffic while the
-read-only masters are serving the Gerrit GUI assets, the HTTP GET REST API and
-git fetch requests (git-upload-pack). The read-only masters are kept synchronized
-with the read/write master in order to be always ready to become a read/write master.
+Supports multiple read/write masters across multiple sites across different
+geographic locations. The Gerrit nodes are kept synchronized
+between each other using the replication plugin and a global ref-database in
+order to detect and prevent split-brains.
 
 For more details on the overall multi-site design and roadmap, please refer
 to the [multi-site plugin DESIGN.md document](DESIGN.md)
@@ -88,3 +86,8 @@
 You also need to setup the Git-level replication between nodes, for more details
 please refer to the
 [replication plugin documentation](https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md).
+
+# HTTP endpoints
+
+For information about available HTTP endpoints please refer to
+the [documentation](src/main/resources/Documentation/http-endpoints.md).
diff --git a/docker-compose.kafka-broker.yaml b/docker-compose.kafka-broker.yaml
index 927b4d4..d3fc713 100644
--- a/docker-compose.kafka-broker.yaml
+++ b/docker-compose.kafka-broker.yaml
@@ -5,7 +5,7 @@
     ports:
       - "2181:2181"
   kafka:
-    image: wurstmeister/kafka:2.12-2.1.0
+    image: wurstmeister/kafka:2.13-2.6.3
     ports:
       - "9092:9092"
     environment:
diff --git a/e2e-tests/README.md b/e2e-tests/README.md
new file mode 100644
index 0000000..f15c8eb
--- /dev/null
+++ b/e2e-tests/README.md
@@ -0,0 +1,51 @@
+# Local e2e tests
+
+This script configures an environment to simulate a Gerrit Multi-Site setup so
+that automated e2e tests (so far only startup and check if all necessary plugins
+were loaded) could be performed by CI.
+
+The environment is composed by:
+* 2 gerrit instances deployed by default in `/tmp/[random]`
+* 1 zookeeper node
+* 1 broker node (kafka - implemented, kinesis or gcloud-pubsub - to be implemented
+  if/when needed)
+
+## Requirements
+
+- java
+- docker and docker-compose
+- wget
+- envsubst
+- git
+
+## Execution
+
+Tests defined in `scenarios.sh` are called automatically when plugin tests are called:
+
+```bash
+bazel test //plugins/multi-site/...
+```
+
+But one can also start them independently with:
+
+```bash
+./test.sh
+```
+
+Upon start docker logs are tailed to file in deployment dir. They will be printed to
+the console (together with `docker-compose.yaml` used to start the setup) upon tests
+failure (`test.sh` will exit with `1` in such case). No ports/volumes are exposed and
+multiple test instances can be started in the same CI node (internal docker network is
+used to perform tests).
+
+# Assumptions
+
+When called in the independent mode it is assumed that `multi-site` jar is available
+under in-tree gerrit repository that is related to the `multi-site` repo (e.g.
+`../../../bazel-bin/plugins/[multi-site/multi-site|replication/replication].jar`).
+It can be further customised with `--multisite-lib-file` execution option. Full list
+of options can be obtained with:
+
+```bash
+./test.sh --help
+```
diff --git a/e2e-tests/docker-compose-kafka.yaml b/e2e-tests/docker-compose-kafka.yaml
new file mode 100644
index 0000000..addb8d5
--- /dev/null
+++ b/e2e-tests/docker-compose-kafka.yaml
@@ -0,0 +1,17 @@
+version: '3'
+services:
+  kafka:
+    image: wurstmeister/kafka:2.13-2.6.3
+    depends_on:
+      - zookeeper
+    environment:
+      KAFKA_ADVERTISED_HOST_NAME: kafka
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+
+  gerrit1:
+    depends_on:
+      - kafka
+
+  gerrit2:
+    depends_on:
+      - kafka
\ No newline at end of file
diff --git a/e2e-tests/docker-compose.yaml b/e2e-tests/docker-compose.yaml
new file mode 100644
index 0000000..3abb757
--- /dev/null
+++ b/e2e-tests/docker-compose.yaml
@@ -0,0 +1,36 @@
+version: '3'
+services:
+  zookeeper:
+    image: wurstmeister/zookeeper:latest
+
+  gerrit1:
+    image: gerritcodereview/gerrit:${GERRIT_IMAGE}
+    depends_on:
+      - zookeeper
+    volumes:
+      - "gerrit1_git:/var/gerrit/git"
+      - "gerrit2_git:/var/gerrit/git-instance2"
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/config/server/healthcheck~status"]
+      start_period: ${GERRIT_HEALTHCHECK_START_PERIOD:-60s}
+      interval: ${GERRIT_HEALTHCHECK_INTERVAL-5s}
+      timeout: ${GERRIT_HEALTHCHECK_TIMEOUT-5s}
+      retries: ${GERRIT_HEALTHCHECK_RETRIES-5}
+
+  gerrit2:
+    image: gerritcodereview/gerrit:${GERRIT_IMAGE}
+    depends_on:
+      - zookeeper
+    volumes:
+      - "gerrit2_git:/var/gerrit/git"
+      - "gerrit1_git:/var/gerrit/git-instance1"
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/config/server/healthcheck~status"]
+      start_period: ${GERRIT_HEALTHCHECK_START_PERIOD:-60s}
+      interval: ${GERRIT_HEALTHCHECK_INTERVAL:-5s}
+      timeout: ${GERRIT_HEALTHCHECK_TIMEOUT:-5s}
+      retries: ${GERRIT_HEALTHCHECK_RETRIES:-5}
+
+volumes:
+  gerrit1_git:
+  gerrit2_git:
\ No newline at end of file
diff --git a/e2e-tests/docker-tester.yaml b/e2e-tests/docker-tester.yaml
new file mode 100644
index 0000000..4b8bf58
--- /dev/null
+++ b/e2e-tests/docker-tester.yaml
@@ -0,0 +1,11 @@
+version: '3'
+services:
+  tester:
+    image: gerritcodereview/gerrit:${GERRIT_IMAGE}
+    user: root
+    depends_on:
+      gerrit1:
+        condition: service_healthy
+      gerrit2:
+        condition: service_healthy
+    entrypoint: ["/var/gerrit/scenarios.sh"]
diff --git a/e2e-tests/scenarios.sh b/e2e-tests/scenarios.sh
new file mode 100755
index 0000000..c61c635
--- /dev/null
+++ b/e2e-tests/scenarios.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SSH_OPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
+
+function call_gerrit {
+  SERVER=$1
+  shift
+  su gerrit -c "ssh -p 29418 ${SSH_OPTS} admin@${SERVER} gerrit \"$@\""
+}
+
+
+chown -R gerrit:gerrit /var/gerrit/.ssh
+
+call_gerrit gerrit1 version || exit 1;
+call_gerrit gerrit2 version || exit 1;
+echo "All tests were finished successfully."
diff --git a/e2e-tests/test.sh b/e2e-tests/test.sh
new file mode 100755
index 0000000..19c8b04
--- /dev/null
+++ b/e2e-tests/test.sh
@@ -0,0 +1,366 @@
+#!/bin/bash
+
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCATION="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+LOCAL_ENV="$( cd "${LOCATION}/../setup_local_env" >/dev/null 2>&1 && pwd )"
+GERRIT_BRANCH=stable-3.3
+GERRIT_CI=https://archive-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job
+LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
+DEF_MULTISITE_LOCATION=${LOCATION}/../../../bazel-bin/plugins/multi-site/multi-site.jar
+DEF_GERRIT_IMAGE=3.3.6-centos8
+DEF_GERRIT_HEALTHCHECK_START_PERIOD=60s
+DEF_GERRIT_HEALTHCHECK_INTERVAL=5s
+DEF_GERRIT_HEALTHCHECK_TIMEOUT=5s
+DEF_GERRIT_HEALTHCHECK_RETRIES=5
+
+function check_application_requirements {
+  type java >/dev/null 2>&1 || { echo >&2 "Require java but it's not installed. Aborting."; exit 1; }
+  type docker >/dev/null 2>&1 || { echo >&2 "Require docker but it's not installed. Aborting."; exit 1; }
+  type docker-compose >/dev/null 2>&1 || { echo >&2 "Require docker-compose but it's not installed. Aborting."; exit 1; }
+  type wget >/dev/null 2>&1 || { echo >&2 "Require wget but it's not installed. Aborting."; exit 1; }
+  type envsubst >/dev/null 2>&1 || { echo >&2 "Require envsubst but it's not installed. Aborting."; exit 1; }
+  type openssl >/dev/null 2>&1 || { echo >&2 "Require openssl but it's not installed. Aborting."; exit 1; }
+  type git >/dev/null 2>&1 || { echo >&2 "Require git but it's not installed. Aborting."; exit 1; }
+}
+
+function setup_zookeeper_config {
+  SOURCE_ZOOKEEPER_CONFIG=${LOCAL_ENV}/configs/zookeeper-refdb.config
+  DESTINATION_ZOOKEEPER_CONFIG=$1
+
+  export ZK_HOST=zookeeper
+  export ZK_PORT=2181
+
+  echo "Replacing variables for file ${SOURCE_ZOOKEEPER_CONFIG} and copying to ${DESTINATION_ZOOKEEPER_CONFIG}"
+  cat $SOURCE_ZOOKEEPER_CONFIG | envsubst | sed 's/#{name}#/${name}/g' > $DESTINATION_ZOOKEEPER_CONFIG
+}
+
+function setup_replication_config {
+  SOURCE_REPLICATION_CONFIG=${LOCAL_ENV}/configs/replication.config
+  DESTINATION_REPLICATION_CONFIG=$1
+
+  export REPLICATION_URL="url = $2"
+  export REPLICATION_DELAY_SEC=1
+
+  echo "Replacing variables for file ${SOURCE_REPLICATION_CONFIG} and copying to ${DESTINATION_REPLICATION_CONFIG}"
+  cat $SOURCE_REPLICATION_CONFIG | envsubst | sed 's/#{name}#/${name}/g' > $DESTINATION_REPLICATION_CONFIG
+}
+
+function setup_gerrit_config {
+  SOURCE_RGERRIT_CONFIG=${LOCAL_ENV}/configs/gerrit.config
+  DESTINATION_GERRIT_CONFIG=$1
+
+  export BROKER_HOST=$2
+  export BROKER_PORT=$3
+  export GROUP_ID=$4
+  export SSH_ADVERTISED_PORT=$5
+  export LOCATION_TEST_SITE=/var/gerrit
+  export REMOTE_DEBUG_PORT=5005
+  export GERRIT_SSHD_PORT=29418
+  export HTTP_PROTOCOL=http
+  export GERRIT_HTTPD_PORT=8080
+
+  echo "Replacing variables for file ${SOURCE_RGERRIT_CONFIG} and copying to ${DESTINATION_GERRIT_CONFIG}"
+  cat $SOURCE_RGERRIT_CONFIG | envsubst | sed 's/#{name}#/${name}/g' > $DESTINATION_GERRIT_CONFIG
+
+  # set plugins for multi-site as mandatory so that gerrit will not start if they are not loaded
+  declare -a MANDATORY_PLUGINS=(${BROKER_PLUGIN} multi-site replication websession-broker zookeeper-refdb)
+  for plugin in "${MANDATORY_PLUGINS[@]}"
+  do
+    git config --file $DESTINATION_GERRIT_CONFIG --add plugins.mandatory "${plugin}"
+  done
+}
+
+function cleanup_tests_hook {
+  echo "Shutting down the setup"
+  docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml down -v
+  echo "Removing setup dir"
+  rm -rf ${DEPLOYMENT_LOCATION}
+}
+
+function check_result {
+  local CONTAINER_ID=$1
+  local OLD_RES=$2
+
+  if [ $OLD_RES -ne 0 ]; then
+    return $OLD_RES;
+  fi
+
+  local RES_VAL=$(docker inspect -f '{{ .State.ExitCode }}' "${CONTAINER_ID}")
+  # first check if RES_VAL (output) is a number
+  if [[ -z "${RES_VAL##*[!0-9]*}" || $RES_VAL -ne 0 ]]; then
+    echo "Tests failed. Here is [docker-compose.yaml] content:"
+    cat "${DEPLOYMENT_LOCATION}/docker-compose.yaml"
+
+    echo "Docker logs:"
+    cat "${DEPLOYMENT_LOCATION}/site.log"
+    return 1
+  fi
+
+  return 0
+}
+
+# Check application requirements
+check_application_requirements
+
+while [ $# -ne 0 ]
+do
+case "$1" in
+  "--help" )
+    echo "Usage: sh $0 [--option $value]"
+    echo
+    echo "[--gerrit-image]                Gerrit docker image to be used for testing; defaults to [${DEF_GERRIT_IMAGE}]"
+    echo "[--multisite-lib-file]          Location to lib multi-site.jar file; defaults to [${DEF_MULTISITE_LOCATION}]"
+    echo "[--broker-type]                 events broker type; 'kafka', 'kinesis' or 'gcloud-pubsub'. Default 'kafka' TODO: so far only 'kafka'"
+    echo "[--start-period]                Gerrit start period timeout (until it gets healthy); defaults to [${DEF_GERRIT_HEALTHCHECK_START_PERIOD}]"
+    echo "[--healthcheck-interval]        Every interval Gerrit health check is performed; defaults to [${DEF_GERRIT_HEALTHCHECK_INTERVAL}]"
+    echo "[--healthcheck-timeout]         If a single run of the check takes longer than timeout it is considered a failure; defaults to [${DEF_GERRIT_HEALTHCHECK_TIMEOUT}]"
+    echo "[--healthcheck-retries]         How many consequtive times health check can fail to consider Gerrit server unhealthy; defaults to [${DEF_GERRIT_HEALTHCHECK_RETRIES}]"
+    echo "[--location]                    Directory in which this script resides. Needed only when called from 'bazel test'."
+    echo "[--local-env]                   'setup_local_env' directory location. Needed only when called from 'bazel test'."
+    echo
+    exit 0
+  ;;
+  "--gerrit-image" )
+    GERRIT_IMAGE=$2
+    shift
+    shift
+  ;;
+  "--multisite-lib-file" )
+    MULTISITE_LIB_LOCATION=$2
+    shift
+    shift
+  ;;
+  "--replication-lib-file" )
+    REPLICATION_LIB_LOCATION=$2
+    shift
+    shift
+  ;;
+  "--broker-type" )
+    BROKER_TYPE=$2
+    shift
+    shift
+    if [ ! "$BROKER_TYPE" = "kafka" ] && [ ! "$BROKER_TYPE" = "kinesis" ] && [ ! "$BROKER_TYPE" = "gcloud-pubsub" ]; then
+      echo >&2 "broker type: '$BROKER_TYPE' not valid. Please supply 'kafka','kinesis' or 'gcloud-pubsub'. Aborting"
+      exit 1
+    fi
+  ;;
+  "--start-period" )
+    GERRIT_HEALTHCHECK_START_PERIOD=$2
+    shift
+    shift
+  ;;
+  "--healthcheck-interval" )
+    GERRIT_HEALTHCHECK_INTERVAL=$2
+    shift
+    shift
+  ;;
+  "--healthcheck-timeout" )
+    GERRIT_HEALTHCHECK_TIMEOUT=$2
+    shift
+    shift
+  ;;
+  "--healthcheck-retries" )
+    GERRIT_HEALTHCHECK_RETRIES=$2
+    shift
+    shift
+  ;;
+  "--location" )
+    LOCATION=$2
+    shift
+    shift
+  ;;
+  "--local-env" )
+    LOCAL_ENV=$2
+    shift
+    shift
+  ;;
+  *     )
+    echo "Unknown option argument: $1"
+    shift
+    shift
+  ;;
+esac
+done
+
+# Defaults
+EVENTS_BROKER_VER=`grep 'com.gerritforge:events-broker' ${LOCATION}/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
+GLOBAL_REFDB_VER=`grep 'com.gerritforge:global-refdb' ${LOCATION}/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
+DEPLOYMENT_LOCATION=$(mktemp -d || $(echo >&2 "Could not create temp dir" && exit 1))
+MULTISITE_LIB_LOCATION=${MULTISITE_LIB_LOCATION:-${DEF_MULTISITE_LOCATION}}
+BROKER_TYPE=${BROKER_TYPE:-"kafka"}
+GERRIT_IMAGE=${GERRIT_IMAGE:-${DEF_GERRIT_IMAGE}}
+GERRIT_HEALTHCHECK_START_PERIOD=${GERRIT_HEALTHCHECK_START_PERIOD:-${DEF_GERRIT_HEALTHCHECK_START_PERIOD}}
+GERRIT_HEALTHCHECK_INTERVAL=${GERRIT_HEALTHCHECK_INTERVAL:-${DEF_GERRIT_HEALTHCHECK_INTERVAL}}
+GERRIT_HEALTHCHECK_TIMEOUT=${GERRIT_HEALTHCHECK_TIMEOUT:-${DEF_GERRIT_HEALTHCHECK_TIMEOUT}}
+GERRIT_HEALTHCHECK_RETRIES=${GERRIT_HEALTHCHECK_RETRIES:-${DEF_GERRIT_HEALTHCHECK_RETRIES}}
+
+# Gerrit primary
+GERRIT_1_ETC=${DEPLOYMENT_LOCATION}/etc_1
+GERRIT_1_PLUGINS=${DEPLOYMENT_LOCATION}/plugins_1
+GERRIT_1_LIBS=${DEPLOYMENT_LOCATION}/libs_1
+
+# Gerrit secondary
+GERRIT_2_ETC=${DEPLOYMENT_LOCATION}/etc_2
+GERRIT_2_PLUGINS=${DEPLOYMENT_LOCATION}/plugins_2
+GERRIT_2_LIBS=${DEPLOYMENT_LOCATION}/libs_2
+
+echo "Deployment location: [${DEPLOYMENT_LOCATION}]"
+
+echo "Downloading common plugins"
+COMMON_PLUGINS=${DEPLOYMENT_LOCATION}/common_plugins
+mkdir -p ${COMMON_PLUGINS}
+
+echo "plugin location[${MULTISITE_LIB_LOCATION}]"
+cp -f $MULTISITE_LIB_LOCATION $COMMON_PLUGINS/multi-site.jar  >/dev/null 2>&1 || \
+  { echo >&2 "$MULTISITE_LIB_LOCATION: Not able to copy the file. Aborting"; exit 1; }
+
+echo "Downloading websession-broker plugin $GERRIT_BRANCH"
+wget $GERRIT_CI/plugin-websession-broker-bazel-$GERRIT_BRANCH/$LAST_BUILD/websession-broker/websession-broker.jar \
+  -O $COMMON_PLUGINS/websession-broker.jar || { echo >&2 "Cannot download websession-broker plugin: Check internet connection. Aborting"; exit 1; }
+
+echo "Downloading healthcheck plugin $GERRIT_BRANCH"
+wget $GERRIT_CI/plugin-healthcheck-bazel-$GERRIT_BRANCH/$LAST_BUILD/healthcheck/healthcheck.jar \
+  -O $COMMON_PLUGINS/healthcheck.jar || { echo >&2 "Cannot download healthcheck plugin: Check internet connection. Aborting"; exit 1; }
+
+echo "Downloading zookeeper plugin $GERRIT_BRANCH"
+wget $GERRIT_CI/plugin-zookeeper-refdb-bazel-$GERRIT_BRANCH/$LAST_BUILD/zookeeper-refdb/zookeeper-refdb.jar \
+  -O $COMMON_PLUGINS/zookeeper-refdb.jar || { echo >&2 "Cannot download zookeeper plugin: Check internet connection. Aborting"; exit 1; }
+
+if [ "$BROKER_TYPE" = "kafka" ]; then
+  echo "Downloading events-kafka plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-events-kafka-bazel-$GERRIT_BRANCH/$LAST_BUILD/events-kafka/events-kafka.jar \
+    -O $COMMON_PLUGINS/events-kafka.jar || { echo >&2 "Cannot download events-kafka plugin: Check internet connection. Aborting"; exit 1; }
+  BROKER_PORT=9092
+  BROKER_HOST=kafka
+  BROKER_PLUGIN=events-kafka
+else
+  #TODO add more broker types handling
+  echo >&2 "Broker type [${BROKER_TYPE}] not supported. Aborting";
+  exit 1;
+fi
+
+echo "Downloading common libs"
+COMMON_LIBS=${DEPLOYMENT_LOCATION}/common_libs
+mkdir -p ${COMMON_LIBS}
+
+echo "Getting replication.jar as a library"
+CONTAINER_NAME=$(docker create -ti --entrypoint /bin/bash gerritcodereview/gerrit:"${GERRIT_IMAGE}") && \
+docker cp ${CONTAINER_NAME}:/var/gerrit/plugins/replication.jar $COMMON_LIBS/
+docker rm -fv ${CONTAINER_NAME}
+
+echo "Downloading global-refdb library $GERRIT_BRANCH"
+wget https://repo1.maven.org/maven2/com/gerritforge/global-refdb/$GLOBAL_REFDB_VER/global-refdb-$GLOBAL_REFDB_VER.jar \
+  -O $COMMON_LIBS/global-refdb.jar || { echo >&2 "Cannot download global-refdb library: Check internet connection. Aborting"; exit 1; }
+
+echo "Downloading events-broker library $GERRIT_BRANCH"
+wget https://repo1.maven.org/maven2/com/gerritforge/events-broker/$EVENTS_BROKER_VER/events-broker-$EVENTS_BROKER_VER.jar \
+  -O $COMMON_LIBS/events-broker.jar || { echo >&2 "Cannot download events-broker library: Check internet connection. Aborting"; exit 1; }
+
+echo "Setting up directories"
+mkdir -p ${GERRIT_1_ETC} ${GERRIT_1_PLUGINS} ${GERRIT_1_LIBS} ${GERRIT_2_ETC} ${GERRIT_2_PLUGINS} ${GERRIT_2_LIBS}
+
+echo "Copying plugins"
+cp -f $COMMON_PLUGINS/* ${GERRIT_1_PLUGINS}
+cp -f $COMMON_PLUGINS/* ${GERRIT_2_PLUGINS}
+
+echo "Copying libs"
+cp -f $COMMON_LIBS/* ${GERRIT_1_LIBS}
+cp -f $COMMON_PLUGINS/multi-site.jar ${GERRIT_1_LIBS}
+cp -f $COMMON_LIBS/* ${GERRIT_2_LIBS}
+cp -f $COMMON_PLUGINS/multi-site.jar ${GERRIT_2_LIBS}
+
+echo "Setting up configuration"
+echo "Setup healthcheck config"
+cp -f ${LOCAL_ENV}/configs/healthcheck.config $GERRIT_1_ETC
+cp -f ${LOCAL_ENV}/configs/healthcheck.config $GERRIT_2_ETC
+
+echo "Setup multi-site config"
+cp -f ${LOCAL_ENV}/configs/multi-site.config $GERRIT_1_ETC
+cp -f ${LOCAL_ENV}/configs/multi-site.config $GERRIT_2_ETC
+
+echo "Setup zookeeper config"
+setup_zookeeper_config "${GERRIT_1_ETC}/zookeeper-refdb.config"
+setup_zookeeper_config "${GERRIT_2_ETC}/zookeeper-refdb.config"
+
+echo "Setup replication config"
+setup_replication_config "${GERRIT_1_ETC}/replication.config" 'file:///var/gerrit/git-instance2/${name}.git'
+setup_replication_config "${GERRIT_2_ETC}/replication.config" 'file:///var/gerrit/git-instance1/${name}.git'
+
+echo "Setup gerrit config"
+setup_gerrit_config "${GERRIT_1_ETC}/gerrit.config" $BROKER_HOST $BROKER_PORT instance-1 29418
+setup_gerrit_config "${GERRIT_2_ETC}/gerrit.config" $BROKER_HOST $BROKER_PORT instance-2 29419
+
+echo "Generating common SSH key for tests"
+COMMON_SSH=${DEPLOYMENT_LOCATION}/common_ssh
+mkdir -p ${COMMON_SSH}
+ssh-keygen -b 2048 -t rsa -f ${COMMON_SSH}/id_rsa -q -N "" || { echo >&2 "Cannot generate common SSH keys. Aborting"; exit 1; }
+
+SCENARIOS="$( cd "${LOCATION}" >/dev/null 2>&1 && pwd )/scenarios.sh"
+
+echo "Starting containers"
+COMPOSE_FILES="-f ${LOCATION}/docker-compose.yaml -f ${LOCATION}/docker-compose-kafka.yaml -f ${LOCATION}/docker-tester.yaml"
+
+# store setup in single file (under ${DEPLOYMENT_LOCATION}) with all variables resolved
+export GERRIT_IMAGE; \
+  export GERRIT_HEALTHCHECK_START_PERIOD; \
+  export GERRIT_HEALTHCHECK_INTERVAL; \
+  export GERRIT_HEALTHCHECK_TIMEOUT; \
+  export GERRIT_HEALTHCHECK_RETRIES; \
+  docker-compose ${COMPOSE_FILES} config > ${DEPLOYMENT_LOCATION}/docker-compose.yaml
+
+trap cleanup_tests_hook EXIT
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml up -d zookeeper kafka
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -a
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml logs -f --no-color -t > ${DEPLOYMENT_LOCATION}/site.log &
+
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml up --no-start gerrit1 gerrit2
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -a
+GERRIT1_CONTAINER=$(docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -q gerrit1)
+GERRIT2_CONTAINER=$(docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -q gerrit2)
+
+#copy files to gerrit containers
+echo "Copying files to Gerrit containers"
+docker cp "${GERRIT_1_ETC}/" "${GERRIT1_CONTAINER}:/var/gerrit/etc"
+docker cp "${GERRIT_1_PLUGINS}/" "${GERRIT1_CONTAINER}:/var/gerrit/plugins"
+docker cp "${GERRIT_1_LIBS}/" "${GERRIT1_CONTAINER}:/var/gerrit/libs"
+docker cp "${COMMON_SSH}/" "${GERRIT1_CONTAINER}:/var/gerrit/.ssh"
+
+docker cp "${GERRIT_2_ETC}/" "${GERRIT2_CONTAINER}:/var/gerrit/etc"
+docker cp "${GERRIT_2_PLUGINS}/" "${GERRIT2_CONTAINER}:/var/gerrit/plugins"
+docker cp "${GERRIT_2_LIBS}/" "${GERRIT2_CONTAINER}:/var/gerrit/libs"
+docker cp "${COMMON_SSH}/" "${GERRIT2_CONTAINER}:/var/gerrit/.ssh"
+
+echo "Starting Gerrit servers"
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml up -d gerrit1 gerrit2
+
+echo "Waiting for services to start (and being healthy) and calling e2e tests"
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml up --no-start tester
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -a
+TEST_CONTAINER=$(docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml ps -q tester)
+docker cp "${COMMON_SSH}/" "${TEST_CONTAINER}:/var/gerrit/.ssh"
+docker cp "${SCENARIOS}" "${TEST_CONTAINER}:/var/gerrit/scenarios.sh"
+
+docker-compose -f ${DEPLOYMENT_LOCATION}/docker-compose.yaml up tester
+
+# inspect test container exit code as 'up' always returns '0'
+check_result "${TEST_CONTAINER}" 0
+RES_VAL=$?
+check_result "${GERRIT1_CONTAINER}" ${RES_VAL}
+RES_VAL=$?
+check_result "${GERRIT2_CONTAINER}" ${RES_VAL}
+RES_VAL=$?
+
+exit $RES_VAL
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index fee8aca..223778e 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,12 +3,12 @@
 def external_plugin_deps():
     maven_jar(
         name = "global-refdb",
-        artifact = "com.gerritforge:global-refdb:3.1.2",
-        sha1 = "6ddee3de0f3fe9254453118ae1eca481ec03e957",
+        artifact = "com.gerritforge:global-refdb:3.3.1",
+        sha1 = "5df9dddad2fc67c922406f41549186b210cd957e",
     )
 
     maven_jar(
         name = "events-broker",
-        artifact = "com.gerritforge:events-broker:3.2.0-rc4",
-        sha1 = "53e3f862ac2c2196dba716756ac9586f4b63af47",
+        artifact = "com.gerritforge:events-broker:3.3.2",
+        sha1 = "d8bcb77047cc12dd7c623b5b4de70a25499d3d6c",
     )
diff --git a/setup_local_env/README.md b/setup_local_env/README.md
index 5b99996..88b40d5 100644
--- a/setup_local_env/README.md
+++ b/setup_local_env/README.md
@@ -4,7 +4,8 @@
 The environment is composed by:
 
 - 2 gerrit instances deployed by default in /tmp
-- 1 kafka node and 1 zookeeper node
+- 1 zookeeper node
+- 1 Broker node (kafka, kinesis or gcloud-pubsub)
 - 1 HA-PROXY
 
 ## Requirements
@@ -14,15 +15,36 @@
 - wget
 - envsubst
 - haproxy
+- aws-cli (only when broker_type is "kinesis")
 
 ## Examples
 
-Simplest setup with all default values and cleanup previous deployment
+Simplest setup with all default values and cleanup previous deployment. This
+will deploy kafka broker
 
 ```bash
 sh setup_local_env/setup.sh --release-war-file /path/to/gerrit.war --multisite-lib-file /path/to/multi-site.jar
 ```
 
+Deploy Kinesis broker
+
+```bash
+sh setup_local_env/setup.sh \
+    --release-war-file /path/to/gerrit.war \
+    --multisite-lib-file /path/to/multi-site.jar \
+    --broker-type kinesis
+```
+
+Deploy GCloud PubSub broker
+
+```bash
+sh setup_local_env/setup.sh \
+    --release-war-file /path/to/gerrit.war \
+    --multisite-lib-file /path/to/multi-site.jar \
+    --broker-type gcloud-pubsub
+```
+
+
 Cleanup the previous deployments
 
 ```bash
@@ -52,13 +74,15 @@
 [--gerrit2-httpd-port]          Gerrit Instance 2 http port; default 18081
 [--gerrit2-sshd-port]           Gerrit Instance 2 sshd port; default 49418
 
-[--replication-type]            Options [file,ssh]; default ssh
+[--replication-type]            Options [file,ssh]; default file
 [--replication-ssh-user]        SSH user for the replication plugin; default $(whoami)
 [--replication-delay]           Replication delay across the two instances in seconds
 
 [--just-cleanup-env]            Cleans up previous deployment; default false
 
 [--enabled-https]               Enabled https; default true
+
+[--broker_type]                 events broker type; 'kafka', 'kinesis' or 'gcloud-pubsub'. Default 'kafka'
 ```
 
 ## Limitations
diff --git a/setup_local_env/configs/gerrit.config b/setup_local_env/configs/gerrit.config
index 884b5be..2cd8c9d 100644
--- a/setup_local_env/configs/gerrit.config
+++ b/setup_local_env/configs/gerrit.config
@@ -18,6 +18,7 @@
 [container]
     javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
     javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
+    javaOptions = "-DPUBSUB_EMULATOR_HOST=localhost:$BROKER_PORT"
     javaOptions = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$REMOTE_DEBUG_PORT"
 [index]
     type = LUCENE
@@ -41,13 +42,25 @@
     directory = $FAKE_NFS
 [plugin "events-kafka"]
     sendAsync = true
-    bootstrapServers = localhost:$KAFKA_PORT
-    groupId = $KAFKA_GROUP_ID
+    bootstrapServers = $BROKER_HOST:$BROKER_PORT
+    groupId = $GROUP_ID
     numberOfSubscribers = 6
     securityProtocol = PLAINTEXT
     pollingIntervalMs = 1000
     enableAutoCommit = true
     autoCommitIntervalMs = 1000
     autoOffsetReset = latest
+[plugin "events-aws-kinesis"]
+    numberOfSubscribers = 6
+    pollingIntervalMs = 1000
+    region = us-east-1
+    endpoint = http://localhost:$BROKER_PORT
+    applicationName = $GROUP_ID
+    initialPosition = trim_horizon
+[plugin "events-gcloud-pubsub"]
+    numberOfSubscribers = 6
+    gcloudProject="test-project"
+    subscriptionId=$GROUP_ID
+    privateKeyLocation="not used in local mode"
 [plugin "metrics-reporter-prometheus"]
     prometheusBearerToken = token
diff --git a/setup_local_env/configs/zookeeper-refdb.config b/setup_local_env/configs/zookeeper-refdb.config
index 2c84a05..9eb0789 100644
--- a/setup_local_env/configs/zookeeper-refdb.config
+++ b/setup_local_env/configs/zookeeper-refdb.config
@@ -1,2 +1,2 @@
 [ref-database "zookeeper"]
-	connectString = localhost:$ZK_PORT
\ No newline at end of file
+	connectString = $ZK_HOST:$ZK_PORT
\ No newline at end of file
diff --git a/setup_local_env/docker-compose.yaml b/setup_local_env/docker-compose-core.yaml
similarity index 61%
rename from setup_local_env/docker-compose.yaml
rename to setup_local_env/docker-compose-core.yaml
index c386d46..dab168d 100644
--- a/setup_local_env/docker-compose.yaml
+++ b/setup_local_env/docker-compose-core.yaml
@@ -5,15 +5,8 @@
     ports:
       - "2181:2181"
     container_name: zk_test_node
-  kafka:
-    image: wurstmeister/kafka:2.12-2.1.0
-    ports:
-      - "9092:9092"
-    container_name: kafka_test_node
-    environment:
-      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
-      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
   prometheus:
+    container_name: prometheus_test_node
     image: prom/prometheus:v2.16.0
     user: root
     volumes:
diff --git a/setup_local_env/docker-compose-gcloud-pubsub.yaml b/setup_local_env/docker-compose-gcloud-pubsub.yaml
new file mode 100644
index 0000000..983e784
--- /dev/null
+++ b/setup_local_env/docker-compose-gcloud-pubsub.yaml
@@ -0,0 +1,13 @@
+version: '3'
+services:
+  pubsub:
+    image: gcr.io/google.com/cloudsdktool/cloud-sdk:316.0.0-emulators
+    ports:
+      - "8085:8085"
+    container_name: gcloud-pubsub_test_node
+    entrypoint: gcloud beta emulators pubsub start --project test-project --host-port 0.0.0.0:8085
+    networks:
+      - setup_local_env_default
+networks:
+  setup_local_env_default:
+    external: true
diff --git a/setup_local_env/docker-compose-kafka.yaml b/setup_local_env/docker-compose-kafka.yaml
new file mode 100644
index 0000000..2a5b0fc
--- /dev/null
+++ b/setup_local_env/docker-compose-kafka.yaml
@@ -0,0 +1,15 @@
+version: '3'
+services:
+  kafka:
+    image: wurstmeister/kafka:2.13-2.6.3
+    ports:
+      - "9092:9092"
+    container_name: kafka_test_node
+    environment:
+      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+    networks:
+      - setup_local_env_default
+networks:
+  setup_local_env_default:
+    external: true
diff --git a/setup_local_env/docker-compose-kinesis.yaml b/setup_local_env/docker-compose-kinesis.yaml
new file mode 100644
index 0000000..04429af
--- /dev/null
+++ b/setup_local_env/docker-compose-kinesis.yaml
@@ -0,0 +1,17 @@
+version: '3'
+services:
+  kinesis:
+    image: localstack/localstack:0.12.17.5
+    ports:
+      - "4566:4566"
+      - "4751:4751"
+    container_name: kinesis_test_node
+    environment:
+      SERVICES: dynamodb,cloudwatch,kinesis
+      AWS_REGION: us-east-1
+      USE_SSL: "true"
+    networks:
+      - setup_local_env_default
+networks:
+  setup_local_env_default:
+    external: true
\ No newline at end of file
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index 7e3dc94..11ae04f 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -16,10 +16,11 @@
 
 
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-GERRIT_BRANCH=stable-3.2
+GERRIT_BRANCH=stable-3.3
 GERRIT_CI=https://archive-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job
 LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
 EVENTS_BROKER_VER=`grep 'com.gerritforge:events-broker' $(dirname $0)/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
+GLOBAL_REFDB_VER=`grep 'com.gerritforge:global-refdb' $(dirname $0)/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
 
 function check_application_requirements {
   type haproxy >/dev/null 2>&1 || { echo >&2 "Require haproxy but it's not installed. Aborting."; exit 1; }
@@ -29,6 +30,9 @@
   type wget >/dev/null 2>&1 || { echo >&2 "Require wget but it's not installed. Aborting."; exit 1; }
   type envsubst >/dev/null 2>&1 || { echo >&2 "Require envsubst but it's not installed. Aborting."; exit 1; }
   type openssl >/dev/null 2>&1 || { echo >&2 "Require openssl but it's not installed. Aborting."; exit 1; }
+  if [ "$BROKER_TYPE" = "kinesis" ];  then
+    type aws >/dev/null 2>&1 || { echo >&2 "Require aws-cli but it's not installed. Aborting."; exit 1; }
+  fi
 }
 
 function get_replication_url {
@@ -66,7 +70,7 @@
     export GERRIT_HOSTNAME=$7
     export REPLICATION_HOSTNAME=$8
     export REMOTE_DEBUG_PORT=$9
-    export KAFKA_GROUP_ID=${10}
+    export GROUP_ID=${10}
     export REPLICATION_URL=$(get_replication_url $REPLICATION_LOCATION_TEST_SITE $REPLICATION_HOSTNAME)
 
     echo "Replacing variables for file $file and copying to $CONFIG_TEST_SITE/$file_name"
@@ -98,11 +102,22 @@
   haproxy -f $HA_PROXY_CONFIG_DIR/haproxy.cfg &
 }
 
+function export_broker_port {
+  if [ "$BROKER_TYPE" = "kinesis" ]; then
+    export BROKER_PORT=4566
+  elif [ "$BROKER_TYPE" =  "kafka" ]; then
+    export BROKER_PORT=9092
+  elif [ "$BROKER_TYPE" =  "gcloud-pubsub" ]; then
+    export BROKER_PORT=8085
+  fi
+}
+
 function deploy_config_files {
-  # KAFKA configuration
-  export KAFKA_PORT=9092
+  # broker configuration
+  export BROKER_HOST=localhost
 
   # ZK configuration
+  export ZK_HOST=localhost
   export ZK_PORT=2181
 
   # SITE 1
@@ -111,21 +126,21 @@
   GERRIT_SITE1_SSHD_PORT=$3
   CONFIG_TEST_SITE_1=$LOCATION_TEST_SITE_1/etc
   GERRIT_SITE1_REMOTE_DEBUG_PORT="5005"
-  GERRIT_SITE1_KAFKA_GROUP_ID="instance-1"
+  GERRIT_SITE1_GROUP_ID="instance-1"
   # SITE 2
   GERRIT_SITE2_HOSTNAME=$4
   GERRIT_SITE2_HTTPD_PORT=$5
   GERRIT_SITE2_SSHD_PORT=$6
   CONFIG_TEST_SITE_2=$LOCATION_TEST_SITE_2/etc
   GERRIT_SITE2_REMOTE_DEBUG_PORT="5006"
-  GERRIT_SITE2_KAFKA_GROUP_ID="instance-2"
+  GERRIT_SITE2_GROUP_ID="instance-2"
 
   # Set config SITE1
-  copy_config_files $CONFIG_TEST_SITE_1 $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_SSHD_PORT $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE1_REMOTE_DEBUG_PORT $GERRIT_SITE1_KAFKA_GROUP_ID
+  copy_config_files $CONFIG_TEST_SITE_1 $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_SSHD_PORT $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE1_REMOTE_DEBUG_PORT $GERRIT_SITE1_GROUP_ID
 
 
   # Set config SITE2
-  copy_config_files $CONFIG_TEST_SITE_2 $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE2_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_REMOTE_DEBUG_PORT $GERRIT_SITE2_KAFKA_GROUP_ID
+  copy_config_files $CONFIG_TEST_SITE_2 $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE2_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_REMOTE_DEBUG_PORT $GERRIT_SITE2_GROUP_ID
 }
 
 function is_docker_desktop {
@@ -141,13 +156,36 @@
   fi
 }
 
+function create_kinesis_streams {
+  for stream in "gerrit_batch_index" "gerrit_cache_eviction" "gerrit_index" "gerrit_list_project" "gerrit_stream" "gerrit_web_session" "gerrit"
+  do
+    create_kinesis_stream $stream
+  done
+}
+
+function create_kinesis_stream {
+  local stream=$1
+
+  export AWS_PAGER=''
+  export AWS_ACCESS_KEY_ID=accessKey
+  export AWS_SECRET_ACCESS_KEY=secretKey
+  export AWS_DEFAULT_REGION=us-east-1
+  echo "[KINESIS] Create stream $stream"
+  until aws --endpoint-url=http://localhost:$BROKER_PORT kinesis create-stream --shard-count 1 --stream-name "$stream"
+  do
+      echo "[KINESIS stream $stream] Creation failed. Retrying in 5 seconds..."
+      sleep 5s
+  done
+}
 
 function cleanup_environment {
   echo "Killing existing HA-PROXY setup"
   kill $(ps -ax | grep haproxy | grep "gerrit_setup/ha-proxy-config" | awk '{print $1}') 2> /dev/null
 
-  echo "Stopping docker containers"
-  docker-compose -f $SCRIPT_DIR/docker-compose.yaml down 2> /dev/null
+  echo "Stopping $BROKER_TYPE docker container"
+  docker-compose -f "${SCRIPT_DIR}/docker-compose-${BROKER_TYPE}.yaml" down 2> /dev/null
+  echo "Stopping core docker containers"
+  docker-compose -f "${SCRIPT_DIR}/docker-compose-core.yaml" down 2> /dev/null
 
   echo "Stopping GERRIT instances"
   $1/bin/gerrit.sh stop 2> /dev/null
@@ -157,8 +195,32 @@
   rm -rf $3 2> /dev/null
 }
 
-function check_if_kafka_is_running {
-  echo $(docker inspect kafka_test_node 2> /dev/null | grep '"Running": true' | wc -l)
+function check_if_container_is_running {
+  local container=$1;
+  echo $(docker inspect "$container" 2> /dev/null | grep '"Running": true' | wc -l)
+}
+
+function ensure_docker_compose_is_up_and_running {
+  local log_label=$1
+  local container_name=$2
+  local docker_compose_file=$3
+
+  local is_container_running=$(check_if_container_is_running "$container_name")
+  if [ "$is_container_running" -lt 1 ];then
+    echo "[$log_label] Starting docker containers"
+    docker-compose -f "${SCRIPT_DIR}/${docker_compose_file}" up -d
+
+    echo "[$log_label] Waiting for docker containers to start..."
+    while [[ $(check_if_container_is_running "$container_name") -lt 1 ]];do sleep 10s; done
+  else
+    echo "[$log_label] Containers already running, nothing to do"
+  fi
+}
+
+function prepare_broker_data {
+  if [ "$BROKER_TYPE" = "kinesis" ]; then
+    create_kinesis_streams
+  fi
 }
 
 while [ $# -ne 0 ]
@@ -193,6 +255,8 @@
     echo
     echo "[--enabled-https]               Enabled https; default true"
     echo
+    echo "[--broker-type]                 events broker type; 'kafka', 'kinesis' or 'gcloud-pubsub'. Default 'kafka'"
+    echo
     exit 0
   ;;
   "--new-deployment")
@@ -280,6 +344,15 @@
     shift
     shift
   ;;
+ "--broker-type" )
+    BROKER_TYPE=$2
+    shift
+    shift
+    if [ ! "$BROKER_TYPE" = "kafka" ] && [ ! "$BROKER_TYPE" = "kinesis" ] && [ ! "$BROKER_TYPE" = "gcloud-pubsub" ]; then
+      echo >&2 "broker type: '$BROKER_TYPE' not valid. Please supply 'kafka','kinesis' or 'gcloud-pubsub'. Aborting"
+      exit 1
+    fi
+  ;;
   *     )
     echo "Unknown option argument: $1"
     shift
@@ -308,6 +381,7 @@
 export REPLICATION_DELAY_SEC=${REPLICATION_DELAY_SEC:-"5"}
 export SSH_ADVERTISED_PORT=${SSH_ADVERTISED_PORT:-"29418"}
 HTTPS_ENABLED=${HTTPS_ENABLED:-"false"}
+BROKER_TYPE=${BROKER_TYPE:-"kafka"}
 
 export COMMON_LOCATION=$DEPLOYMENT_LOCATION/gerrit_setup
 LOCATION_TEST_SITE_1=$COMMON_LOCATION/instance-1
@@ -356,15 +430,37 @@
   -O $DEPLOYMENT_LOCATION/zookeeper-refdb.jar || { echo >&2 "Cannot download zookeeper plugin: Check internet connection. Abort\
 ing"; exit 1; }
 
+echo "Downloading global-refdb library $GERRIT_BRANCH"
+  wget https://repo1.maven.org/maven2/com/gerritforge/global-refdb/$GLOBAL_REFDB_VER/global-refdb-$GLOBAL_REFDB_VER.jar \
+  -O $DEPLOYMENT_LOCATION/global-refdb.jar || { echo >&2 "Cannot download global-refdb library: Check internet connection. Abort\
+ing"; exit 1; }
+
 echo "Downloading events-broker library $GERRIT_BRANCH"
   wget https://repo1.maven.org/maven2/com/gerritforge/events-broker/$EVENTS_BROKER_VER/events-broker-$EVENTS_BROKER_VER.jar \
   -O $DEPLOYMENT_LOCATION/events-broker.jar || { echo >&2 "Cannot download events-broker library: Check internet connection. Abort\
 ing"; exit 1; }
 
+if [ "$BROKER_TYPE" = "kafka" ]; then
 echo "Downloading events-kafka plugin $GERRIT_BRANCH"
   wget $GERRIT_CI/plugin-events-kafka-bazel-$GERRIT_BRANCH/$LAST_BUILD/events-kafka/events-kafka.jar \
   -O $DEPLOYMENT_LOCATION/events-kafka.jar || { echo >&2 "Cannot download events-kafka plugin: Check internet connection. Abort\
 ing"; exit 1; }
+fi
+
+if [ "$BROKER_TYPE" = "kinesis" ]; then
+echo "Downloading events-aws-kinesis plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-events-aws-kinesis-bazel-$GERRIT_BRANCH/$LAST_BUILD/events-aws-kinesis/events-aws-kinesis.jar \
+  -O $DEPLOYMENT_LOCATION/events-aws-kinesis.jar || { echo >&2 "Cannot download events-aws-kinesis plugin: Check internet connection. Abort\
+ing"; exit 1; }
+fi
+
+
+if [ "$BROKER_TYPE" = "gcloud-pubsub" ]; then
+echo "Downloading events-gcloud-pubsub plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-events-gcloud-pubsub-bazel-$GERRIT_BRANCH/$LAST_BUILD/events-gcloud-pubsub/events-gcloud-pubsub.jar \
+  -O $DEPLOYMENT_LOCATION/events-gcloud-pubsub.jar || { echo >&2 "Cannot download events-gcloud-pubsub plugin: Check internet connection. Abort\
+ing"; exit 1; }
+fi
 
 echo "Downloading metrics-reporter-prometheus plugin $GERRIT_BRANCH"
   wget $GERRIT_CI/plugin-metrics-reporter-prometheus-bazel-master-$GERRIT_BRANCH/$LAST_BUILD/metrics-reporter-prometheus/metrics-reporter-prometheus.jar \
@@ -410,11 +506,20 @@
   echo "Copy zookeeper plugin"
   cp -f $DEPLOYMENT_LOCATION/zookeeper-refdb.jar $LOCATION_TEST_SITE_1/plugins/zookeeper-refdb.jar
 
+  echo "Copy global refdb library"
+  cp -f $DEPLOYMENT_LOCATION/global-refdb.jar $LOCATION_TEST_SITE_1/lib/global-refdb.jar
+
   echo "Copy events broker library"
   cp -f $DEPLOYMENT_LOCATION/events-broker.jar $LOCATION_TEST_SITE_1/lib/events-broker.jar
 
-  echo "Copy events kafka plugin"
-  cp -f $DEPLOYMENT_LOCATION/events-kafka.jar $LOCATION_TEST_SITE_1/plugins/events-kafka.jar
+  echo "Copy $BROKER_TYPE events plugin"
+  if [ $BROKER_TYPE = "kinesis" ]; then
+     cp -f $DEPLOYMENT_LOCATION/events-aws-kinesis.jar $LOCATION_TEST_SITE_1/plugins/events-aws-kinesis.jar
+  elif [ $BROKER_TYPE = "gcloud-pubsub" ]; then
+    cp -f $DEPLOYMENT_LOCATION/events-gcloud-pubsub.jar $LOCATION_TEST_SITE_1/plugins/events-gcloud-pubsub.jar
+  else
+    cp -f $DEPLOYMENT_LOCATION/events-kafka.jar $LOCATION_TEST_SITE_1/plugins/events-kafka.jar
+  fi
 
   echo "Copy metrics-reporter-prometheus plugin"
   cp -f $DEPLOYMENT_LOCATION/metrics-reporter-prometheus.jar $LOCATION_TEST_SITE_1/plugins/metrics-reporter-prometheus.jar
@@ -446,14 +551,10 @@
 
 cat $SCRIPT_DIR/configs/prometheus.yml | envsubst > $COMMON_LOCATION/prometheus.yml
 
-IS_KAFKA_RUNNING=$(check_if_kafka_is_running)
-if [ $IS_KAFKA_RUNNING -lt 1 ];then
-
-  echo "Starting zk and kafka"
-  docker-compose -f $SCRIPT_DIR/docker-compose.yaml up -d
-  echo "Waiting for kafka to start..."
-  while [[ $(check_if_kafka_is_running) -lt 1 ]];do sleep 10s; done
-fi
+export_broker_port
+ensure_docker_compose_is_up_and_running "core" "prometheus_test_node" "docker-compose-core.yaml"
+ensure_docker_compose_is_up_and_running "$BROKER_TYPE" "${BROKER_TYPE}_test_node" "docker-compose-$BROKER_TYPE.yaml"
+prepare_broker_data
 
 echo "Re-deploying configuration files"
 deploy_config_files $GERRIT_1_HOSTNAME $GERRIT_1_HTTPD_PORT $GERRIT_1_SSHD_PORT $GERRIT_2_HOSTNAME $GERRIT_2_HTTPD_PORT $GERRIT_2_SSHD_PORT
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
index f509cd4..6b77713 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -17,17 +17,16 @@
 import static com.google.common.base.Suppliers.memoize;
 import static com.google.common.base.Suppliers.ofInstance;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.spi.Message;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
@@ -62,7 +61,7 @@
   private final Supplier<Event> event;
   private final Supplier<Index> index;
   private final Supplier<Projects> projects;
-  private final Supplier<SharedRefDatabase> sharedRefDb;
+  private final Supplier<SharedRefDbConfiguration> sharedRefDb;
   private final Supplier<Collection<Message>> replicationConfigValidation;
   private final Supplier<Broker> broker;
   private final Config multiSiteConfig;
@@ -81,7 +80,11 @@
     event = memoize(() -> new Event(lazyMultiSiteCfg));
     index = memoize(() -> new Index(lazyMultiSiteCfg));
     projects = memoize(() -> new Projects(lazyMultiSiteCfg));
-    sharedRefDb = memoize(() -> new SharedRefDatabase(lazyMultiSiteCfg));
+    sharedRefDb =
+        memoize(
+            () ->
+                new SharedRefDbConfiguration(
+                    enableSharedRefDbByDefault(lazyMultiSiteCfg.get()), PLUGIN_NAME));
     broker = memoize(() -> new Broker(lazyMultiSiteCfg));
   }
 
@@ -89,7 +92,7 @@
     return multiSiteConfig;
   }
 
-  public SharedRefDatabase getSharedRefDb() {
+  public SharedRefDbConfiguration getSharedRefDbConfiguration() {
     return sharedRefDb.get();
   }
 
@@ -121,6 +124,28 @@
     return new FileBasedConfig(sitePaths.etc_dir.resolve(configFileName).toFile(), FS.DETECTED);
   }
 
+  private Config enableSharedRefDbByDefault(Config cfg) {
+    if (Strings.isNullOrEmpty(
+        cfg.getString(
+            SharedRefDbConfiguration.SharedRefDatabase.SECTION,
+            null,
+            SharedRefDbConfiguration.SharedRefDatabase.ENABLE_KEY))) {
+      cfg.setBoolean(
+          SharedRefDbConfiguration.SharedRefDatabase.SECTION,
+          null,
+          SharedRefDbConfiguration.SharedRefDatabase.ENABLE_KEY,
+          true);
+      if (cfg instanceof FileBasedConfig) {
+        try {
+          ((FileBasedConfig) cfg).save();
+        } catch (IOException e) {
+          throw new IllegalStateException("Error while enabling global-refdb by default", e);
+        }
+      }
+    }
+    return cfg;
+  }
+
   private Supplier<Config> lazyLoad(Config config) {
     if (config instanceof FileBasedConfig) {
       return memoize(
@@ -172,38 +197,6 @@
     }
   }
 
-  public static class SharedRefDatabase {
-    public static final String SECTION = "ref-database";
-    public static final String ENABLE_KEY = "enabled";
-    public static final String SUBSECTION_ENFORCEMENT_RULES = "enforcementRules";
-
-    private final boolean enabled;
-    private final Multimap<EnforcePolicy, String> enforcementRules;
-
-    private SharedRefDatabase(Supplier<Config> cfg) {
-      enabled = getBoolean(cfg, SECTION, null, ENABLE_KEY, true);
-
-      enforcementRules = MultimapBuilder.hashKeys().arrayListValues().build();
-      for (EnforcePolicy policy : EnforcePolicy.values()) {
-        enforcementRules.putAll(
-            policy, getList(cfg, SECTION, SUBSECTION_ENFORCEMENT_RULES, policy.name()));
-      }
-    }
-
-    public boolean isEnabled() {
-      return enabled;
-    }
-
-    public Multimap<EnforcePolicy, String> getEnforcementRules() {
-      return enforcementRules;
-    }
-
-    private List<String> getList(
-        Supplier<Config> cfg, String section, String subsection, String name) {
-      return ImmutableList.copyOf(cfg.get().getStringList(section, subsection, name));
-    }
-  }
-
   public static class Projects {
     public static final String SECTION = "projects";
     public static final String PATTERN_KEY = "pattern";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
index 387cefa..96bb4e8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
 import com.google.gerrit.server.ModuleImpl;
 import com.google.gerrit.server.config.RepositoryConfig;
 import com.google.gerrit.server.git.GitRepositoryManagerModule;
@@ -34,8 +35,10 @@
 
   @Override
   protected void configure() {
+    SharedRefDbConfiguration sharedRefDbConfiguration = config.getSharedRefDbConfiguration();
+    bind(SharedRefDbConfiguration.class).toInstance(sharedRefDbConfiguration);
     bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
-    if (config.getSharedRefDb().isEnabled()) {
+    if (sharedRefDbConfiguration.getSharedRefDb().isEnabled()) {
       install(new ValidationModule(config, repoConfig));
     } else {
       install(new GitRepositoryManagerModule(repoConfig));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java
deleted file mode 100644
index 0e018d3..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2019 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.multisite;
-
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-public class LockWrapper implements AutoCloseable {
-  public interface Factory {
-    LockWrapper create(
-        @Assisted("project") String project,
-        @Assisted("refName") String refName,
-        @Assisted("lock") AutoCloseable lock);
-  }
-
-  private final String project;
-  private final String refName;
-  private final AutoCloseable lock;
-  private final SharedRefLogger sharedRefLogger;
-
-  @Inject
-  public LockWrapper(
-      SharedRefLogger sharedRefLogger,
-      @Assisted("project") String project,
-      @Assisted("refName") String refName,
-      @Assisted("lock") AutoCloseable lock) {
-    this.lock = lock;
-    this.sharedRefLogger = sharedRefLogger;
-    this.project = project;
-    this.refName = refName;
-  }
-
-  @Override
-  public void close() throws Exception {
-    lock.close();
-    sharedRefLogger.logLockRelease(project, refName);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
deleted file mode 100644
index 003ce5b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (C) 2019 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.multisite;
-
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.common.GitPerson;
-import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.server.CommonConverters;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.util.SystemLog;
-import com.google.gson.Gson;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import org.apache.log4j.PatternLayout;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.lib.Constants;
-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.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class Log4jSharedRefLogger extends LibModuleLogFile implements SharedRefLogger {
-  private static final String LOG_NAME = "sharedref_log";
-  private Logger sharedRefDBLog;
-  private final GitRepositoryManager gitRepositoryManager;
-  private static final Gson gson = OutputFormat.JSON_COMPACT.newGson();
-
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  @Inject
-  public Log4jSharedRefLogger(SystemLog systemLog, GitRepositoryManager gitRepositoryManager) {
-    super(systemLog, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
-    this.gitRepositoryManager = gitRepositoryManager;
-    sharedRefDBLog = LoggerFactory.getLogger(LOG_NAME);
-  }
-
-  @Override
-  public void logRefUpdate(String project, Ref currRef, ObjectId newRefValue) {
-    if (!ObjectId.zeroId().equals(newRefValue)) {
-      try (Repository repository = gitRepositoryManager.openRepository(Project.nameKey(project));
-          RevWalk walk = new RevWalk(repository)) {
-        GitPerson committer = null;
-        String commitMessage = null;
-        if (newRefValue != null) {
-          int objectType = walk.parseAny(newRefValue).getType();
-          switch (objectType) {
-            case OBJ_COMMIT:
-              RevCommit commit = walk.parseCommit(newRefValue);
-              committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
-              commitMessage = commit.getShortMessage();
-              break;
-            case OBJ_BLOB:
-              break;
-            default:
-              throw new IncorrectObjectTypeException(newRefValue, Constants.typeString(objectType));
-          }
-        }
-        sharedRefDBLog.info(
-            gson.toJson(
-                new SharedRefLogEntry.UpdateRef(
-                    project,
-                    currRef.getName(),
-                    currRef.getObjectId().getName(),
-                    newRefValue == null ? ObjectId.zeroId().name() : newRefValue.getName(),
-                    committer,
-                    commitMessage)));
-      } catch (IOException e) {
-        logger.atSevere().withCause(e).log(
-            "Cannot log sharedRefDB interaction for ref %s on project %s",
-            currRef.getName(), project);
-      }
-    } else {
-      sharedRefDBLog.info(
-          gson.toJson(
-              new SharedRefLogEntry.DeleteRef(
-                  project, currRef.getName(), currRef.getObjectId().getName())));
-    }
-  }
-
-  @Override
-  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {
-    if (newRefValue != null) {
-      sharedRefDBLog.info(
-          gson.toJson(
-              new SharedRefLogEntry.UpdateRef(
-                  project, refName, safeToString(currRef), safeToString(newRefValue), null, null)));
-    } else {
-      sharedRefDBLog.info(
-          gson.toJson(new SharedRefLogEntry.DeleteRef(project, refName, safeToString(currRef))));
-    }
-  }
-
-  @Override
-  public void logProjectDelete(String project) {
-    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.DeleteProject(project)));
-  }
-
-  @Override
-  public void logLockAcquisition(String project, String refName) {
-    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockAcquire(project, refName)));
-  }
-
-  @Override
-  public void logLockRelease(String project, String refName) {
-    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockRelease(project, refName)));
-  }
-
-  @VisibleForTesting
-  public void setLogger(Logger logger) {
-    this.sharedRefDBLog = logger;
-  }
-
-  private <T> String safeToString(T currRef) {
-    if (currRef == null) {
-      return "<null>";
-    }
-    return currRef.toString();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index 8d8b206..91a927b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -14,14 +14,12 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
-import com.google.gerrit.extensions.registration.DynamicItem;
+import com.gerritforge.gerrit.globalrefdb.validation.LibModule;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.CreationException;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
-import com.google.inject.Scopes;
 import com.google.inject.Singleton;
 import com.google.inject.spi.Message;
 import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
@@ -29,7 +27,6 @@
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
 import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
@@ -58,11 +55,7 @@
       throw new CreationException(validationErrors);
     }
 
-    DynamicItem.itemOf(binder(), GlobalRefDatabase.class);
-    DynamicItem.bind(binder(), GlobalRefDatabase.class)
-        .to(NoopSharedRefDatabase.class)
-        .in(Scopes.SINGLETON);
-    log.info("Shared ref-db engine: none");
+    install(new LibModule());
 
     listener().to(Log4jMessageLogger.class);
     bind(MessageLogger.class).to(Log4jMessageLogger.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
index 2eaa5d9..20aaef6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectDeletedSharedDbCleanup;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -21,9 +22,9 @@
 import com.google.inject.Scopes;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.consumer.MultiSiteConsumerRunner;
+import com.googlesource.gerrit.plugins.multisite.consumer.ReplicationStatusModule;
 import com.googlesource.gerrit.plugins.multisite.consumer.SubscriberModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerForwarderModule;
-import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
 
 public class PluginModule extends LifecycleModule {
   private Configuration config;
@@ -41,7 +42,8 @@
     install(new BrokerForwarderModule());
     listener().to(MultiSiteConsumerRunner.class);
 
-    if (config.getSharedRefDb().isEnabled()) {
+    install(new ReplicationStatusModule());
+    if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
       listener().to(PluginStartup.class);
       DynamicSet.bind(binder(), ProjectDeletedListener.class)
           .to(ProjectDeletedSharedDbCleanup.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
index 33b54d2..db7064c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java
deleted file mode 100644
index d6fac47..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java
+++ /dev/null
@@ -1,109 +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.multisite;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.Project.NameKey;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.ProjectEvent;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.util.List;
-import java.util.Set;
-
-@Singleton
-public class ProjectsFilter {
-  public enum PatternType {
-    REGEX,
-    WILDCARD,
-    EXACT_MATCH;
-
-    public static PatternType getPatternType(String pattern) {
-      if (pattern.startsWith(AccessSection.REGEX_PREFIX)) {
-        return REGEX;
-      } else if (pattern.endsWith("*")) {
-        return WILDCARD;
-      } else {
-        return EXACT_MATCH;
-      }
-    }
-  }
-
-  private Set<NameKey> globalProjects = Sets.newConcurrentHashSet();
-  private Set<NameKey> localProjects = Sets.newConcurrentHashSet();
-  private final List<String> projectPatterns;
-
-  @Inject
-  public ProjectsFilter(Configuration cfg) {
-    projectPatterns = cfg.projects().getPatterns();
-  }
-
-  public boolean matches(Event event) {
-    if (event == null) {
-      throw new IllegalArgumentException("Event object cannot be null");
-    }
-    if (event instanceof ProjectEvent) {
-      return matches(((ProjectEvent) event).getProjectNameKey());
-    }
-    return false;
-  }
-
-  public boolean matches(String projectName) {
-    return matches(NameKey.parse(projectName));
-  }
-
-  public boolean matches(Project.NameKey name) {
-    if (name == null || Strings.isNullOrEmpty(name.get())) {
-      throw new IllegalArgumentException(
-          String.format("Project name cannot be null or empty, but was %s", name));
-    }
-    if (projectPatterns.isEmpty() || globalProjects.contains(name)) {
-      return true;
-    }
-
-    if (localProjects.contains(name)) {
-      return false;
-    }
-
-    String projectName = name.get();
-
-    for (String pattern : projectPatterns) {
-      if (matchesPattern(projectName, pattern)) {
-        globalProjects.add(name);
-        return true;
-      }
-    }
-    localProjects.add(name);
-    return false;
-  }
-
-  private boolean matchesPattern(String projectName, String pattern) {
-    boolean match = false;
-    switch (PatternType.getPatternType(pattern)) {
-      case REGEX:
-        match = projectName.matches(pattern);
-        break;
-      case WILDCARD:
-        match = projectName.startsWith(pattern.substring(0, pattern.length() - 1));
-        break;
-      case EXACT_MATCH:
-        match = projectName.equals(pattern);
-    }
-    return match;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
deleted file mode 100644
index ddf9d84..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (C) 2019 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.multisite;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
-import java.util.Optional;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-
-public class SharedRefDatabaseWrapper implements GlobalRefDatabase {
-  private static final GlobalRefDatabase NOOP_REFDB = new NoopSharedRefDatabase();
-
-  @Inject(optional = true)
-  private DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem;
-
-  private final SharedRefLogger sharedRefLogger;
-
-  @Inject
-  public SharedRefDatabaseWrapper(SharedRefLogger sharedRefLogger) {
-    this.sharedRefLogger = sharedRefLogger;
-  }
-
-  @VisibleForTesting
-  public SharedRefDatabaseWrapper(
-      DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem, SharedRefLogger sharedRefLogger) {
-    this.sharedRefLogger = sharedRefLogger;
-    this.sharedRefDbDynamicItem = sharedRefDbDynamicItem;
-  }
-
-  @Override
-  public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
-    return sharedRefDb().isUpToDate(project, ref);
-  }
-
-  @Override
-  public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
-      throws GlobalRefDbSystemError {
-    boolean succeeded = sharedRefDb().compareAndPut(project, currRef, newRefValue);
-    if (succeeded) {
-      sharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
-    }
-    return succeeded;
-  }
-
-  @Override
-  public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
-      throws GlobalRefDbSystemError {
-    boolean succeeded = sharedRefDb().compareAndPut(project, refName, currValue, newValue);
-    if (succeeded) {
-      sharedRefLogger.logRefUpdate(project.get(), refName, currValue, newValue);
-    }
-    return succeeded;
-  }
-
-  @Override
-  public AutoCloseable lockRef(Project.NameKey project, String refName)
-      throws GlobalRefDbLockException {
-    AutoCloseable locker = sharedRefDb().lockRef(project, refName);
-    sharedRefLogger.logLockAcquisition(project.get(), refName);
-    return locker;
-  }
-
-  @Override
-  public boolean exists(Project.NameKey project, String refName) {
-    return sharedRefDb().exists(project, refName);
-  }
-
-  @Override
-  public void remove(Project.NameKey project) throws GlobalRefDbSystemError {
-    sharedRefDb().remove(project);
-    sharedRefLogger.logProjectDelete(project.get());
-  }
-
-  @Override
-  public <T> Optional<T> get(Project.NameKey nameKey, String s, Class<T> clazz)
-      throws GlobalRefDbSystemError {
-    return sharedRefDb().get(nameKey, s, clazz);
-  }
-
-  private GlobalRefDatabase sharedRefDb() {
-    return Optional.ofNullable(sharedRefDbDynamicItem).map(di -> di.get()).orElse(NOOP_REFDB);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java
deleted file mode 100644
index d9e762f..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2019 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.multisite;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.common.GitPerson;
-
-public class SharedRefLogEntry {
-
-  public enum Type {
-    UPDATE_REF,
-    DELETE_REF,
-    DELETE_PROJECT,
-    LOCK_ACQUIRE,
-    LOCK_RELEASE
-  }
-
-  public String projectName;
-  public Type type;
-
-  public static class UpdateRef extends SharedRefLogEntry {
-
-    public String refName;
-    public String oldId;
-    public String newId;
-    public GitPerson committer;
-    public String comment;
-
-    UpdateRef(
-        String projectName,
-        String refName,
-        String oldId,
-        String newId,
-        @Nullable GitPerson committer,
-        @Nullable String comment) {
-      this.type = Type.UPDATE_REF;
-      this.projectName = projectName;
-      this.refName = refName;
-      this.oldId = oldId;
-      this.newId = newId;
-      this.committer = committer;
-      this.comment = comment;
-    }
-  }
-
-  public static class DeleteProject extends SharedRefLogEntry {
-
-    DeleteProject(String projectName) {
-      this.type = Type.DELETE_PROJECT;
-      this.projectName = projectName;
-    }
-  }
-
-  public static class DeleteRef extends SharedRefLogEntry {
-
-    public String refName;
-    public String oldId;
-
-    DeleteRef(String projectName, String refName, String oldId) {
-      this.type = Type.DELETE_REF;
-      this.projectName = projectName;
-      this.refName = refName;
-      this.oldId = oldId;
-    }
-  }
-
-  public static class LockAcquire extends SharedRefLogEntry {
-
-    public String refName;
-
-    LockAcquire(String projectName, String refName) {
-      this.type = Type.LOCK_ACQUIRE;
-      this.projectName = projectName;
-      this.refName = refName;
-    }
-  }
-
-  public static class LockRelease extends SharedRefLogEntry {
-
-    public String refName;
-
-    LockRelease(String projectName, String refName) {
-      this.type = Type.LOCK_RELEASE;
-      this.projectName = projectName;
-      this.refName = refName;
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
deleted file mode 100644
index 3853af6..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2019 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.multisite;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-
-public interface SharedRefLogger {
-
-  void logRefUpdate(String project, Ref currRef, ObjectId newRefValue);
-
-  <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue);
-
-  void logProjectDelete(String project);
-
-  void logLockAcquisition(String project, String refName);
-
-  void logLockRelease(String project, String refName);
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
index 2e2f9de..fdc6fc3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
@@ -14,13 +14,13 @@
 
 package com.googlesource.gerrit.plugins.multisite.cache;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.events.ProjectEvent;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
index 1f8766a..b37f434 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -34,7 +35,7 @@
 
   private final ForwardedEventRouter eventRouter;
   private final DynamicSet<DroppedEventListener> droppedEventListeners;
-  private final UUID instanceId;
+  private final String instanceId;
   private final MessageLogger msgLog;
   private SubscriberMetrics subscriberMetrics;
   private final Configuration cfg;
@@ -49,7 +50,7 @@
       Configuration cfg) {
     this.eventRouter = eventRouter;
     this.droppedEventListeners = droppedEventListeners;
-    this.instanceId = instanceId;
+    this.instanceId = instanceId.toString();
     this.msgLog = msgLog;
     this.subscriberMetrics = subscriberMetrics;
     this.cfg = cfg;
@@ -63,11 +64,18 @@
   }
 
   private void processRecord(EventMessage event) {
+    String sourceInstanceId = event.getHeader().sourceInstanceId;
 
-    if (event.getHeader().sourceInstanceId.equals(instanceId)) {
-      logger.atFiner().log(
-          "Dropping event %s produced by our instanceId %s",
-          event.toString(), instanceId.toString());
+    if (Strings.isNullOrEmpty(sourceInstanceId) || instanceId.equals(sourceInstanceId)) {
+      if (Strings.isNullOrEmpty(sourceInstanceId)) {
+        logger.atWarning().log(
+            String.format(
+                "Dropping event %s because sourceInstanceId cannot be null", event.toString()));
+      } else {
+        logger.atFiner().log(
+            String.format(
+                "Dropping event %s produced by our instanceId %s", event.toString(), instanceId));
+      }
       droppedEventListeners.forEach(l -> l.onEventDropped(event));
     } else {
       try {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
new file mode 100644
index 0000000..7360b8f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.consumer;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.cache.Cache;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.serialize.JavaCacheSerializer;
+import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Singleton
+public class ReplicationStatus implements LifecycleListener {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final Map<String, Long> replicationStatusPerProject = new HashMap<>();
+  static final String REPLICATION_STATUS_CACHE = "replication_status";
+
+  public static Module cacheModule() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        persist(REPLICATION_STATUS_CACHE, String.class, Long.class)
+            .version(1)
+            .keySerializer(StringCacheSerializer.INSTANCE)
+            .valueSerializer(new JavaCacheSerializer<>());
+      }
+    };
+  }
+
+  private final Map<String, Long> localVersionPerProject = new HashMap<>();
+  private final Cache<String, Long> cache;
+  private final Optional<ProjectVersionRefUpdate> projectVersionRefUpdate;
+  private final ProjectVersionLogger verLogger;
+  private final ProjectCache projectCache;
+
+  @Inject
+  public ReplicationStatus(
+      @Named(REPLICATION_STATUS_CACHE) Cache<String, Long> cache,
+      Optional<ProjectVersionRefUpdate> projectVersionRefUpdate,
+      ProjectVersionLogger verLogger,
+      ProjectCache projectCache) {
+    this.cache = cache;
+    this.projectVersionRefUpdate = projectVersionRefUpdate;
+    this.verLogger = verLogger;
+    this.projectCache = projectCache;
+  }
+
+  public Long getMaxLag() {
+    Collection<Long> lags = replicationStatusPerProject.values();
+    if (lags.isEmpty()) {
+      return 0L;
+    }
+    return Collections.max(lags);
+  }
+
+  public Map<String, Long> getReplicationLags(Integer limit) {
+    return replicationStatusPerProject.entrySet().stream()
+        .sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue()))
+        .limit(limit)
+        .collect(
+            Collectors.toMap(
+                Map.Entry::getKey,
+                Map.Entry::getValue,
+                (oldValue, newValue) -> oldValue,
+                LinkedHashMap::new));
+  }
+
+  public void updateReplicationLag(Project.NameKey projectName) {
+    Optional<Long> remoteVersion =
+        projectVersionRefUpdate.flatMap(
+            refUpdate -> refUpdate.getProjectRemoteVersion(projectName.get()));
+    Optional<Long> localVersion =
+        projectVersionRefUpdate.flatMap(
+            refUpdate -> refUpdate.getProjectLocalVersion(projectName.get()));
+    if (remoteVersion.isPresent() && localVersion.isPresent()) {
+      long lag = remoteVersion.get() - localVersion.get();
+
+      if (!localVersion.get().equals(localVersionPerProject.get(projectName.get()))
+          || lag != replicationStatusPerProject.get(projectName.get())) {
+        logger.atFine().log(
+            "Updated replication lag for project '%s' of %d sec(s) [local-ref=%d global-ref=%d]",
+            projectName, lag, localVersion.get(), remoteVersion.get());
+        doUpdateLag(projectName, lag);
+        localVersionPerProject.put(projectName.get(), localVersion.get());
+        verLogger.log(projectName, localVersion.get(), lag);
+      }
+    } else {
+      logger.atFine().log(
+          "Did not update replication lag for %s because the %s version is not defined",
+          projectName, localVersion.isPresent() ? "remote" : "local");
+    }
+  }
+
+  @VisibleForTesting
+  public void doUpdateLag(Project.NameKey projectName, Long lag) {
+    cache.put(projectName.get(), lag);
+    replicationStatusPerProject.put(projectName.get(), lag);
+  }
+
+  @Override
+  public void start() {
+    loadAllFromCache();
+  }
+
+  @Override
+  public void stop() {}
+
+  private void loadAllFromCache() {
+    Set<String> cachedProjects =
+        projectCache.all().stream().map(Project.NameKey::get).collect(Collectors.toSet());
+    replicationStatusPerProject.putAll(cache.getAllPresent(cachedProjects));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusModule.java
new file mode 100644
index 0000000..e954bc4
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.consumer;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Scopes;
+
+public class ReplicationStatusModule extends LifecycleModule {
+  @Override
+  protected void configure() {
+    bind(ReplicationStatus.class).in(Scopes.SINGLETON);
+    install(ReplicationStatus.cacheModule());
+    listener().to(ReplicationStatus.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
index 4459859..899233e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
@@ -16,7 +16,6 @@
 
 import com.gerritforge.gerrit.eventbroker.EventMessage;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
 import com.google.gerrit.metrics.Counter1;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.MetricMaker;
@@ -25,16 +24,9 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.MultiSiteMetrics;
-import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
 import com.googlesource.gerrit.plugins.replication.RefReplicatedEvent;
 import com.googlesource.gerrit.plugins.replication.RefReplicationDoneEvent;
 import com.googlesource.gerrit.plugins.replication.ReplicationScheduledEvent;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
 
 @Singleton
 public class SubscriberMetrics extends MultiSiteMetrics {
@@ -47,20 +39,12 @@
 
   private final Counter1<String> subscriberSuccessCounter;
   private final Counter1<String> subscriberFailureCounter;
-  private final ProjectVersionLogger verLogger;
-
-  private final Map<String, Long> replicationStatusPerProject = new HashMap<>();
-  private final Map<String, Long> localVersionPerProject = new HashMap<>();
-
-  private ProjectVersionRefUpdate projectVersionRefUpdate;
+  private final ReplicationStatus replicationStatus;
 
   @Inject
-  public SubscriberMetrics(
-      MetricMaker metricMaker,
-      ProjectVersionRefUpdate projectVersionRefUpdate,
-      ProjectVersionLogger verLogger) {
+  public SubscriberMetrics(MetricMaker metricMaker, ReplicationStatus replicationStatus) {
+    this.replicationStatus = replicationStatus;
 
-    this.projectVersionRefUpdate = projectVersionRefUpdate;
     this.subscriberSuccessCounter =
         metricMaker.newCounter(
             "multi_site/subscriber/subscriber_message_consumer_counter",
@@ -79,15 +63,7 @@
         REPLICATION_LAG_SEC,
         Long.class,
         new Description("Replication lag (sec)").setGauge().setUnit(Description.Units.SECONDS),
-        () -> {
-          Collection<Long> lags = replicationStatusPerProject.values();
-          if (lags.isEmpty()) {
-            return 0L;
-          }
-          return Collections.max(lags);
-        });
-
-    this.verLogger = verLogger;
+        replicationStatus::getMaxLag);
   }
 
   public void incrementSubscriberConsumedMessage() {
@@ -102,40 +78,16 @@
     Event event = eventMessage.getEvent();
     if (event instanceof RefReplicationDoneEvent) {
       RefReplicationDoneEvent replicationDone = (RefReplicationDoneEvent) event;
-      updateReplicationLagMetrics(
-          replicationDone.getProjectNameKey(), replicationDone.getRefName());
+      replicationStatus.updateReplicationLag(replicationDone.getProjectNameKey());
     } else if (event instanceof RefReplicatedEvent) {
       RefReplicatedEvent replicated = (RefReplicatedEvent) event;
-      updateReplicationLagMetrics(replicated.getProjectNameKey(), replicated.getRefName());
+      replicationStatus.updateReplicationLag(replicated.getProjectNameKey());
     } else if (event instanceof ReplicationScheduledEvent) {
       ReplicationScheduledEvent updated = (ReplicationScheduledEvent) event;
-      updateReplicationLagMetrics(updated.getProjectNameKey(), updated.getRefName());
+      replicationStatus.updateReplicationLag(updated.getProjectNameKey());
     } else if (event instanceof RefUpdatedEvent) {
       RefUpdatedEvent updated = (RefUpdatedEvent) event;
-      updateReplicationLagMetrics(updated.getProjectNameKey(), updated.getRefName());
-    }
-  }
-
-  private void updateReplicationLagMetrics(Project.NameKey projectName, String ref) {
-    Optional<Long> remoteVersion =
-        projectVersionRefUpdate.getProjectRemoteVersion(projectName.get());
-    Optional<Long> localVersion = projectVersionRefUpdate.getProjectLocalVersion(projectName.get());
-    if (remoteVersion.isPresent() && localVersion.isPresent()) {
-      long lag = remoteVersion.get() - localVersion.get();
-
-      if (!localVersion.get().equals(localVersionPerProject.get(projectName.get()))
-          || lag != replicationStatusPerProject.get(projectName.get())) {
-        logger.atFine().log(
-            "Published replication lag metric for project '%s' of %d sec(s) [local-ref=%d global-ref=%d]",
-            projectName, lag, localVersion.get(), remoteVersion.get());
-        replicationStatusPerProject.put(projectName.get(), lag);
-        localVersionPerProject.put(projectName.get(), localVersion.get());
-        verLogger.log(projectName, localVersion.get(), lag);
-      }
-    } else {
-      logger.atFine().log(
-          "Did not publish replication lag metric for %s because the %s version is not defined",
-          projectName, localVersion.isPresent() ? "remote" : "local");
+      replicationStatus.updateReplicationLag(updated.getProjectNameKey());
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
index a1b7a53..b2efb80 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
@@ -14,12 +14,12 @@
 
 package com.googlesource.gerrit.plugins.multisite.event;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
 import java.util.concurrent.Executor;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
index e122c24..5f16210 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
@@ -39,7 +39,7 @@
     DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
     OptionalBinder<ProjectVersionRefUpdate> projectVersionRefUpdateBinder =
         OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class);
-    if (config.getSharedRefDb().isEnabled()) {
+    if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
       DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdateImpl.class);
       projectVersionRefUpdateBinder
           .setBinding()
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParser.java
similarity index 62%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java
rename to src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParser.java
index 0bbdada..ef14a8e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParser.java
@@ -17,50 +17,57 @@
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.events.EventGson;
 import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.cache.Constants;
 
-public final class GsonParser {
+public final class CacheKeyJsonParser {
   private final Gson gson;
 
   @Inject
-  public GsonParser(@EventGson Gson gson) {
+  public CacheKeyJsonParser(@EventGson Gson gson) {
     this.gson = gson;
   }
 
   @SuppressWarnings("cast")
-  public Object fromJson(String cacheName, Object json) {
-    Object key;
+  public Object from(String cacheName, Object cacheKeyValue) {
+    Object parsedKey;
     // Need to add a case for 'adv_bases'
     switch (cacheName) {
       case Constants.ACCOUNTS:
-        key = Account.id(jsonElement(json).getAsJsonObject().get("id").getAsInt());
+        parsedKey = Account.id(jsonElement(cacheKeyValue).getAsJsonObject().get("id").getAsInt());
         break;
       case Constants.GROUPS:
-        key = AccountGroup.id(jsonElement(json).getAsJsonObject().get("id").getAsInt());
+        parsedKey =
+            AccountGroup.id(jsonElement(cacheKeyValue).getAsJsonObject().get("id").getAsInt());
         break;
       case Constants.GROUPS_BYINCLUDE:
       case Constants.GROUPS_MEMBERS:
-        key = AccountGroup.uuid(jsonElement(json).getAsJsonObject().get("uuid").getAsString());
+        parsedKey =
+            AccountGroup.uuid(
+                jsonElement(cacheKeyValue).getAsJsonObject().get("uuid").getAsString());
+        break;
+      case Constants.PROJECTS:
+        parsedKey = Project.nameKey(nullToEmpty(cacheKeyValue));
         break;
       case Constants.PROJECT_LIST:
-        key = gson.fromJson(nullToEmpty(json).toString(), Object.class);
+        parsedKey = gson.fromJson(nullToEmpty(cacheKeyValue).toString(), Object.class);
         break;
       default:
-        if (json instanceof String) {
-          key = (String) json;
+        if (cacheKeyValue instanceof String) {
+          parsedKey = (String) cacheKeyValue;
         } else {
           try {
-            key = gson.fromJson(nullToEmpty(json).toString().trim(), String.class);
+            parsedKey = gson.fromJson(nullToEmpty(cacheKeyValue).toString().trim(), String.class);
           } catch (Exception e) {
-            key = gson.fromJson(nullToEmpty(json).toString(), Object.class);
+            parsedKey = gson.fromJson(nullToEmpty(cacheKeyValue).toString(), Object.class);
           }
         }
     }
-    return key;
+    return parsedKey;
   }
 
   private JsonElement jsonElement(Object json) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/CacheEvictionEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/CacheEvictionEventRouter.java
index 0fb0c0a..a44da29 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/CacheEvictionEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/CacheEvictionEventRouter.java
@@ -16,25 +16,25 @@
 
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEntry;
+import com.googlesource.gerrit.plugins.multisite.forwarder.CacheKeyJsonParser;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.GsonParser;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
 
 public class CacheEvictionEventRouter implements ForwardedEventRouter<CacheEvictionEvent> {
   private final ForwardedCacheEvictionHandler cacheEvictionHanlder;
-  private final GsonParser gsonParser;
+  private final CacheKeyJsonParser gsonParser;
 
   @Inject
   public CacheEvictionEventRouter(
-      ForwardedCacheEvictionHandler cacheEvictionHanlder, GsonParser gsonParser) {
+      ForwardedCacheEvictionHandler cacheEvictionHanlder, CacheKeyJsonParser gsonParser) {
     this.cacheEvictionHanlder = cacheEvictionHanlder;
     this.gsonParser = gsonParser;
   }
 
   @Override
   public void route(CacheEvictionEvent cacheEvictionEvent) throws CacheNotFoundException {
-    Object parsedKey = gsonParser.fromJson(cacheEvictionEvent.cacheName, cacheEvictionEvent.key);
+    Object parsedKey = gsonParser.from(cacheEvictionEvent.cacheName, cacheEvictionEvent.key);
     cacheEvictionHanlder.evict(CacheEntry.from(cacheEvictionEvent.cacheName, parsedKey));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/http/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/http/HttpModule.java
new file mode 100644
index 0000000..2ae2d9b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/http/HttpModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.http;
+
+import com.google.inject.servlet.ServletModule;
+
+public class HttpModule extends ServletModule {
+
+  public static final String LAG_ENDPOINT_SEGMENT = "replication-lag";
+
+  @Override
+  protected void configureServlets() {
+    serve(String.format("/%s", LAG_ENDPOINT_SEGMENT)).with(ReplicationStatusServlet.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServlet.java
new file mode 100644
index 0000000..e2b3e18
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServlet.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.http;
+
+import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.consumer.ReplicationStatus;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Optional;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+public class ReplicationStatusServlet extends HttpServlet {
+  protected static final FluentLogger logger = FluentLogger.forEnclosingClass();
+  private static final String LIMIT_RESULT_PARAMETER = "limit";
+  private static final long serialVersionUID = 1L;
+  private static final Integer DEFAULT_LIMIT_RESULT_PARAMETER = 10;
+
+  private final Gson gson;
+  private final ReplicationStatus replicationStatus;
+  private final PermissionBackend permissionBackend;
+
+  @Inject
+  ReplicationStatusServlet(
+      Gson gson, ReplicationStatus replicationStatus, PermissionBackend permissionBackend) {
+    this.gson = gson;
+    this.replicationStatus = replicationStatus;
+    this.permissionBackend = permissionBackend;
+  }
+
+  @Override
+  protected void doGet(
+      HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
+      throws ServletException, IOException {
+    if (!permissionBackend.currentUser().testOrFalse(ADMINISTRATE_SERVER)) {
+      setResponse(
+          httpServletResponse,
+          HttpServletResponse.SC_FORBIDDEN,
+          String.format("%s permissions required. Operation not permitted", ADMINISTRATE_SERVER));
+      return;
+    }
+
+    int limitResult =
+        Optional.ofNullable(httpServletRequest.getParameter(LIMIT_RESULT_PARAMETER))
+            .map(Integer::parseInt)
+            .orElse(DEFAULT_LIMIT_RESULT_PARAMETER);
+
+    setResponse(
+        httpServletResponse,
+        HttpServletResponse.SC_OK,
+        gson.toJson(replicationStatus.getReplicationLags(limitResult)));
+  }
+
+  static void setResponse(HttpServletResponse httpResponse, int statusCode, String value)
+      throws IOException {
+    httpResponse.setContentType("application/json");
+    httpResponse.setStatus(statusCode);
+    PrintWriter writer = httpResponse.getWriter();
+    writer.print(new String(RestApiServlet.JSON_MAGIC));
+    writer.print(value);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
index 02972b0..08b26f7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
@@ -15,7 +15,7 @@
 package com.googlesource.gerrit.plugins.multisite.index;
 
 import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.HumanComment;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeFinder;
@@ -148,7 +148,7 @@
     Change change = notes.getChange();
     Timestamp changeTs = change.getLastUpdatedOn();
     try {
-      for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) {
+      for (HumanComment comment : commentsUtil.draftByChange(changeNotes.get())) {
         Timestamp commentTs = comment.writtenOn;
         changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
       }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
index 5effd0e..eef3e4b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.index;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.common.base.Objects;
 import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
@@ -21,7 +22,6 @@
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
deleted file mode 100644
index 0afe308..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.multisite.LockWrapper;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-public class BatchRefUpdateValidator extends RefUpdateValidator {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public static interface Factory {
-    BatchRefUpdateValidator create(String projectName, RefDatabase refDb);
-  }
-
-  public interface BatchValidationWrapper {
-    void apply(BatchRefUpdate batchRefUpdate, NoParameterVoidFunction arg) throws IOException;
-  }
-
-  @Inject
-  public BatchRefUpdateValidator(
-      SharedRefDatabaseWrapper sharedRefDb,
-      ValidationMetrics validationMetrics,
-      SharedRefEnforcement refEnforcement,
-      LockWrapper.Factory lockWrapperFactory,
-      ProjectsFilter projectsFilter,
-      @Assisted String projectName,
-      @Assisted RefDatabase refDb) {
-    super(
-        sharedRefDb,
-        validationMetrics,
-        refEnforcement,
-        lockWrapperFactory,
-        projectsFilter,
-        projectName,
-        refDb);
-  }
-
-  public void executeBatchUpdateWithValidation(
-      BatchRefUpdate batchRefUpdate,
-      NoParameterVoidFunction batchRefUpdateFunction,
-      OneParameterVoidFunction<List<ReceiveCommand>> batchRefUpdateRollbackFunction)
-      throws IOException {
-    if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED
-        || !isGlobalProject(projectName)) {
-      batchRefUpdateFunction.invoke();
-      return;
-    }
-
-    try {
-      doExecuteBatchUpdate(batchRefUpdate, batchRefUpdateFunction, batchRefUpdateRollbackFunction);
-    } catch (IOException e) {
-      logger.atWarning().withCause(e).log(
-          "Failed to execute Batch Update on project %s", projectName);
-      if (refEnforcement.getPolicy(projectName) == EnforcePolicy.REQUIRED) {
-        throw e;
-      }
-    }
-  }
-
-  private void doExecuteBatchUpdate(
-      BatchRefUpdate batchRefUpdate,
-      NoParameterVoidFunction delegateUpdate,
-      OneParameterVoidFunction<List<ReceiveCommand>> delegateUpdateRollback)
-      throws IOException {
-
-    List<ReceiveCommand> commands = batchRefUpdate.getCommands();
-    if (commands.isEmpty()) {
-      return;
-    }
-
-    List<RefPair> refsToUpdate = getRefsPairs(commands).collect(Collectors.toList());
-    List<RefPair> refsFailures =
-        refsToUpdate.stream().filter(RefPair::hasFailed).collect(Collectors.toList());
-    if (!refsFailures.isEmpty()) {
-      String allFailuresMessage =
-          refsFailures.stream()
-              .map(refPair -> String.format("Failed to fetch ref %s", refPair.compareRef.getName()))
-              .collect(Collectors.joining(", "));
-      Exception firstFailureException = refsFailures.get(0).exception;
-
-      logger.atSevere().withCause(firstFailureException).log(allFailuresMessage);
-      throw new IOException(allFailuresMessage, firstFailureException);
-    }
-
-    try (CloseableSet<AutoCloseable> locks = new CloseableSet<>()) {
-      final List<RefPair> finalRefsToUpdate = compareAndGetLatestLocalRefs(refsToUpdate, locks);
-      delegateUpdate.invoke();
-      try {
-        updateSharedRefDb(batchRefUpdate.getCommands().stream(), finalRefsToUpdate);
-      } catch (Exception e) {
-        List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
-        logger.atWarning().withCause(e).log(
-            String.format(
-                "Batch ref-update failing because of failure during the global refdb update. Set all commands Result to LOCK_FAILURE [%d]",
-                receiveCommands.size()));
-        rollback(delegateUpdateRollback, finalRefsToUpdate, receiveCommands);
-      }
-    } catch (OutOfSyncException e) {
-      List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
-      logger.atWarning().withCause(e).log(
-          String.format(
-              "Batch ref-update failing because node is out of sync with the shared ref-db. Set all commands Result to LOCK_FAILURE [%d]",
-              receiveCommands.size()));
-      receiveCommands.forEach((command) -> command.setResult(ReceiveCommand.Result.LOCK_FAILURE));
-    }
-  }
-
-  private void rollback(
-      OneParameterVoidFunction<List<ReceiveCommand>> delegateUpdateRollback,
-      List<RefPair> refsBeforeUpdate,
-      List<ReceiveCommand> receiveCommands)
-      throws IOException {
-    List<ReceiveCommand> rollbackCommands =
-        refsBeforeUpdate.stream()
-            .map(
-                refBeforeUpdate ->
-                    new ReceiveCommand(
-                        refBeforeUpdate.putValue,
-                        refBeforeUpdate.compareRef.getObjectId(),
-                        refBeforeUpdate.getName()))
-            .collect(Collectors.toList());
-    delegateUpdateRollback.invoke(rollbackCommands);
-    receiveCommands.forEach(command -> command.setResult(ReceiveCommand.Result.LOCK_FAILURE));
-  }
-
-  private void updateSharedRefDb(Stream<ReceiveCommand> commandStream, List<RefPair> refsToUpdate)
-      throws IOException {
-    if (commandStream
-        .filter(cmd -> cmd.getResult() != ReceiveCommand.Result.OK)
-        .findFirst()
-        .isPresent()) {
-      return;
-    }
-
-    for (RefPair refPair : refsToUpdate) {
-      updateSharedDbOrThrowExceptionFor(refPair);
-    }
-  }
-
-  private Stream<RefPair> getRefsPairs(List<ReceiveCommand> receivedCommands) {
-    return receivedCommands.stream().map(this::getRefPairForCommand);
-  }
-
-  private RefPair getRefPairForCommand(ReceiveCommand command) {
-    try {
-      switch (command.getType()) {
-        case CREATE:
-          return new RefPair(nullRef(command.getRefName()), getNewRef(command));
-
-        case UPDATE:
-        case UPDATE_NONFASTFORWARD:
-          return new RefPair(getCurrentRef(command.getRefName()), getNewRef(command));
-
-        case DELETE:
-          return new RefPair(getCurrentRef(command.getRefName()), ObjectId.zeroId());
-
-        default:
-          return new RefPair(
-              command.getRef(),
-              new IllegalArgumentException("Unsupported command type " + command.getType()));
-      }
-    } catch (IOException e) {
-      return new RefPair(command.getRef(), e);
-    }
-  }
-
-  private ObjectId getNewRef(ReceiveCommand command) {
-    return command.getNewId();
-  }
-
-  private List<RefPair> compareAndGetLatestLocalRefs(
-      List<RefPair> refsToUpdate, CloseableSet<AutoCloseable> locks) throws IOException {
-    List<RefPair> latestRefsToUpdate = new ArrayList<>();
-    for (RefPair refPair : refsToUpdate) {
-      latestRefsToUpdate.add(compareAndGetLatestLocalRef(refPair, locks));
-    }
-    return latestRefsToUpdate;
-  }
-
-  private static final Ref nullRef(String refName) {
-    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, ObjectId.zeroId());
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdate.java
deleted file mode 100644
index fd4f910..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdate.java
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.PushCertificate;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.util.time.ProposedTimestamp;
-
-public class MultiSiteBatchRefUpdate extends BatchRefUpdate {
-
-  private final BatchRefUpdate batchRefUpdate;
-  private final BatchRefUpdate batchRefUpdateRollback;
-  private final String project;
-  private final BatchRefUpdateValidator.Factory batchRefValidatorFactory;
-  private final RefDatabase refDb;
-
-  public static interface Factory {
-    MultiSiteBatchRefUpdate create(String project, RefDatabase refDb);
-  }
-
-  @Inject
-  public MultiSiteBatchRefUpdate(
-      BatchRefUpdateValidator.Factory batchRefValidatorFactory,
-      @Assisted String project,
-      @Assisted RefDatabase refDb) {
-    super(refDb);
-    this.refDb = refDb;
-    this.project = project;
-    this.batchRefUpdate = refDb.newBatchUpdate();
-    this.batchRefUpdateRollback = refDb.newBatchUpdate();
-    this.batchRefValidatorFactory = batchRefValidatorFactory;
-  }
-
-  @Override
-  public int hashCode() {
-    return batchRefUpdate.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    return batchRefUpdate.equals(obj);
-  }
-
-  @Override
-  public boolean isAllowNonFastForwards() {
-    return batchRefUpdate.isAllowNonFastForwards();
-  }
-
-  @Override
-  public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
-    batchRefUpdateRollback.setAllowNonFastForwards(allow);
-    return batchRefUpdate.setAllowNonFastForwards(allow);
-  }
-
-  @Override
-  public PersonIdent getRefLogIdent() {
-    return batchRefUpdate.getRefLogIdent();
-  }
-
-  @Override
-  public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
-    batchRefUpdateRollback.setRefLogIdent(pi);
-    return batchRefUpdate.setRefLogIdent(pi);
-  }
-
-  @Override
-  public String getRefLogMessage() {
-    return batchRefUpdate.getRefLogMessage();
-  }
-
-  @Override
-  public boolean isRefLogIncludingResult() {
-    return batchRefUpdate.isRefLogIncludingResult();
-  }
-
-  @Override
-  public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
-    batchRefUpdateRollback.setRefLogMessage(msg, appendStatus);
-    return batchRefUpdate.setRefLogMessage(msg, appendStatus);
-  }
-
-  @Override
-  public BatchRefUpdate disableRefLog() {
-    return batchRefUpdate.disableRefLog();
-  }
-
-  @Override
-  public BatchRefUpdate setForceRefLog(boolean force) {
-    batchRefUpdateRollback.setForceRefLog(force);
-    return batchRefUpdate.setForceRefLog(force);
-  }
-
-  @Override
-  public boolean isRefLogDisabled() {
-    return batchRefUpdate.isRefLogDisabled();
-  }
-
-  @Override
-  public BatchRefUpdate setAtomic(boolean atomic) {
-    return batchRefUpdate.setAtomic(atomic);
-  }
-
-  @Override
-  public boolean isAtomic() {
-    return batchRefUpdate.isAtomic();
-  }
-
-  @Override
-  public void setPushCertificate(PushCertificate cert) {
-    batchRefUpdate.setPushCertificate(cert);
-  }
-
-  @Override
-  public List<ReceiveCommand> getCommands() {
-    return batchRefUpdate.getCommands();
-  }
-
-  @Override
-  public BatchRefUpdate addCommand(ReceiveCommand cmd) {
-    return batchRefUpdate.addCommand(cmd);
-  }
-
-  @Override
-  public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
-    return batchRefUpdate.addCommand(cmd);
-  }
-
-  @Override
-  public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
-    return batchRefUpdate.addCommand(cmd);
-  }
-
-  @Override
-  public List<String> getPushOptions() {
-    return batchRefUpdate.getPushOptions();
-  }
-
-  @Override
-  public List<ProposedTimestamp> getProposedTimestamps() {
-    return batchRefUpdate.getProposedTimestamps();
-  }
-
-  @Override
-  public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
-    return batchRefUpdate.addProposedTimestamp(ts);
-  }
-
-  @Override
-  public void execute(RevWalk walk, ProgressMonitor monitor, List<String> options)
-      throws IOException {
-    batchRefValidatorFactory
-        .create(project, refDb)
-        .executeBatchUpdateWithValidation(
-            batchRefUpdate,
-            () -> batchRefUpdate.execute(walk, monitor, options),
-            (commands) ->
-                batchRefUpdateRollback.addCommand(commands).execute(walk, monitor, options));
-  }
-
-  @Override
-  public void execute(RevWalk walk, ProgressMonitor monitor) throws IOException {
-    batchRefValidatorFactory
-        .create(project, refDb)
-        .executeBatchUpdateWithValidation(
-            batchRefUpdate,
-            () -> batchRefUpdate.execute(walk, monitor),
-            (commands) -> batchRefUpdateRollback.addCommand(commands).execute(walk, monitor));
-  }
-
-  @Override
-  public String toString() {
-    return batchRefUpdate.toString();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManager.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManager.java
deleted file mode 100644
index 6ece734..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManager.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.google.gerrit.server.git.RepositoryCaseMismatchException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.SortedSet;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Repository;
-
-@Singleton
-public class MultiSiteGitRepositoryManager implements GitRepositoryManager {
-  private final GitRepositoryManager gitRepositoryManager;
-  private final MultiSiteRepository.Factory multiSiteRepoFactory;
-
-  @Inject
-  public MultiSiteGitRepositoryManager(
-      MultiSiteRepository.Factory multiSiteRepoFactory,
-      LocalDiskRepositoryManager localDiskRepositoryManager) {
-    this.multiSiteRepoFactory = multiSiteRepoFactory;
-    this.gitRepositoryManager = localDiskRepositoryManager;
-  }
-
-  @Override
-  public Repository openRepository(Project.NameKey name)
-      throws RepositoryNotFoundException, IOException {
-    return wrap(name, gitRepositoryManager.openRepository(name));
-  }
-
-  @Override
-  public Repository createRepository(Project.NameKey name)
-      throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException {
-    return wrap(name, gitRepositoryManager.createRepository(name));
-  }
-
-  @Override
-  public Boolean canPerformGC() {
-    return gitRepositoryManager.canPerformGC();
-  }
-
-  @Override
-  public SortedSet<Project.NameKey> list() {
-    return gitRepositoryManager.list();
-  }
-
-  private Repository wrap(Project.NameKey projectName, Repository projectRepo) {
-    return multiSiteRepoFactory.create(projectName.get(), projectRepo);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
deleted file mode 100644
index e1d1c65..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefRename;
-import org.eclipse.jgit.lib.RefUpdate;
-
-public class MultiSiteRefDatabase extends RefDatabase {
-  private final MultiSiteRefUpdate.Factory refUpdateFactory;
-  private final MultiSiteBatchRefUpdate.Factory batchRefUpdateFactory;
-  private final String projectName;
-  private final RefDatabase refDatabase;
-
-  public interface Factory {
-    public MultiSiteRefDatabase create(String projectName, RefDatabase refDatabase);
-  }
-
-  @Inject
-  public MultiSiteRefDatabase(
-      MultiSiteRefUpdate.Factory refUpdateFactory,
-      MultiSiteBatchRefUpdate.Factory batchRefUpdateFactory,
-      @Assisted String projectName,
-      @Assisted RefDatabase refDatabase) {
-    this.refUpdateFactory = refUpdateFactory;
-    this.batchRefUpdateFactory = batchRefUpdateFactory;
-    this.projectName = projectName;
-    this.refDatabase = refDatabase;
-  }
-
-  @Override
-  public int hashCode() {
-    return refDatabase.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    return refDatabase.equals(obj);
-  }
-
-  @Override
-  public void create() throws IOException {
-    refDatabase.create();
-  }
-
-  @Override
-  public void close() {
-    refDatabase.close();
-  }
-
-  @Override
-  public boolean isNameConflicting(String name) throws IOException {
-    return refDatabase.isNameConflicting(name);
-  }
-
-  @Override
-  public Collection<String> getConflictingNames(String name) throws IOException {
-    return refDatabase.getConflictingNames(name);
-  }
-
-  @Override
-  public RefUpdate newUpdate(String name, boolean detach) throws IOException {
-    return wrapRefUpdate(refDatabase.newUpdate(name, detach));
-  }
-
-  @Override
-  public RefRename newRename(String fromName, String toName) throws IOException {
-    return refDatabase.newRename(fromName, toName);
-  }
-
-  @Override
-  public BatchRefUpdate newBatchUpdate() {
-    return batchRefUpdateFactory.create(projectName, refDatabase);
-  }
-
-  @Override
-  public boolean performsAtomicTransactions() {
-    return refDatabase.performsAtomicTransactions();
-  }
-
-  @Override
-  public String toString() {
-    return refDatabase.toString();
-  }
-
-  @Override
-  public Ref exactRef(String name) throws IOException {
-    return refDatabase.exactRef(name);
-  }
-
-  @Override
-  public Map<String, Ref> exactRef(String... refs) throws IOException {
-    return refDatabase.exactRef(refs);
-  }
-
-  @Override
-  public Ref firstExactRef(String... refs) throws IOException {
-    return refDatabase.firstExactRef(refs);
-  }
-
-  @Override
-  public List<Ref> getRefs() throws IOException {
-    return refDatabase.getRefs();
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  public Map<String, Ref> getRefs(String prefix) throws IOException {
-    return refDatabase.getRefs(prefix);
-  }
-
-  @Override
-  public List<Ref> getRefsByPrefix(String prefix) throws IOException {
-    return refDatabase.getRefsByPrefix(prefix);
-  }
-
-  @Override
-  public boolean hasRefs() throws IOException {
-    return refDatabase.hasRefs();
-  }
-
-  @Override
-  public List<Ref> getAdditionalRefs() throws IOException {
-    return refDatabase.getAdditionalRefs();
-  }
-
-  @Override
-  public Ref peel(Ref ref) throws IOException {
-    return refDatabase.peel(ref);
-  }
-
-  @Override
-  public void refresh() {
-    refDatabase.refresh();
-  }
-
-  RefUpdate wrapRefUpdate(RefUpdate refUpdate) {
-    return refUpdateFactory.create(projectName, refUpdate, refDatabase);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdate.java
deleted file mode 100644
index 20e12fe..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdate.java
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.NoParameterFunction;
-import java.io.IOException;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.PushCertificate;
-
-public class MultiSiteRefUpdate extends RefUpdate {
-
-  protected final RefUpdate refUpdateBase;
-  private final String projectName;
-  private final RefUpdateValidator.Factory refValidatorFactory;
-  private final RefUpdateValidator refUpdateValidator;
-
-  public interface Factory {
-    MultiSiteRefUpdate create(String projectName, RefUpdate refUpdate, RefDatabase refDb);
-  }
-
-  @Inject
-  public MultiSiteRefUpdate(
-      RefUpdateValidator.Factory refValidatorFactory,
-      @Assisted String projectName,
-      @Assisted RefUpdate refUpdate,
-      @Assisted RefDatabase refDb) {
-    super(refUpdate.getRef());
-    refUpdateBase = refUpdate;
-    this.projectName = projectName;
-    this.refValidatorFactory = refValidatorFactory;
-    refUpdateValidator = this.refValidatorFactory.create(this.projectName, refDb);
-  }
-
-  @Override
-  protected RefDatabase getRefDatabase() {
-    return notImplementedException();
-  }
-
-  private <T> T notImplementedException() {
-    throw new IllegalStateException("This method should have never been invoked");
-  }
-
-  @Override
-  protected Repository getRepository() {
-    return notImplementedException();
-  }
-
-  @Override
-  protected boolean tryLock(boolean deref) throws IOException {
-    return notImplementedException();
-  }
-
-  @Override
-  protected void unlock() {
-    notImplementedException();
-  }
-
-  @Override
-  protected Result doUpdate(Result result) throws IOException {
-    return notImplementedException();
-  }
-
-  @Override
-  protected Result doDelete(Result result) throws IOException {
-    return notImplementedException();
-  }
-
-  @Override
-  protected Result doLink(String target) throws IOException {
-    return notImplementedException();
-  }
-
-  @Override
-  public Result update() throws IOException {
-    return refUpdateValidator.executeRefUpdate(
-        refUpdateBase,
-        refUpdateBase::update,
-        objectId -> rollback(objectId, refUpdateBase::update));
-  }
-
-  @Override
-  public Result update(RevWalk rev) throws IOException {
-    return refUpdateValidator.executeRefUpdate(
-        refUpdateBase,
-        () -> refUpdateBase.update(rev),
-        objectId -> rollback(objectId, () -> refUpdateBase.update(rev)));
-  }
-
-  @Override
-  public Result delete() throws IOException {
-    return refUpdateValidator.executeRefUpdate(
-        refUpdateBase,
-        refUpdateBase::delete,
-        objectId -> rollback(objectId, refUpdateBase::update));
-  }
-
-  @Override
-  public Result delete(RevWalk walk) throws IOException {
-    return refUpdateValidator.executeRefUpdate(
-        refUpdateBase,
-        () -> refUpdateBase.delete(walk),
-        objectId -> rollback(objectId, () -> refUpdateBase.update(walk)));
-  }
-
-  @Override
-  public int hashCode() {
-    return refUpdateBase.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    return refUpdateBase.equals(obj);
-  }
-
-  @Override
-  public String toString() {
-    return refUpdateBase.toString();
-  }
-
-  @Override
-  public String getName() {
-    return refUpdateBase.getName();
-  }
-
-  @Override
-  public Ref getRef() {
-    return refUpdateBase.getRef();
-  }
-
-  @Override
-  public ObjectId getNewObjectId() {
-    return refUpdateBase.getNewObjectId();
-  }
-
-  @Override
-  public void setDetachingSymbolicRef() {
-    refUpdateBase.setDetachingSymbolicRef();
-  }
-
-  @Override
-  public boolean isDetachingSymbolicRef() {
-    return refUpdateBase.isDetachingSymbolicRef();
-  }
-
-  @Override
-  public void setNewObjectId(AnyObjectId id) {
-    refUpdateBase.setNewObjectId(id);
-  }
-
-  @Override
-  public ObjectId getExpectedOldObjectId() {
-    return refUpdateBase.getExpectedOldObjectId();
-  }
-
-  @Override
-  public void setExpectedOldObjectId(AnyObjectId id) {
-    refUpdateBase.setExpectedOldObjectId(id);
-  }
-
-  @Override
-  public boolean isForceUpdate() {
-    return refUpdateBase.isForceUpdate();
-  }
-
-  @Override
-  public void setForceUpdate(boolean b) {
-    refUpdateBase.setForceUpdate(b);
-  }
-
-  @Override
-  public PersonIdent getRefLogIdent() {
-    return refUpdateBase.getRefLogIdent();
-  }
-
-  @Override
-  public void setRefLogIdent(PersonIdent pi) {
-    refUpdateBase.setRefLogIdent(pi);
-  }
-
-  @Override
-  public String getRefLogMessage() {
-    return refUpdateBase.getRefLogMessage();
-  }
-
-  @Override
-  public void setRefLogMessage(String msg, boolean appendStatus) {
-    refUpdateBase.setRefLogMessage(msg, appendStatus);
-  }
-
-  @Override
-  public void disableRefLog() {
-    refUpdateBase.disableRefLog();
-  }
-
-  @Override
-  public void setForceRefLog(boolean force) {
-    refUpdateBase.setForceRefLog(force);
-  }
-
-  @Override
-  public ObjectId getOldObjectId() {
-    return refUpdateBase.getOldObjectId();
-  }
-
-  @Override
-  public void setPushCertificate(PushCertificate cert) {
-    refUpdateBase.setPushCertificate(cert);
-  }
-
-  @Override
-  public Result getResult() {
-    return refUpdateBase.getResult();
-  }
-
-  @Override
-  public Result forceUpdate() throws IOException {
-    return refUpdateValidator.executeRefUpdate(
-        refUpdateBase,
-        refUpdateBase::forceUpdate,
-        objectId -> rollback(objectId, refUpdateBase::forceUpdate));
-  }
-
-  @Override
-  public Result link(String target) throws IOException {
-    return refUpdateBase.link(target);
-  }
-
-  @Override
-  public void setCheckConflicting(boolean check) {
-    refUpdateBase.setCheckConflicting(check);
-  }
-
-  private Result rollback(ObjectId objectId, NoParameterFunction<Result> updateFunction)
-      throws IOException {
-    refUpdateBase.setExpectedOldObjectId(refUpdateBase.getNewObjectId());
-    refUpdateBase.setNewObjectId(objectId);
-    return updateFunction.invoke();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepository.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepository.java
deleted file mode 100644
index fa3de64..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepository.java
+++ /dev/null
@@ -1,407 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.eclipse.jgit.attributes.AttributesNodeProvider;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.errors.AmbiguousObjectException;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.NoWorkTreeException;
-import org.eclipse.jgit.errors.RevisionSyntaxException;
-import org.eclipse.jgit.events.ListenerList;
-import org.eclipse.jgit.events.RepositoryEvent;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BaseRepositoryBuilder;
-import org.eclipse.jgit.lib.ObjectDatabase;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.RebaseTodoLine;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefRename;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.ReflogReader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryState;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.util.FS;
-
-public class MultiSiteRepository extends Repository {
-
-  private final Repository repository;
-  private final RefDatabase refDatabase;
-  private final MultiSiteRefDatabase multiSiteRefDatabase;
-
-  public interface Factory {
-    public MultiSiteRepository create(String projectName, Repository repository);
-  }
-
-  @Inject
-  public MultiSiteRepository(
-      MultiSiteRefDatabase.Factory multiSiteRefDbFactory,
-      @Assisted String projectName,
-      @Assisted Repository repository) {
-    super(new BaseRepositoryBuilder());
-    this.repository = repository;
-    this.refDatabase = repository.getRefDatabase();
-    this.multiSiteRefDatabase = multiSiteRefDbFactory.create(projectName, refDatabase);
-  }
-
-  @Override
-  public void create(boolean b) throws IOException {}
-
-  @Override
-  public ObjectDatabase getObjectDatabase() {
-    return repository.getObjectDatabase();
-  }
-
-  @Override
-  public RefDatabase getRefDatabase() {
-    return multiSiteRefDatabase;
-  }
-
-  @Override
-  public StoredConfig getConfig() {
-    return repository.getConfig();
-  }
-
-  @Override
-  public AttributesNodeProvider createAttributesNodeProvider() {
-    return repository.createAttributesNodeProvider();
-  }
-
-  @Override
-  public void scanForRepoChanges() throws IOException {
-    repository.scanForRepoChanges();
-  }
-
-  @Override
-  public void notifyIndexChanged(boolean b) {
-    repository.notifyIndexChanged(b);
-  }
-
-  @Override
-  public ReflogReader getReflogReader(String s) throws IOException {
-    return repository.getReflogReader(s);
-  }
-
-  @Override
-  public int hashCode() {
-    return repository.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    return repository.equals(obj);
-  }
-
-  @Override
-  public ListenerList getListenerList() {
-    return repository.getListenerList();
-  }
-
-  @Override
-  public void fireEvent(RepositoryEvent<?> event) {
-    repository.fireEvent(event);
-  }
-
-  @Override
-  public void create() throws IOException {
-    repository.create();
-  }
-
-  @Override
-  public File getDirectory() {
-    return repository.getDirectory();
-  }
-
-  @Override
-  public ObjectInserter newObjectInserter() {
-    return repository.newObjectInserter();
-  }
-
-  @Override
-  public ObjectReader newObjectReader() {
-    return repository.newObjectReader();
-  }
-
-  @Override
-  public FS getFS() {
-    return repository.getFS();
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  public boolean hasObject(AnyObjectId objectId) {
-    return repository.hasObject(objectId);
-  }
-
-  @Override
-  public ObjectLoader open(AnyObjectId objectId) throws MissingObjectException, IOException {
-    return repository.open(objectId);
-  }
-
-  @Override
-  public ObjectLoader open(AnyObjectId objectId, int typeHint)
-      throws MissingObjectException, IncorrectObjectTypeException, IOException {
-    return repository.open(objectId, typeHint);
-  }
-
-  @Override
-  public RefUpdate updateRef(String ref) throws IOException {
-    return multiSiteRefDatabase.wrapRefUpdate(repository.updateRef(ref));
-  }
-
-  @Override
-  public RefUpdate updateRef(String ref, boolean detach) throws IOException {
-    return multiSiteRefDatabase.wrapRefUpdate(repository.updateRef(ref, detach));
-  }
-
-  @Override
-  public RefRename renameRef(String fromRef, String toRef) throws IOException {
-    return repository.renameRef(fromRef, toRef);
-  }
-
-  @Override
-  public ObjectId resolve(String revstr)
-      throws AmbiguousObjectException, IncorrectObjectTypeException, RevisionSyntaxException,
-          IOException {
-    return repository.resolve(revstr);
-  }
-
-  @Override
-  public String simplify(String revstr) throws AmbiguousObjectException, IOException {
-    return repository.simplify(revstr);
-  }
-
-  @Override
-  public void incrementOpen() {
-    repository.incrementOpen();
-  }
-
-  @Override
-  public void close() {
-    repository.close();
-  }
-
-  @Override
-  public String toString() {
-    return repository.toString();
-  }
-
-  @Override
-  public String getFullBranch() throws IOException {
-    return repository.getFullBranch();
-  }
-
-  @Override
-  public String getBranch() throws IOException {
-    return repository.getBranch();
-  }
-
-  @Override
-  public Set<ObjectId> getAdditionalHaves() {
-    return repository.getAdditionalHaves();
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  public Map<String, Ref> getAllRefs() {
-    return repository.getAllRefs();
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  public Map<String, Ref> getTags() {
-    return repository.getTags();
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  public Ref peel(Ref ref) {
-    return repository.peel(ref);
-  }
-
-  @Override
-  public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
-    return repository.getAllRefsByPeeledObjectId();
-  }
-
-  @Override
-  public File getIndexFile() throws NoWorkTreeException {
-    return repository.getIndexFile();
-  }
-
-  @Override
-  public RevCommit parseCommit(AnyObjectId id)
-      throws IncorrectObjectTypeException, IOException, MissingObjectException {
-    return repository.parseCommit(id);
-  }
-
-  @Override
-  public DirCache readDirCache() throws NoWorkTreeException, CorruptObjectException, IOException {
-    return repository.readDirCache();
-  }
-
-  @Override
-  public DirCache lockDirCache() throws NoWorkTreeException, CorruptObjectException, IOException {
-    return repository.lockDirCache();
-  }
-
-  @Override
-  public RepositoryState getRepositoryState() {
-    return repository.getRepositoryState();
-  }
-
-  @Override
-  public boolean isBare() {
-    return repository.isBare();
-  }
-
-  @Override
-  public File getWorkTree() throws NoWorkTreeException {
-    return repository.getWorkTree();
-  }
-
-  @Override
-  public String shortenRemoteBranchName(String refName) {
-    return repository.shortenRemoteBranchName(refName);
-  }
-
-  @Override
-  public String getRemoteName(String refName) {
-    return repository.getRemoteName(refName);
-  }
-
-  @Override
-  public String getGitwebDescription() throws IOException {
-    return repository.getGitwebDescription();
-  }
-
-  @Override
-  public void setGitwebDescription(String description) throws IOException {
-    repository.setGitwebDescription(description);
-  }
-
-  @Override
-  public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
-    return repository.readMergeCommitMsg();
-  }
-
-  @Override
-  public void writeMergeCommitMsg(String msg) throws IOException {
-    repository.writeMergeCommitMsg(msg);
-  }
-
-  @Override
-  public String readCommitEditMsg() throws IOException, NoWorkTreeException {
-    return repository.readCommitEditMsg();
-  }
-
-  @Override
-  public void writeCommitEditMsg(String msg) throws IOException {
-    repository.writeCommitEditMsg(msg);
-  }
-
-  @Override
-  public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
-    return repository.readMergeHeads();
-  }
-
-  @Override
-  public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
-    repository.writeMergeHeads(heads);
-  }
-
-  @Override
-  public ObjectId readCherryPickHead() throws IOException, NoWorkTreeException {
-    return repository.readCherryPickHead();
-  }
-
-  @Override
-  public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
-    return repository.readRevertHead();
-  }
-
-  @Override
-  public void writeCherryPickHead(ObjectId head) throws IOException {
-    repository.writeCherryPickHead(head);
-  }
-
-  @Override
-  public void writeRevertHead(ObjectId head) throws IOException {
-    repository.writeRevertHead(head);
-  }
-
-  @Override
-  public void writeOrigHead(ObjectId head) throws IOException {
-    repository.writeOrigHead(head);
-  }
-
-  @Override
-  public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
-    return repository.readOrigHead();
-  }
-
-  @Override
-  public String readSquashCommitMsg() throws IOException {
-    return repository.readSquashCommitMsg();
-  }
-
-  @Override
-  public void writeSquashCommitMsg(String msg) throws IOException {
-    repository.writeSquashCommitMsg(msg);
-  }
-
-  @Override
-  public List<RebaseTodoLine> readRebaseTodo(String path, boolean includeComments)
-      throws IOException {
-    return repository.readRebaseTodo(path, includeComments);
-  }
-
-  @Override
-  public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, boolean append)
-      throws IOException {
-    repository.writeRebaseTodoFile(path, steps, append);
-  }
-
-  @Override
-  public Set<String> getRemoteNames() {
-    return repository.getRemoteNames();
-  }
-
-  @Override
-  public void autoGC(ProgressMonitor monitor) {
-    repository.autoGC(monitor);
-  }
-
-  @Override
-  public String getIdentifier() {
-    return repository.getIdentifier();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
index f831671..98f4897 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
@@ -15,13 +15,13 @@
 package com.googlesource.gerrit.plugins.multisite.validation;
 
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.google.common.base.Preconditions;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 import java.io.IOException;
 import java.util.Collections;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
deleted file mode 100644
index 329c1c3..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.events.ProjectDeletedListener;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-
-public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final SharedRefDatabaseWrapper sharedDb;
-
-  private final ValidationMetrics validationMetrics;
-
-  @Inject
-  public ProjectDeletedSharedDbCleanup(
-      SharedRefDatabaseWrapper sharedDb, ValidationMetrics validationMetrics) {
-    this.sharedDb = sharedDb;
-    this.validationMetrics = validationMetrics;
-  }
-
-  @Override
-  public void onProjectDeleted(Event event) {
-    String projectName = event.getProjectName();
-    logger.atInfo().log(
-        "Deleting project '%s'. Will perform a cleanup in Shared-Ref database.", projectName);
-
-    try {
-      sharedDb.remove(Project.nameKey(projectName));
-    } catch (GlobalRefDbSystemError e) {
-      validationMetrics.incrementSplitBrain();
-      logger.atSevere().withCause(e).log(
-          "Project '%s' deleted from GIT but it was not able to cleanup"
-              + " from Shared-Ref database",
-          projectName);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
index 6924e71..1526059 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2022 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.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
index bf46aba..469122b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
@@ -18,6 +18,8 @@
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
@@ -30,8 +32,6 @@
 import com.google.gerrit.server.notedb.IntBlob;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import java.io.IOException;
 import java.util.Optional;
@@ -237,6 +237,7 @@
   /* (non-Javadoc)
    * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectRemoteVersion(java.lang.String)
    */
+  @Override
   public Optional<Long> getProjectRemoteVersion(String projectName) {
     Optional<String> globalVersion =
         sharedRefDb.get(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefPair.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefPair.java
deleted file mode 100644
index 77ae4f1..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefPair.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-
-public class RefPair {
-  public final Ref compareRef;
-  public final ObjectId putValue;
-  public final Exception exception;
-
-  RefPair(Ref oldRef, ObjectId newRefValue) {
-    if (oldRef == null) {
-      throw new IllegalArgumentException("Required not-null ref in RefPair");
-    }
-    this.compareRef = oldRef;
-    this.putValue = newRefValue;
-    this.exception = null;
-  }
-
-  RefPair(Ref newRef, Exception e) {
-    this.compareRef = newRef;
-    this.exception = e;
-    this.putValue = ObjectId.zeroId();
-  }
-
-  public String getName() {
-    return compareRef.getName();
-  }
-
-  public boolean hasFailed() {
-    return exception != null;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
deleted file mode 100644
index c110f0f..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.common.base.MoreObjects;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.multisite.LockWrapper;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import java.io.IOException;
-import java.util.HashMap;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-
-public class RefUpdateValidator {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  protected final SharedRefDatabaseWrapper sharedRefDb;
-  protected final ValidationMetrics validationMetrics;
-
-  protected final String projectName;
-  private final LockWrapper.Factory lockWrapperFactory;
-  protected final RefDatabase refDb;
-  protected final SharedRefEnforcement refEnforcement;
-  protected final ProjectsFilter projectsFilter;
-
-  public static interface Factory {
-    RefUpdateValidator create(String projectName, RefDatabase refDb);
-  }
-
-  public interface ExceptionThrowingSupplier<T, E extends Exception> {
-    T create() throws E;
-  }
-
-  public interface RefValidationWrapper {
-    RefUpdate.Result apply(NoParameterFunction<RefUpdate.Result> arg, RefUpdate refUpdate)
-        throws IOException;
-  }
-
-  public interface NoParameterFunction<T> {
-    T invoke() throws IOException;
-  }
-
-  public interface NoParameterVoidFunction {
-    void invoke() throws IOException;
-  }
-
-  public interface OneParameterFunction<F, T> {
-    T invoke(F f) throws IOException;
-  }
-
-  public interface OneParameterVoidFunction<T> {
-    void invoke(T f) throws IOException;
-  }
-
-  @Inject
-  public RefUpdateValidator(
-      SharedRefDatabaseWrapper sharedRefDb,
-      ValidationMetrics validationMetrics,
-      SharedRefEnforcement refEnforcement,
-      LockWrapper.Factory lockWrapperFactory,
-      ProjectsFilter projectsFilter,
-      @Assisted String projectName,
-      @Assisted RefDatabase refDb) {
-    this.sharedRefDb = sharedRefDb;
-    this.validationMetrics = validationMetrics;
-    this.lockWrapperFactory = lockWrapperFactory;
-    this.refDb = refDb;
-    this.projectName = projectName;
-    this.refEnforcement = refEnforcement;
-    this.projectsFilter = projectsFilter;
-  }
-
-  public RefUpdate.Result executeRefUpdate(
-      RefUpdate refUpdate,
-      NoParameterFunction<RefUpdate.Result> refUpdateFunction,
-      OneParameterFunction<ObjectId, Result> rollbackFunction)
-      throws IOException {
-    if (isProjectVersionUpdate(refUpdate.getName())
-        || !isGlobalProject(projectName)
-        || refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
-      return refUpdateFunction.invoke();
-    }
-
-    return doExecuteRefUpdate(refUpdate, refUpdateFunction, rollbackFunction);
-  }
-
-  private Boolean isProjectVersionUpdate(String refName) {
-    Boolean isProjectVersionUpdate =
-        refName.equals(ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF);
-    logger.atFine().log("Is project version update? " + isProjectVersionUpdate);
-    return isProjectVersionUpdate;
-  }
-
-  private <T extends Throwable> void softFailBasedOnEnforcement(T e, EnforcePolicy policy)
-      throws T {
-    logger.atWarning().withCause(e).log(
-        String.format(
-            "Failure while running with policy enforcement %s. Error message: %s",
-            policy, e.getMessage()));
-    if (policy == EnforcePolicy.REQUIRED) {
-      throw e;
-    }
-  }
-
-  protected Boolean isGlobalProject(String projectName) {
-    Boolean isGlobalProject = projectsFilter.matches(projectName);
-    logger.atFine().log("Is global project? " + isGlobalProject);
-    return isGlobalProject;
-  }
-
-  protected RefUpdate.Result doExecuteRefUpdate(
-      RefUpdate refUpdate,
-      NoParameterFunction<Result> refUpdateFunction,
-      OneParameterFunction<ObjectId, Result> rollbackFunction)
-      throws IOException {
-    try (CloseableSet<AutoCloseable> locks = new CloseableSet<>()) {
-      RefPair refPairForUpdate = newRefPairFrom(refUpdate);
-      compareAndGetLatestLocalRef(refPairForUpdate, locks);
-      RefUpdate.Result result = refUpdateFunction.invoke();
-      try {
-        if (isSuccessful(result)) {
-          updateSharedDbOrThrowExceptionFor(refPairForUpdate);
-        }
-      } catch (Exception e) {
-        result = rollbackFunction.invoke(refPairForUpdate.compareRef.getObjectId());
-        if (isSuccessful(result)) {
-          result = RefUpdate.Result.LOCK_FAILURE;
-        }
-        logger.atSevere().withCause(e).log(
-            String.format(
-                "Failed to update global refdb, the local refdb has been rolled back: %s",
-                e.getMessage()));
-      }
-      return result;
-    } catch (OutOfSyncException e) {
-      logger.atWarning().withCause(e).log(
-          String.format("Local node is out of sync with ref-db: %s", e.getMessage()));
-
-      return RefUpdate.Result.LOCK_FAILURE;
-    }
-  }
-
-  protected void updateSharedDbOrThrowExceptionFor(RefPair refPair) throws IOException {
-    // We are not checking refs that should be ignored
-    final EnforcePolicy refEnforcementPolicy =
-        refEnforcement.getPolicy(projectName, refPair.getName());
-    if (refEnforcementPolicy == EnforcePolicy.IGNORED) return;
-
-    String errorMessage =
-        String.format(
-            "Not able to persist the data in Zookeeper for project '%s' and ref '%s',"
-                + "the cluster is now in Split Brain since the commit has been "
-                + "persisted locally but not in SharedRef the value %s",
-            projectName, refPair.getName(), refPair.putValue);
-    boolean succeeded;
-    try {
-      succeeded =
-          sharedRefDb.compareAndPut(
-              Project.nameKey(projectName), refPair.compareRef, refPair.putValue);
-    } catch (GlobalRefDbSystemError e) {
-      logger.atWarning().withCause(e).log(
-          "Not able to persist the data in Zookeeper for project '{}' and ref '{}', message: {}",
-          projectName,
-          refPair.getName(),
-          e.getMessage());
-      throw e;
-    }
-
-    if (!succeeded) {
-      throw new SharedDbSplitBrainException(errorMessage);
-    }
-  }
-
-  protected RefPair compareAndGetLatestLocalRef(RefPair refPair, CloseableSet<AutoCloseable> locks)
-      throws SharedLockException, OutOfSyncException, IOException {
-    String refName = refPair.getName();
-    EnforcePolicy refEnforcementPolicy = refEnforcement.getPolicy(projectName, refName);
-    if (refEnforcementPolicy == EnforcePolicy.IGNORED) {
-      return refPair;
-    }
-
-    locks.addResourceIfNotExist(
-        String.format("%s-%s", projectName, refName),
-        () ->
-            lockWrapperFactory.create(
-                projectName, refName, sharedRefDb.lockRef(Project.nameKey(projectName), refName)));
-
-    RefPair latestRefPair = getLatestLocalRef(refPair);
-    if (sharedRefDb.isUpToDate(Project.nameKey(projectName), latestRefPair.compareRef)) {
-      return latestRefPair;
-    }
-
-    if (isNullRef(latestRefPair.compareRef)
-        || sharedRefDb.exists(Project.nameKey(projectName), refName)) {
-      validationMetrics.incrementSplitBrainPrevention();
-
-      softFailBasedOnEnforcement(
-          new OutOfSyncException(projectName, latestRefPair.compareRef), refEnforcementPolicy);
-    }
-
-    return latestRefPair;
-  }
-
-  private boolean isNullRef(Ref ref) {
-    return ref.getObjectId().equals(ObjectId.zeroId());
-  }
-
-  private RefPair getLatestLocalRef(RefPair refPair) throws IOException {
-    Ref latestRef = refDb.exactRef(refPair.getName());
-    return new RefPair(
-        latestRef == null ? nullRef(refPair.getName()) : latestRef, refPair.putValue);
-  }
-
-  private Ref nullRef(String name) {
-    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, name, ObjectId.zeroId());
-  }
-
-  protected boolean isSuccessful(RefUpdate.Result result) {
-    switch (result) {
-      case NEW:
-      case FORCED:
-      case FAST_FORWARD:
-      case NO_CHANGE:
-      case RENAMED:
-        return true;
-
-      case REJECTED_OTHER_REASON:
-      case REJECTED_MISSING_OBJECT:
-      case REJECTED_CURRENT_BRANCH:
-      case NOT_ATTEMPTED:
-      case LOCK_FAILURE:
-      case IO_FAILURE:
-      case REJECTED:
-      default:
-        return false;
-    }
-  }
-
-  protected RefPair newRefPairFrom(RefUpdate refUpdate) throws IOException {
-    return new RefPair(getCurrentRef(refUpdate.getName()), refUpdate.getNewObjectId());
-  }
-
-  protected Ref getCurrentRef(String refName) throws IOException {
-    return MoreObjects.firstNonNull(refDb.findRef(refName), nullRef(refName));
-  }
-
-  public static class CloseableSet<T extends AutoCloseable> implements AutoCloseable {
-    private final HashMap<String, AutoCloseable> elements;
-
-    public CloseableSet() {
-      this(new HashMap<>());
-    }
-
-    public CloseableSet(HashMap<String, AutoCloseable> elements) {
-      this.elements = elements;
-    }
-
-    public void addResourceIfNotExist(
-        String key, ExceptionThrowingSupplier<T, SharedLockException> resourceFactory)
-        throws SharedLockException {
-      if (!elements.containsKey(key)) {
-        elements.put(key, resourceFactory.create());
-      }
-    }
-
-    @Override
-    public void close() {
-      elements.values().stream()
-          .forEach(
-              closeable -> {
-                try {
-                  closeable.close();
-                } catch (Exception closingException) {
-                  logger.atSevere().withCause(closingException).log(
-                      "Exception trying to release resource %s, "
-                          + "the locked resources won't be accessible in all cluster unless"
-                          + " the lock is removed from ZK manually",
-                      closeable);
-                }
-              });
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RepositoryManagerModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RepositoryManagerModule.java
index 9b4d1a5..b71ca67 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RepositoryManagerModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RepositoryManagerModule.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.RepositoryConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -29,7 +30,7 @@
 
   @Override
   protected void configure() {
-    bind(GitRepositoryManager.class).to(MultiSiteGitRepositoryManager.class);
+    bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
 
     // part responsible for physical repositories handling
     listener().to(LocalDiskRepositoryManager.Lifecycle.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationMetrics.java
deleted file mode 100644
index cd2129a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationMetrics.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.google.gerrit.metrics.Counter1;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.MultiSiteMetrics;
-
-@Singleton
-public class ValidationMetrics extends MultiSiteMetrics {
-  private static final String GIT_UPDATE_SPLIT_BRAIN_PREVENTED = "git_update_split_brain_prevented";
-  private static final String GIT_UPDATE_SPLIT_BRAIN = "git_update_split_brain";
-
-  private final Counter1<String> splitBrainPreventionCounter;
-  private final Counter1<String> splitBrainCounter;
-
-  @Inject
-  public ValidationMetrics(MetricMaker metricMaker) {
-    this.splitBrainPreventionCounter =
-        metricMaker.newCounter(
-            "multi_site/validation/git_update_split_brain_prevented",
-            rateDescription("errors", "Rate of REST API error responses"),
-            stringField(
-                GIT_UPDATE_SPLIT_BRAIN_PREVENTED,
-                "Ref-update operations, split-brain detected and prevented"));
-
-    this.splitBrainCounter =
-        metricMaker.newCounter(
-            "multi_site/validation/git_update_split_brain",
-            rateDescription("errors", "Rate of REST API error responses"),
-            stringField(
-                GIT_UPDATE_SPLIT_BRAIN,
-                "Ref-update operation left node in a split-brain scenario"));
-  }
-
-  public void incrementSplitBrainPrevention() {
-    splitBrainPreventionCounter.increment(GIT_UPDATE_SPLIT_BRAIN_PREVENTED);
-  }
-
-  public void incrementSplitBrain() {
-    splitBrainCounter.increment(GIT_UPDATE_SPLIT_BRAIN);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index c7a2e0f..8601cf2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -14,18 +14,28 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
+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;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRepository;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefLogger;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.config.RepositoryConfig;
 import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.LockWrapper;
-import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 
@@ -46,19 +56,24 @@
     bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
     factory(LockWrapper.Factory.class);
 
-    factory(MultiSiteRepository.Factory.class);
-    factory(MultiSiteRefDatabase.Factory.class);
-    factory(MultiSiteRefUpdate.Factory.class);
-    factory(MultiSiteBatchRefUpdate.Factory.class);
+    factory(SharedRefDbRepository.Factory.class);
+    factory(SharedRefDbRefDatabase.Factory.class);
+    factory(SharedRefDbRefUpdate.Factory.class);
+    factory(SharedRefDbBatchRefUpdate.Factory.class);
     factory(RefUpdateValidator.Factory.class);
     factory(BatchRefUpdateValidator.Factory.class);
 
+    bind(new TypeLiteral<ImmutableSet<String>>() {})
+        .annotatedWith(Names.named(SharedRefDbGitRepositoryManager.IGNORED_REFS))
+        .toInstance(
+            ImmutableSet.of(
+                ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+                ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF));
     install(new RepositoryManagerModule(repoConfig));
-
     DynamicItem.bind(binder(), ReplicationPushFilter.class)
         .to(MultisiteReplicationPushFilter.class);
 
-    if (cfg.getSharedRefDb().getEnforcementRules().isEmpty()) {
+    if (cfg.getSharedRefDbConfiguration().getSharedRefDb().getEnforcementRules().isEmpty()) {
       bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
     } else {
       bind(SharedRefEnforcement.class)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
deleted file mode 100644
index 77a0c0b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import static com.google.common.base.Suppliers.memoize;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Splitter;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-public class CustomSharedRefEnforcementByProject implements SharedRefEnforcement {
-  private static final String ALL = ".*";
-
-  private final Supplier<Map<String, Map<String, EnforcePolicy>>> predefEnforcements;
-
-  @Inject
-  public CustomSharedRefEnforcementByProject(Configuration config) {
-    this.predefEnforcements = memoize(() -> parseDryRunEnforcementsToMap(config));
-  }
-
-  private static Map<String, Map<String, EnforcePolicy>> parseDryRunEnforcementsToMap(
-      Configuration config) {
-    Map<String, Map<String, EnforcePolicy>> enforcementMap = new HashMap<>();
-
-    for (Map.Entry<EnforcePolicy, String> enforcementEntry :
-        config.getSharedRefDb().getEnforcementRules().entries()) {
-      parseEnforcementEntry(enforcementMap, enforcementEntry);
-    }
-
-    return enforcementMap;
-  }
-
-  private static void parseEnforcementEntry(
-      Map<String, Map<String, EnforcePolicy>> enforcementMap,
-      Map.Entry<EnforcePolicy, String> enforcementEntry) {
-    Iterator<String> projectAndRef = Splitter.on(':').split(enforcementEntry.getValue()).iterator();
-    EnforcePolicy enforcementPolicy = enforcementEntry.getKey();
-
-    if (projectAndRef.hasNext()) {
-      String projectName = emptyToAll(projectAndRef.next());
-      String refName = emptyToAll(projectAndRef.hasNext() ? projectAndRef.next() : ALL);
-
-      Map<String, EnforcePolicy> existingOrDefaultRef =
-          enforcementMap.getOrDefault(projectName, new HashMap<>());
-
-      existingOrDefaultRef.put(refName, enforcementPolicy);
-
-      enforcementMap.put(projectName, existingOrDefaultRef);
-    }
-  }
-
-  private static String emptyToAll(String value) {
-    return value.trim().isEmpty() ? ALL : value;
-  }
-
-  @Override
-  public EnforcePolicy getPolicy(String projectName, String refName) {
-    if (isRefToBeIgnoredBySharedRefDb(refName)) {
-      return EnforcePolicy.IGNORED;
-    }
-
-    return getRefEnforcePolicy(projectName, refName);
-  }
-
-  private EnforcePolicy getRefEnforcePolicy(String projectName, String refName) {
-    Map<String, EnforcePolicy> orDefault =
-        predefEnforcements
-            .get()
-            .getOrDefault(
-                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
-
-    return MoreObjects.firstNonNull(
-        orDefault.getOrDefault(refName, orDefault.get(ALL)), EnforcePolicy.REQUIRED);
-  }
-
-  @Override
-  public EnforcePolicy getPolicy(String projectName) {
-    Map<String, EnforcePolicy> policiesForProject =
-        predefEnforcements
-            .get()
-            .getOrDefault(
-                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
-    return policiesForProject.getOrDefault(ALL, EnforcePolicy.REQUIRED);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcement.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcement.java
deleted file mode 100644
index 01cd5c5..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcement.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-public class DefaultSharedRefEnforcement implements SharedRefEnforcement {
-
-  @Override
-  public EnforcePolicy getPolicy(String projectName, String refName) {
-    return isRefToBeIgnoredBySharedRefDb(refName) ? EnforcePolicy.IGNORED : EnforcePolicy.REQUIRED;
-  }
-
-  @Override
-  public EnforcePolicy getPolicy(String projectName) {
-    return EnforcePolicy.REQUIRED;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
deleted file mode 100644
index 1530838..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.gerrit.entities.Project;
-import java.util.Optional;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-
-public class NoopSharedRefDatabase implements GlobalRefDatabase {
-
-  @Override
-  public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
-    return true;
-  }
-
-  @Override
-  public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
-      throws GlobalRefDbSystemError {
-    return true;
-  }
-
-  @Override
-  public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
-      throws GlobalRefDbSystemError {
-    return false;
-  }
-
-  @Override
-  public AutoCloseable lockRef(Project.NameKey project, String refName)
-      throws GlobalRefDbLockException {
-    return () -> {};
-  }
-
-  @Override
-  public boolean exists(Project.NameKey project, String refName) {
-    return false;
-  }
-
-  @Override
-  public void remove(Project.NameKey project) throws GlobalRefDbSystemError {}
-
-  @Override
-  public <T> Optional<T> get(Project.NameKey project, String refName, Class<T> clazz)
-      throws GlobalRefDbSystemError {
-    return Optional.empty();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/OutOfSyncException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/OutOfSyncException.java
deleted file mode 100644
index 50f06b9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/OutOfSyncException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import java.io.IOException;
-import org.eclipse.jgit.lib.Ref;
-
-/** Local project/ref is out of sync with the shared refdb */
-public class OutOfSyncException extends IOException {
-  private static final long serialVersionUID = 1L;
-
-  public OutOfSyncException(String project, Ref localRef) {
-    super(
-        localRef == null
-            ? String.format(
-                "Local ref doesn't exists locally for project %s but exists in the shared ref-db",
-                project)
-            : String.format(
-                "Local ref %s (ObjectId=%s) on project %s is out of sync with the shared ref-db",
-                localRef.getName(), localRef.getObjectId().getName(), project));
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedDbSplitBrainException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedDbSplitBrainException.java
deleted file mode 100644
index 8ca54c9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedDbSplitBrainException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import java.io.IOException;
-
-public class SharedDbSplitBrainException extends IOException {
-  private static final long serialVersionUID = 1L;
-
-  public SharedDbSplitBrainException(String message) {
-    super(message);
-  }
-
-  public SharedDbSplitBrainException(String message, Throwable cause) {
-    super(message, cause);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedLockException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedLockException.java
deleted file mode 100644
index e53c37c..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedLockException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import java.io.IOException;
-
-/** Unable to lock a project/ref resource. */
-public class SharedLockException extends IOException {
-  private static final long serialVersionUID = 1L;
-
-  public SharedLockException(String project, String refName, Exception cause) {
-    super(String.format("Unable to lock project %s on ref %s", project, refName), cause);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java
deleted file mode 100644
index 100def2..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-/** Type of enforcement to implement between the local and shared RefDb. */
-public interface SharedRefEnforcement {
-  public enum EnforcePolicy {
-    IGNORED,
-    REQUIRED;
-  }
-
-  /**
-   * Get the enforcement policy for a project/refName.
-   *
-   * @param projectName project to be enforced
-   * @param refName ref name to be enforced
-   * @return the {@link EnforcePolicy} value
-   */
-  public EnforcePolicy getPolicy(String projectName, String refName);
-
-  /**
-   * Get the enforcement policy for a project
-   *
-   * @param projectName
-   * @return the {@link EnforcePolicy} value
-   */
-  public EnforcePolicy getPolicy(String projectName);
-
-  /**
-   * Check if a refName should be ignored by shared Ref-Db
-   *
-   * @param refName
-   * @return true if ref should be ignored; false otherwise
-   */
-  default boolean isRefToBeIgnoredBySharedRefDb(String refName) {
-    return refName == null
-        || refName.startsWith("refs/draft-comments")
-        || (refName.startsWith("refs/changes") && !refName.endsWith("/meta"))
-        || refName.startsWith("refs/cache-automerge");
-  }
-}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index b6de0a9..623871b 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -10,6 +10,8 @@
 
 * events-broker library must be installed as a library module in the
   `$GERRIT_SITE/lib` directory of all the masters
+* global-refdb library must be installed as a library module in the
+  `$GERRIT_SITE/lib` directory of all the masters
 * connected to the same message broker
 * behind a load balancer (e.g., HAProxy)
 
diff --git a/src/main/resources/Documentation/http-endpoints.md b/src/main/resources/Documentation/http-endpoints.md
new file mode 100644
index 0000000..ff56fdf
--- /dev/null
+++ b/src/main/resources/Documentation/http-endpoints.md
@@ -0,0 +1,42 @@
+HTTP endpoints
+=========================
+
+The @PLUGIN@ plugin also provides HTTP endpoints, as described here below:
+
+## replication-lag
+
+Admin users can query for the replication lag in order to understand what
+projects' replication is running behind and by how much (in milliseconds).
+
+The results are returned in a map ordered in descending order by the replication
+lag, so that the most behind projects are returned first.
+
+Whilst some lag information is also available as a metric (see
+the [documentation](./about.md#metrics)), this endpoint provides more
+information since it shows _which_ project is associated to _which specific lag_.
+
+You can query the endpoint (at the receiving end of the replication) as follows:
+
+```bash
+curl -v -XGET -u <admin> '<gerrit>/a/plugins/multi-site/replication-lag?[limit=LIMIT]'
+```
+
+Output example:
+
+```
+)]}'
+{
+  "All-Users": 62,
+  "bar": 13,
+  "foo": 0,
+  "some/other/project": 1451,
+  "baz": 6432
+}
+```
+
+Optionally the REST endpoint can receive the following additional arguments:
+
+* limit=LIMIT
+
+maximum number of projects to return
+*default:10*
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/GitModuleTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/GitModuleTest.java
index eb50991..7faccae 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/GitModuleTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/GitModuleTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.UseLocalDisk;
@@ -25,7 +26,6 @@
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.server.git.MultiBaseLocalDiskRepositoryManager;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.validation.MultiSiteGitRepositoryManager;
 import org.junit.Test;
 
 @UseLocalDisk
@@ -39,7 +39,7 @@
       name = "gerrit.installDbModule",
       value = "com.googlesource.gerrit.plugins.multisite.GitModule")
   public void shouldUseLocalDiskRepositoryManagerByDefault() {
-    assertThat(gitRepoManager).isInstanceOf(MultiSiteGitRepositoryManager.class);
+    assertThat(gitRepoManager).isInstanceOf(SharedRefDbGitRepositoryManager.class);
     assertThat(wrapped).isNotInstanceOf(MultiBaseLocalDiskRepositoryManager.class);
   }
 
@@ -49,7 +49,7 @@
       value = "com.googlesource.gerrit.plugins.multisite.GitModule")
   @GerritConfig(name = "repository.r1.basePath", value = "/tmp/git1")
   public void shouldUseMultiBaseLocalDiskRepositoryManagerWhenItIsConfigured() {
-    assertThat(gitRepoManager).isInstanceOf(MultiSiteGitRepositoryManager.class);
+    assertThat(gitRepoManager).isInstanceOf(SharedRefDbGitRepositoryManager.class);
     assertThat(wrapped).isInstanceOf(MultiBaseLocalDiskRepositoryManager.class);
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java
deleted file mode 100644
index e81c906..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java
+++ /dev/null
@@ -1,180 +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.multisite;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.collect.Lists;
-import com.google.gerrit.entities.Project.NameKey;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.ProjectEvent;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gerrit.testing.GerritJUnit;
-import com.googlesource.gerrit.plugins.multisite.Configuration.Projects;
-import java.util.Collections;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ProjectsFilterTest {
-
-  @Mock private Configuration configuration;
-  @Mock private Projects projects;
-
-  private ProjectsFilter objectUnderTest;
-
-  @Test
-  public void shouldMatchByExactProjectName() {
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isFalse();
-  }
-
-  @Test
-  public void shouldMatchByWildcard() {
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project*"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("2test_project"))).isFalse();
-  }
-
-  @Test
-  public void shouldMatchByRegex() {
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("^test_(project|project2)"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
-  }
-
-  @Test
-  public void shouldExcludeByRegex() {
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("^(?:(?!test_project3).)*$"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
-  }
-
-  @Test
-  public void shouldExcludeByMultipleProjectsRegexPattern() {
-    when(projects.getPatterns())
-        .thenReturn(Lists.newArrayList("^(?:(?!(test_project3|test_project4)).)*$"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project4"))).isFalse();
-  }
-
-  @Test
-  public void shouldMatchWhenNoPatternProvided() {
-    when(projects.getPatterns()).thenReturn(Collections.emptyList());
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
-  }
-
-  @Test
-  public void shouldMatchProjectEvent() {
-    ProjectEvent event = mock(ProjectEvent.class);
-    when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project"));
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(event)).isTrue();
-  }
-
-  @Test
-  public void shouldMatchRefUpdatedEvent() {
-    RefUpdatedEvent event = mock(RefUpdatedEvent.class);
-    when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project"));
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(event)).isTrue();
-  }
-
-  @Test
-  public void shouldExcludedNonProjectEvents() {
-    Event event = mock(Event.class);
-    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project)"));
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    assertThat(objectUnderTest.matches(event)).isFalse();
-  }
-
-  @Test
-  public void shouldThrowExceptionWhenProjecNameIsNull() {
-    when(projects.getPatterns()).thenReturn(Collections.emptyList());
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    GerritJUnit.assertThrows(
-        IllegalArgumentException.class, () -> objectUnderTest.matches((NameKey) null));
-  }
-
-  @Test
-  public void shouldThrowExceptionWhenProjecNameIsEmpty() {
-    when(projects.getPatterns()).thenReturn(Collections.emptyList());
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    GerritJUnit.assertThrows(
-        IllegalArgumentException.class, () -> objectUnderTest.matches(NameKey.parse("")));
-  }
-
-  @Test
-  public void shouldThrowExceptionWhenEventIsNull() {
-    when(projects.getPatterns()).thenReturn(Collections.emptyList());
-    when(configuration.projects()).thenReturn(projects);
-
-    objectUnderTest = new ProjectsFilter(configuration);
-
-    GerritJUnit.assertThrows(
-        IllegalArgumentException.class, () -> objectUnderTest.matches((Event) null));
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
index 2a25c37..4263ddb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
@@ -23,11 +23,11 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.cache.ProjectListUpdateHandler.ProjectListUpdateTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
new file mode 100644
index 0000000..ce82954
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.consumer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.project.ProjectCache;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ReplicationStatusTest {
+
+  @Mock private ProjectVersionLogger verLogger;
+  @Mock private ProjectCache projectCache;
+  @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
+  private ReplicationStatus objectUnderTest;
+  private Cache<String, Long> replicationStatusCache;
+
+  @Before
+  public void setup() throws Exception {
+    when(projectCache.all())
+        .thenReturn(
+            ImmutableSortedSet.of(Project.nameKey("projectA"), Project.nameKey("projectB")));
+    replicationStatusCache = CacheBuilder.newBuilder().build();
+    objectUnderTest =
+        new ReplicationStatus(
+            replicationStatusCache, Optional.of(projectVersionRefUpdate), verLogger, projectCache);
+  }
+
+  @Test
+  public void shouldPopulateLagsFromPersistedCacheOnStart() {
+    replicationStatusCache.put("projectA", 10L);
+    replicationStatusCache.put("projectB", 3L);
+
+    objectUnderTest.start();
+    assertThat(objectUnderTest.getMaxLag()).isEqualTo(10L);
+  }
+
+  @Test
+  public void shouldBeAbleToUpdatePersistedCacheValues() {
+    replicationStatusCache.put("projectA", 3L);
+
+    objectUnderTest.start();
+
+    objectUnderTest.doUpdateLag(Project.nameKey("projectA"), 20L);
+    assertThat(objectUnderTest.getMaxLag()).isEqualTo(20L);
+  }
+
+  @Test
+  public void shouldCombinePersistedProjectsWithNewEntries() {
+    replicationStatusCache.put("projectA", 3L);
+    objectUnderTest.start();
+
+    objectUnderTest.doUpdateLag(Project.nameKey("projectB"), 20L);
+
+    assertThat(objectUnderTest.getReplicationLags(2).keySet())
+        .containsExactly("projectA", "projectB");
+  }
+
+  @Test
+  public void shouldUpdatePersistedCacheWhenUpdatingLagValue() {
+    objectUnderTest.doUpdateLag(Project.nameKey("projectA"), 20L);
+
+    assertThat(replicationStatusCache.getIfPresent("projectA")).isEqualTo(20L);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
index 99148fa..4c45e07 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
@@ -18,14 +18,16 @@
 import static org.mockito.Mockito.when;
 
 import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheBuilder;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.project.ProjectCache;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
 import java.util.Optional;
 import java.util.UUID;
@@ -45,6 +47,7 @@
   @Mock private GitReferenceUpdated gitReferenceUpdated;
   @Mock private MetricMaker metricMaker;
   @Mock private ProjectVersionLogger verLogger;
+  @Mock private ProjectCache projectCache;
   @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
   private SubscriberMetrics metrics;
   private EventMessage.Header msgHeader;
@@ -52,7 +55,14 @@
   @Before
   public void setup() throws Exception {
     msgHeader = new EventMessage.Header(UUID.randomUUID(), UUID.randomUUID());
-    metrics = new SubscriberMetrics(metricMaker, projectVersionRefUpdate, verLogger);
+    metrics =
+        new SubscriberMetrics(
+            metricMaker,
+            new ReplicationStatus(
+                CacheBuilder.newBuilder().build(),
+                Optional.of(projectVersionRefUpdate),
+                verLogger,
+                projectCache));
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
index a632b74..76c001f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
@@ -16,10 +16,13 @@
 
 import static org.mockito.Mockito.verify;
 
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.EventGsonProvider;
 import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.multisite.cache.Constants;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEntry;
+import com.googlesource.gerrit.plugins.multisite.forwarder.CacheKeyJsonParser;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.GsonParser;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.CacheEvictionEventRouter;
 import org.junit.Before;
@@ -31,12 +34,13 @@
 @RunWith(MockitoJUnitRunner.class)
 public class CacheEvictionEventRouterTest {
 
+  private static Gson gson = new EventGsonProvider().get();
   private CacheEvictionEventRouter router;
   @Mock private ForwardedCacheEvictionHandler cacheEvictionHandler;
 
   @Before
   public void setUp() {
-    router = new CacheEvictionEventRouter(cacheEvictionHandler, new GsonParser(new Gson()));
+    router = new CacheEvictionEventRouter(cacheEvictionHandler, new CacheKeyJsonParser(gson));
   }
 
   @Test
@@ -48,11 +52,21 @@
   }
 
   @Test
-  public void routerShouldSendEventsToTheAppropriateHandler_ProjectCacheEvictionWithSlash()
+  public void routerShouldSendEventsToTheAppropriateHandler_CacheEvictionWithSlash()
       throws Exception {
-    final CacheEvictionEvent event = new CacheEvictionEvent("cache", "some/project");
+    final CacheEvictionEvent event = new CacheEvictionEvent("cache", "some/key");
     router.route(event);
 
     verify(cacheEvictionHandler).evict(CacheEntry.from(event.cacheName, event.key));
   }
+
+  @Test
+  public void routerShouldSendEventsToTheAppropriateHandler_ProjectCacheEvictionWithSlash()
+      throws Exception {
+    final CacheEvictionEvent event = new CacheEvictionEvent(Constants.PROJECTS, "some/project");
+    router.route(event);
+
+    verify(cacheEvictionHandler)
+        .evict(CacheEntry.from(event.cacheName, Project.nameKey((String) event.key)));
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
index bac0851..b9b416d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
@@ -22,12 +22,12 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.event.EventHandler.EventTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParserTest.java
similarity index 70%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParserTest.java
index 12bdb74..e42b4e5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheKeyJsonParserTest.java
@@ -18,42 +18,50 @@
 
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.events.EventGsonProvider;
 import com.google.gson.Gson;
 import com.googlesource.gerrit.plugins.multisite.cache.Constants;
 import org.junit.Test;
 
-public class GsonParserTest {
+public class CacheKeyJsonParserTest {
   private static final Object EMPTY_JSON = "{}";
 
   private final Gson gson = new EventGsonProvider().get();
-  private final GsonParser gsonParser = new GsonParser(gson);
+  private final CacheKeyJsonParser gsonParser = new CacheKeyJsonParser(gson);
 
   @Test
   public void accountIDParse() {
     Account.Id accountId = Account.id(1);
     String json = gson.toJson(accountId);
-    assertThat(accountId).isEqualTo(gsonParser.fromJson(Constants.ACCOUNTS, json));
+    assertThat(accountId).isEqualTo(gsonParser.from(Constants.ACCOUNTS, json));
   }
 
   @Test
   public void accountGroupIDParse() {
     AccountGroup.Id accountGroupId = AccountGroup.id(1);
     String json = gson.toJson(accountGroupId);
-    assertThat(accountGroupId).isEqualTo(gsonParser.fromJson(Constants.GROUPS, json));
+    assertThat(accountGroupId).isEqualTo(gsonParser.from(Constants.GROUPS, json));
   }
 
   @Test
   public void accountGroupUUIDParse() {
     AccountGroup.UUID accountGroupUuid = AccountGroup.uuid("abc123");
     String json = gson.toJson(accountGroupUuid);
-    assertThat(accountGroupUuid).isEqualTo(gsonParser.fromJson(Constants.GROUPS_BYINCLUDE, json));
+    assertThat(accountGroupUuid).isEqualTo(gsonParser.from(Constants.GROUPS_BYINCLUDE, json));
+  }
+
+  @Test
+  public void projectNameKeyParse() {
+    String projectNameString = "foo";
+    Project.NameKey projectNameKey = Project.nameKey(projectNameString);
+    assertThat(projectNameKey).isEqualTo(gsonParser.from(Constants.PROJECTS, projectNameString));
   }
 
   @Test
   public void stringParse() {
     String key = "key";
-    assertThat(key).isEqualTo(gsonParser.fromJson(Constants.PROJECTS, key));
+    assertThat(key).isEqualTo(gsonParser.from("any-cache-with-string-key", key));
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerIT.java
new file mode 100644
index 0000000..826e154
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerIT.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.forwarder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.events.EventGson;
+import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gson.Gson;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
+import com.googlesource.gerrit.plugins.multisite.forwarder.router.CacheEvictionEventRouter;
+import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
+import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+    name = "multi-site",
+    sysModule =
+        "com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandlerIT$TestModule")
+public class ForwardedCacheEvictionHandlerIT extends LightweightPluginDaemonTest {
+  private static final Duration CACHE_EVICTIONS_WAIT_TIMEOUT = Duration.ofMinutes(1);
+
+  @SuppressWarnings("rawtypes")
+  @Inject
+  private DynamicSet<CacheRemovalListener> cacheRemovalListeners;
+
+  @Inject private CacheEvictionEventRouter objectUnderTest;
+  @Inject @EventGson private Gson gson;
+  private CacheEvictionsTracker<?, ?> evictionsCacheTracker;
+  private RegistrationHandle cacheEvictionRegistrationHandle;
+
+  public static class TestModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      install(new ForwarderModule());
+      install(new CacheModule());
+      install(new RouterModule());
+      install(new IndexModule());
+      SharedRefDbConfiguration sharedRefDbConfig =
+          new SharedRefDbConfiguration(new Config(), "multi-site");
+      bind(SharedRefDbConfiguration.class).toInstance(sharedRefDbConfig);
+    }
+  }
+
+  public static class CacheEvictionsTracker<K, V> implements CacheRemovalListener<K, V> {
+    private final Map<String, Set<Object>> trackedEvictions;
+    private final CountDownLatch allExpectedEvictionsArrived;
+
+    public CacheEvictionsTracker(int numExpectedEvictions) {
+      allExpectedEvictionsArrived = new CountDownLatch(numExpectedEvictions);
+      trackedEvictions = Maps.newHashMap();
+    }
+
+    public Set<Object> trackedEvictionsFor(String cacheName) {
+      return trackedEvictions.getOrDefault(cacheName, Collections.emptySet());
+    }
+
+    public void waitForExpectedEvictions() throws InterruptedException {
+      allExpectedEvictionsArrived.await(
+          CACHE_EVICTIONS_WAIT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onRemoval(
+        String pluginName, String cacheName, RemovalNotification<K, V> notification) {
+      trackedEvictions.compute(
+          cacheName,
+          (k, v) -> {
+            if (v == null) {
+              return Sets.newHashSet(notification.getKey());
+            }
+            v.add(notification.getKey());
+            return v;
+          });
+      allExpectedEvictionsArrived.countDown();
+    }
+  }
+
+  @Before
+  public void startTrackingCacheEvictions() {
+    evictionsCacheTracker = new CacheEvictionsTracker<>(1);
+    cacheEvictionRegistrationHandle = cacheRemovalListeners.add("gerrit", evictionsCacheTracker);
+  }
+
+  @After
+  public void stopTrackingCacheEvictions() {
+    cacheEvictionRegistrationHandle.remove();
+  }
+
+  @Test
+  public void shouldEvictProjectCache() throws Exception {
+    objectUnderTest.route(new CacheEvictionEvent(ProjectCacheImpl.CACHE_NAME, project.get()));
+    evictionsCacheTracker.waitForExpectedEvictions();
+
+    assertThat(evictionsCacheTracker.trackedEvictionsFor(ProjectCacheImpl.CACHE_NAME))
+        .contains(project);
+  }
+
+  @Test
+  public void shouldEvictProjectCacheWithSlash() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.name = name("my/project");
+    gApi.projects().create(in);
+    Project.NameKey projectNameKey = Project.nameKey(in.name);
+
+    restartCacheEvictionsTracking();
+
+    objectUnderTest.route(
+        new CacheEvictionEvent(ProjectCacheImpl.CACHE_NAME, projectNameKey.get()));
+
+    evictionsCacheTracker.waitForExpectedEvictions();
+    assertThat(evictionsCacheTracker.trackedEvictionsFor(ProjectCacheImpl.CACHE_NAME))
+        .contains(projectNameKey);
+  }
+
+  private void restartCacheEvictionsTracking() {
+    stopTrackingCacheEvictions();
+    startTrackingCacheEvictions();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
new file mode 100644
index 0000000..0739eab
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
@@ -0,0 +1,135 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.http;
+
+import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.multisite.http.HttpModule.LAG_ENDPOINT_SEGMENT;
+
+import com.gerritforge.gerrit.globalrefdb.validation.Log4jSharedRefLogger;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefLogger;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.OptionalBinder;
+import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
+import com.googlesource.gerrit.plugins.multisite.consumer.ReplicationStatus;
+import com.googlesource.gerrit.plugins.multisite.consumer.ReplicationStatusModule;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
+import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
+import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdateImpl;
+import java.io.IOException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+    name = "multi-site",
+    sysModule =
+        "com.googlesource.gerrit.plugins.multisite.http.ReplicationStatusServletIT$TestModule",
+    httpModule = "com.googlesource.gerrit.plugins.multisite.http.HttpModule")
+public class ReplicationStatusServletIT extends LightweightPluginDaemonTest {
+  private static final String APPLICATION_JSON = "application/json";
+  private static final String LAG_ENDPOINT =
+      String.format("/plugins/multi-site/%s", LAG_ENDPOINT_SEGMENT);
+  private ReplicationStatus replicationStatus;
+
+  public static class TestModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      install(new ForwarderModule());
+      install(new CacheModule());
+      install(new RouterModule());
+      install(new IndexModule());
+      install(new ReplicationStatusModule());
+      SharedRefDbConfiguration sharedRefDbConfig =
+          new SharedRefDbConfiguration(new Config(), "multi-site");
+      bind(SharedRefDbConfiguration.class).toInstance(sharedRefDbConfig);
+      bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
+      bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+      OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class)
+          .setBinding()
+          .to(ProjectVersionRefUpdateImpl.class)
+          .in(Scopes.SINGLETON);
+    }
+  }
+
+  @Before
+  public void setUp() throws IOException {
+    replicationStatus = plugin.getSysInjector().getInstance(ReplicationStatus.class);
+  }
+
+  @Test
+  public void shouldSucceedForAdminUsers() throws Exception {
+    RestResponse result = adminRestSession.get(LAG_ENDPOINT);
+
+    result.assertOK();
+    assertThat(result.getHeader(CONTENT_TYPE)).contains(APPLICATION_JSON);
+  }
+
+  @Test
+  public void shouldFailWhenUserHasNoAdminServerCapability() throws Exception {
+    RestResponse result = userRestSession.get(LAG_ENDPOINT);
+    result.assertForbidden();
+    assertThat(result.getEntityContent()).contains("not permitted");
+  }
+
+  @Test
+  public void shouldReturnCurrentProjectLag() throws Exception {
+    replicationStatus.doUpdateLag(Project.nameKey("foo"), 123L);
+
+    RestResponse result = adminRestSession.get(LAG_ENDPOINT);
+
+    result.assertOK();
+    assertThat(contentWithoutMagicJson(result)).isEqualTo("{\"foo\":123}");
+  }
+
+  @Test
+  public void shouldReturnProjectsOrderedDescendinglyByLag() throws Exception {
+    replicationStatus.doUpdateLag(Project.nameKey("bar"), 123L);
+    replicationStatus.doUpdateLag(Project.nameKey("foo"), 3L);
+    replicationStatus.doUpdateLag(Project.nameKey("baz"), 52300L);
+
+    RestResponse result = adminRestSession.get(LAG_ENDPOINT);
+
+    result.assertOK();
+    assertThat(contentWithoutMagicJson(result)).isEqualTo("{\"baz\":52300,\"bar\":123,\"foo\":3}");
+  }
+
+  @Test
+  public void shouldHonourTheLimitParameter() throws Exception {
+    replicationStatus.doUpdateLag(Project.nameKey("bar"), 1L);
+    replicationStatus.doUpdateLag(Project.nameKey("foo"), 2L);
+    replicationStatus.doUpdateLag(Project.nameKey("baz"), 3L);
+
+    RestResponse result = adminRestSession.get(String.format("%s?limit=2", LAG_ENDPOINT));
+
+    result.assertOK();
+    assertThat(contentWithoutMagicJson(result)).isEqualTo("{\"baz\":3,\"foo\":2}");
+  }
+
+  private String contentWithoutMagicJson(RestResponse response) throws IOException {
+    return response.getEntityContent().substring(RestApiServlet.JSON_MAGIC.length);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
index 022dec5..660a302 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
@@ -21,9 +21,9 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent;
 import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexProjectTask;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
deleted file mode 100644
index 41bebc2..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.metrics.DisabledMetricMaker;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.eclipse.jgit.internal.storage.file.RefDirectory;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-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.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class BatchRefUpdateValidatorTest extends LocalDiskRepositoryTestCase implements RefFixture {
-  @Rule public TestName nameRule = new TestName();
-
-  private Repository diskRepo;
-  private TestRepository<Repository> repo;
-  private RefDirectory refdir;
-  private RevCommit A;
-  private RevCommit B;
-
-  @Mock SharedRefDatabaseWrapper sharedRefDatabase;
-
-  @Mock SharedRefEnforcement tmpRefEnforcement;
-
-  @Mock ProjectsFilter projectsFilter;
-
-  @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction;
-
-  @Before
-  public void setup() throws Exception {
-    super.setUp();
-    when(projectsFilter.matches(anyString())).thenReturn(true);
-    gitRepoSetup();
-  }
-
-  private void gitRepoSetup() throws Exception {
-    diskRepo = createBareRepository();
-    refdir = (RefDirectory) diskRepo.getRefDatabase();
-    repo = new TestRepository<>(diskRepo);
-    A = repo.commit().create();
-    B = repo.commit(repo.getRevWalk().parseCommit(A));
-  }
-
-  @Test
-  public void immutableChangeShouldNotBeWrittenIntoZk() throws Exception {
-    String AN_IMMUTABLE_REF = "refs/changes/01/1/1";
-
-    List<ReceiveCommand> cmds = Arrays.asList(new ReceiveCommand(A, B, AN_IMMUTABLE_REF, UPDATE));
-
-    BatchRefUpdate batchRefUpdate = newBatchUpdate(cmds);
-    BatchRefUpdateValidator BatchRefUpdateValidator = newDefaultValidator(A_TEST_PROJECT_NAME);
-
-    BatchRefUpdateValidator.executeBatchUpdateWithValidation(
-        batchRefUpdate, () -> execute(batchRefUpdate), this::defaultRollback);
-
-    verify(sharedRefDatabase, never())
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-  }
-
-  @Test
-  public void compareAndPutShouldAlwaysIngoreAlwaysDraftCommentsEvenOutOfOrder() throws Exception {
-    String DRAFT_COMMENT = "refs/draft-comments/56/450756/1013728";
-    List<ReceiveCommand> cmds = Arrays.asList(new ReceiveCommand(A, B, DRAFT_COMMENT, UPDATE));
-
-    BatchRefUpdate batchRefUpdate = newBatchUpdate(cmds);
-    BatchRefUpdateValidator BatchRefUpdateValidator = newDefaultValidator(A_TEST_PROJECT_NAME);
-
-    BatchRefUpdateValidator.executeBatchUpdateWithValidation(
-        batchRefUpdate, () -> execute(batchRefUpdate), this::defaultRollback);
-
-    verify(sharedRefDatabase, never())
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, newRef(DRAFT_COMMENT, A.getId()), B.getId());
-  }
-
-  @Test
-  public void validationShouldFailWhenLocalRefDbIsOutOfSync() throws Exception {
-    String AN_OUT_OF_SYNC_REF = "refs/changes/01/1/1";
-    BatchRefUpdate batchRefUpdate =
-        newBatchUpdate(
-            Collections.singletonList(new ReceiveCommand(A, B, AN_OUT_OF_SYNC_REF, UPDATE)));
-    BatchRefUpdateValidator batchRefUpdateValidator =
-        getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement);
-
-    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
-        .when(batchRefUpdateValidator.refEnforcement)
-        .getPolicy(A_TEST_PROJECT_NAME, AN_OUT_OF_SYNC_REF);
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDatabase)
-        .isUpToDate(A_TEST_PROJECT_NAME_KEY, newRef(AN_OUT_OF_SYNC_REF, AN_OBJECT_ID_1));
-
-    batchRefUpdateValidator.executeBatchUpdateWithValidation(
-        batchRefUpdate, () -> execute(batchRefUpdate), rollbackFunction);
-
-    verify(rollbackFunction, never()).invoke(any());
-
-    final List<ReceiveCommand> commands = batchRefUpdate.getCommands();
-    assertThat(commands.size()).isEqualTo(1);
-    commands.forEach(
-        (command) -> assertThat(command.getResult()).isEqualTo(ReceiveCommand.Result.LOCK_FAILURE));
-  }
-
-  @Test
-  public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception {
-    when(projectsFilter.matches(anyString())).thenReturn(false);
-
-    String AN_OUT_OF_SYNC_REF = "refs/changes/01/1/1";
-    BatchRefUpdate batchRefUpdate =
-        newBatchUpdate(
-            Collections.singletonList(new ReceiveCommand(A, B, AN_OUT_OF_SYNC_REF, UPDATE)));
-    BatchRefUpdateValidator batchRefUpdateValidator =
-        getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement);
-
-    batchRefUpdateValidator.executeBatchUpdateWithValidation(
-        batchRefUpdate, () -> execute(batchRefUpdate), this::defaultRollback);
-
-    verify(sharedRefDatabase, never())
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-  }
-
-  private BatchRefUpdateValidator newDefaultValidator(String projectName) {
-    return getRefValidatorForEnforcement(projectName, new DefaultSharedRefEnforcement());
-  }
-
-  private BatchRefUpdateValidator getRefValidatorForEnforcement(
-      String projectName, SharedRefEnforcement sharedRefEnforcement) {
-    return new BatchRefUpdateValidator(
-        sharedRefDatabase,
-        new ValidationMetrics(new DisabledMetricMaker()),
-        sharedRefEnforcement,
-        new DummyLockWrapper(),
-        projectsFilter,
-        projectName,
-        diskRepo.getRefDatabase());
-  }
-
-  private Void execute(BatchRefUpdate u) throws IOException {
-    try (RevWalk rw = new RevWalk(diskRepo)) {
-      u.execute(rw, NullProgressMonitor.INSTANCE);
-    }
-    return null;
-  }
-
-  private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
-    BatchRefUpdate u = refdir.newBatchUpdate();
-    u.addCommand(cmds);
-    cmds.forEach(c -> c.setResult(Result.OK));
-    return u;
-  }
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  private void defaultRollback(List<ReceiveCommand> cmds) throws IOException {
-    // do nothing
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
deleted file mode 100644
index 047a1c5..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Ignore;
-
-@Ignore
-public class DisabledSharedRefLogger implements SharedRefLogger {
-
-  @Override
-  public void logRefUpdate(String project, Ref currRef, ObjectId newRefValue) {}
-
-  @Override
-  public void logProjectDelete(String project) {}
-
-  @Override
-  public void logLockAcquisition(String project, String refName) {}
-
-  @Override
-  public void logLockRelease(String project, String refName) {}
-
-  @Override
-  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {}
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java
deleted file mode 100644
index 1ff0429..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite.validation;
-
-import com.googlesource.gerrit.plugins.multisite.LockWrapper;
-import org.junit.Ignore;
-
-@Ignore
-public class DummyLockWrapper implements LockWrapper.Factory {
-
-  @Override
-  public LockWrapper create(String project, String refName, AutoCloseable lock) {
-    return new LockWrapper(new DisabledSharedRefLogger(), project, refName, lock);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
deleted file mode 100644
index 3debdf4..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.notedb.Sequences;
-import com.google.gerrit.server.util.SystemLog;
-import com.google.gson.Gson;
-import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.SharedRefLogEntry;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.PatternLayout;
-import org.apache.log4j.WriterAppender;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class Log4jSharedRefLoggerTest extends AbstractDaemonTest {
-
-  private static final Gson gson = OutputFormat.JSON_COMPACT.newGson();
-  private StringWriter logWriter;
-  private Log4jSharedRefLogger log4jSharedRefLogger;
-
-  @Before
-  public void setUp() throws IOException {
-    this.logWriter = new StringWriter();
-    this.log4jSharedRefLogger = newLog4jSharedRefLogger();
-  }
-
-  @Test
-  public void shouldLogProjectDeletion() {
-    log4jSharedRefLogger.logProjectDelete(project.get());
-
-    SharedRefLogEntry.DeleteProject gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.DeleteProject.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.DELETE_PROJECT);
-    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
-  }
-
-  @Test
-  public void shouldLogUpdateRef() throws Exception {
-    final String refName = "refs/remotes/origin/master";
-    Ref currRef = repo().exactRef(refName);
-    PushOneCommit.Result result = pushTo(refName);
-    ObjectId newRefValue = result.getCommit().toObjectId();
-
-    log4jSharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
-
-    SharedRefLogEntry.UpdateRef gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.UpdateRef.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.UPDATE_REF);
-    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
-    assertThat(gotLogEntry.refName).isEqualTo(refName);
-    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
-    assertThat(gotLogEntry.newId).isEqualTo(newRefValue.getName());
-    assertThat(gotLogEntry.comment).isNotNull();
-    assertThat(gotLogEntry.committer).isNotNull();
-  }
-
-  @Test
-  public void shouldLogDeleteRef() throws Exception {
-    final String refName = "refs/remotes/origin/master";
-    Ref currRef = repo().exactRef(refName);
-
-    log4jSharedRefLogger.logRefUpdate(project.get(), currRef, ObjectId.zeroId());
-
-    SharedRefLogEntry.DeleteRef gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.DeleteRef.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.DELETE_REF);
-    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
-    assertThat(gotLogEntry.refName).isEqualTo(refName);
-    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
-  }
-
-  @Test
-  public void shouldLogBlobRefs() throws Exception {
-    Repository allUsersRepo = repoManager.openRepository(allUsers);
-    String blobRefName = RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS;
-    Ref currRef = allUsersRepo.exactRef(blobRefName);
-    log4jSharedRefLogger.logRefUpdate(allUsers.get(), currRef, currRef.getObjectId());
-
-    SharedRefLogEntry.UpdateRef gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.UpdateRef.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.UPDATE_REF);
-    assertThat(gotLogEntry.projectName).isEqualTo(allUsers.get());
-    assertThat(gotLogEntry.refName).isEqualTo(blobRefName);
-    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
-    assertThat(gotLogEntry.newId).isEqualTo(currRef.getObjectId().getName());
-    assertThat(gotLogEntry.comment).isNull();
-    assertThat(gotLogEntry.committer).isNull();
-  }
-
-  @Test
-  public void shouldLogLockAcquisition() {
-    String refName = "refs/foo/bar";
-    log4jSharedRefLogger.logLockAcquisition(project.get(), refName);
-
-    SharedRefLogEntry.LockAcquire gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.LockAcquire.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.LOCK_ACQUIRE);
-    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
-    assertThat(gotLogEntry.refName).isEqualTo(refName);
-  }
-
-  @Test
-  public void shouldLogLockRelease() {
-    String refName = "refs/foo/bar";
-    log4jSharedRefLogger.logLockRelease(project.get(), refName);
-
-    SharedRefLogEntry.LockAcquire gotLogEntry =
-        gson.fromJson(logWriter.toString(), SharedRefLogEntry.LockAcquire.class);
-
-    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.LOCK_RELEASE);
-    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
-    assertThat(gotLogEntry.refName).isEqualTo(refName);
-  }
-
-  private Log4jSharedRefLogger newLog4jSharedRefLogger() throws IOException {
-    final Log4jSharedRefLogger log4jSharedRefLogger =
-        new Log4jSharedRefLogger(new SystemLog(new SitePaths(newPath()), baseConfig), repoManager);
-    log4jSharedRefLogger.setLogger(logWriterLogger());
-    return log4jSharedRefLogger;
-  }
-
-  private Logger logWriterLogger() {
-    org.apache.log4j.Logger logger = LogManager.getLogger("logWriterLogger");
-    logger.addAppender(new WriterAppender(new PatternLayout("%m"), logWriter));
-    return LoggerFactory.getLogger("logWriterLogger");
-  }
-
-  private static Path newPath() throws IOException {
-    Path tmp = Files.createTempFile("gerrit_test_", "_site");
-    Files.deleteIfExists(tmp);
-    return tmp;
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
deleted file mode 100644
index 4aa4ba1..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static java.util.Arrays.asList;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class MultiSiteBatchRefUpdateTest implements RefFixture {
-
-  @Mock SharedRefDatabaseWrapper sharedRefDb;
-  @Mock BatchRefUpdate batchRefUpdate;
-  @Mock BatchRefUpdateValidator batchRefUpdateValidator;
-  @Mock RefDatabase refDatabase;
-  @Mock RevWalk revWalk;
-  @Mock ProgressMonitor progressMonitor;
-  @Mock ValidationMetrics validationMetrics;
-  @Mock ProjectsFilter projectsFilter;
-  @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction;
-
-  private final Ref oldRef =
-      new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_1);
-  private final Ref newRef =
-      new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_2);
-  ReceiveCommand receiveCommandBeforeExecution =
-      createReceiveCommand(
-          oldRef.getObjectId(), newRef.getObjectId(), oldRef.getName(), Result.NOT_ATTEMPTED);
-
-  ReceiveCommand successReceiveCommandAfterExecution =
-      createReceiveCommand(oldRef.getObjectId(), newRef.getObjectId(), oldRef.getName(), Result.OK);
-
-  ReceiveCommand rejectReceiveCommandAfterExecution =
-      createReceiveCommand(
-          oldRef.getObjectId(),
-          newRef.getObjectId(),
-          oldRef.getName(),
-          Result.REJECTED_NONFASTFORWARD);
-
-  private ReceiveCommand createReceiveCommand(
-      ObjectId oldRefObjectId, ObjectId newRefObjectId, String refName, Result result) {
-    ReceiveCommand receiveCommand = new ReceiveCommand(oldRefObjectId, newRefObjectId, refName);
-    receiveCommand.setResult(result);
-    return receiveCommand;
-  }
-
-  private MultiSiteBatchRefUpdate multiSiteRefUpdate;
-
-  @Rule public TestName nameRule = new TestName();
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  @Before
-  public void setup() {
-    when(projectsFilter.matches(anyString())).thenReturn(true);
-  }
-
-  @SuppressWarnings("deprecation")
-  private void setMockRequiredReturnValues() throws IOException {
-
-    doReturn(batchRefUpdate).when(refDatabase).newBatchUpdate();
-
-    when(batchRefUpdate.getCommands())
-        .thenReturn(asList(receiveCommandBeforeExecution))
-        .thenReturn(asList(successReceiveCommandAfterExecution));
-
-    doReturn(oldRef).when(refDatabase).getRef(A_TEST_REF_NAME);
-    doReturn(oldRef).when(refDatabase).exactRef(A_TEST_REF_NAME);
-
-    multiSiteRefUpdate = getMultiSiteBatchRefUpdateWithDefaultPolicyEnforcement();
-
-    verifyZeroInteractions(validationMetrics);
-  }
-
-  @Test
-  public void executeAndDelegateSuccessfullyWithNoExceptions() throws Exception {
-    setMockRequiredReturnValues();
-
-    // When compareAndPut against sharedDb succeeds
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
-    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
-    verify(sharedRefDb)
-        .compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
-  }
-
-  private Ref refEquals(Ref oldRef) {
-    return argThat(new RefMatcher(oldRef));
-  }
-
-  @Test(expected = IOException.class)
-  public void executeAndFailsWithExceptions() throws IOException {
-    multiSiteRefUpdate = getMultiSiteBatchRefUpdateWithMockedValidator();
-    doThrow(new IOException("IO Test Exception"))
-        .when(batchRefUpdateValidator)
-        .executeBatchUpdateWithValidation(any(), any(), any());
-
-    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
-  }
-
-  @Test
-  public void executeSuccessfullyWithNoExceptionsWhenOutOfSync() throws IOException {
-    setMockRequiredReturnValues();
-    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME_KEY, A_TEST_REF_NAME);
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-
-    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
-
-    verify(validationMetrics).incrementSplitBrainPrevention();
-  }
-
-  @Test
-  public void executeSuccessfullyWithNoExceptionsWhenEmptyList() throws IOException {
-    doReturn(batchRefUpdate).when(refDatabase).newBatchUpdate();
-    doReturn(Collections.emptyList()).when(batchRefUpdate).getCommands();
-
-    multiSiteRefUpdate = getMultiSiteBatchRefUpdateWithDefaultPolicyEnforcement();
-
-    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
-  }
-
-  private MultiSiteBatchRefUpdate getMultiSiteBatchRefUpdateWithDefaultPolicyEnforcement() {
-    BatchRefUpdateValidator.Factory batchRefValidatorFactory =
-        new BatchRefUpdateValidator.Factory() {
-          @Override
-          public BatchRefUpdateValidator create(String projectName, RefDatabase refDb) {
-            return new BatchRefUpdateValidator(
-                sharedRefDb,
-                validationMetrics,
-                new DefaultSharedRefEnforcement(),
-                new DummyLockWrapper(),
-                projectsFilter,
-                projectName,
-                refDb);
-          }
-        };
-    return new MultiSiteBatchRefUpdate(batchRefValidatorFactory, A_TEST_PROJECT_NAME, refDatabase);
-  }
-
-  private MultiSiteBatchRefUpdate getMultiSiteBatchRefUpdateWithMockedValidator() {
-    BatchRefUpdateValidator.Factory batchRefValidatorFactory =
-        new BatchRefUpdateValidator.Factory() {
-          @Override
-          public BatchRefUpdateValidator create(String projectName, RefDatabase refDb) {
-            return batchRefUpdateValidator;
-          }
-        };
-    return new MultiSiteBatchRefUpdate(batchRefValidatorFactory, A_TEST_PROJECT_NAME, refDatabase);
-  }
-
-  protected static class RefMatcher implements ArgumentMatcher<Ref> {
-    private Ref left;
-
-    public RefMatcher(Ref ref) {
-      this.left = ref;
-    }
-
-    @Override
-    public boolean matches(Ref right) {
-      return left.getName().equals(right.getName())
-          && left.getObjectId().equals(right.getObjectId());
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java
deleted file mode 100644
index 491ced4..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class MultiSiteGitRepositoryManagerTest implements RefFixture {
-
-  @Mock LocalDiskRepositoryManager localDiskRepositoryManagerMock;
-
-  @Mock MultiSiteRepository.Factory multiSiteRepositoryFactoryMock;
-
-  @Mock Repository repositoryMock;
-
-  @Mock MultiSiteRepository multiSiteRepositoryMock;
-
-  MultiSiteGitRepositoryManager msRepoMgr;
-
-  @Override
-  public String testBranch() {
-    return "foo";
-  }
-
-  @Before
-  public void setUp() throws Exception {
-    doReturn(multiSiteRepositoryMock)
-        .when(multiSiteRepositoryFactoryMock)
-        .create(A_TEST_PROJECT_NAME, repositoryMock);
-    msRepoMgr =
-        new MultiSiteGitRepositoryManager(
-            multiSiteRepositoryFactoryMock, localDiskRepositoryManagerMock);
-  }
-
-  @Test
-  public void openRepositoryShouldCreateMultiSiteRepositoryWrapper() throws Exception {
-    doReturn(repositoryMock)
-        .when(localDiskRepositoryManagerMock)
-        .openRepository(A_TEST_PROJECT_NAME_KEY);
-
-    msRepoMgr.openRepository(A_TEST_PROJECT_NAME_KEY);
-
-    verifyThatMultiSiteRepositoryWrapperHasBeenCreated();
-  }
-
-  @Test
-  public void createRepositoryShouldCreateMultiSiteRepositoryWrapper() throws Exception {
-    doReturn(repositoryMock)
-        .when(localDiskRepositoryManagerMock)
-        .createRepository(A_TEST_PROJECT_NAME_KEY);
-
-    msRepoMgr.createRepository(A_TEST_PROJECT_NAME_KEY);
-
-    verifyThatMultiSiteRepositoryWrapperHasBeenCreated();
-  }
-
-  private void verifyThatMultiSiteRepositoryWrapperHasBeenCreated() {
-    verify(multiSiteRepositoryFactoryMock).create(A_TEST_PROJECT_NAME, repositoryMock);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java
deleted file mode 100644
index 41b83e5..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class MultiSiteRefDatabaseTest implements RefFixture {
-
-  @Rule public TestName nameRule = new TestName();
-
-  @Mock MultiSiteRefUpdate.Factory refUpdateFactoryMock;
-  @Mock MultiSiteBatchRefUpdate.Factory refBatchUpdateFactoryMock;
-
-  @Mock RefDatabase refDatabaseMock;
-
-  @Mock RefUpdate refUpdateMock;
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  @Test
-  public void newUpdateShouldCreateMultiSiteRefUpdate() throws Exception {
-    String refName = aBranchRef();
-    MultiSiteRefDatabase multiSiteRefDb =
-        new MultiSiteRefDatabase(
-            refUpdateFactoryMock, refBatchUpdateFactoryMock, A_TEST_PROJECT_NAME, refDatabaseMock);
-    doReturn(refUpdateMock).when(refDatabaseMock).newUpdate(refName, false);
-
-    multiSiteRefDb.newUpdate(refName, false);
-
-    verify(refUpdateFactoryMock).create(A_TEST_PROJECT_NAME, refUpdateMock, refDatabaseMock);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
deleted file mode 100644
index 029a4c6..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.Factory;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefUpdateStub;
-import java.io.IOException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@Ignore // The focus of this test suite is unclear and all tests are failing when the code is
-// working, and the other way around
-public class MultiSiteRefUpdateTest implements RefFixture {
-
-  @Mock SharedRefDatabaseWrapper sharedRefDb;
-  @Mock ValidationMetrics validationMetrics;
-  @Mock RefDatabase refDb;
-  @Mock ProjectsFilter projectsFilter;
-
-  private final Ref oldRef =
-      new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_1);
-  private final Ref newRef =
-      new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_2);
-
-  @Rule public TestName nameRule = new TestName();
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  @Before
-  public void setup() {
-    when(projectsFilter.matches(anyString())).thenReturn(true);
-  }
-
-  @Test
-  public void newUpdateShouldValidateAndSucceed() throws Exception {
-
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThat(multiSiteRefUpdate.update()).isEqualTo(Result.FAST_FORWARD);
-    verifyZeroInteractions(validationMetrics);
-  }
-
-  @Test(expected = Exception.class)
-  public void newUpdateShouldValidateAndFailWithIOException() throws Exception {
-
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-    multiSiteRefUpdate.update();
-  }
-
-  @Test
-  public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() {
-
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThrows(IOException.class, () -> multiSiteRefUpdate.update());
-    verify(validationMetrics).incrementSplitBrainPrevention();
-  }
-
-  @Test
-  public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate() {
-
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-    doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThrows(IOException.class, () -> multiSiteRefUpdate.update());
-    verify(validationMetrics, never()).incrementSplitBrainPrevention();
-  }
-
-  @Test
-  public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate() {
-
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-    doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThrows(IOException.class, () -> multiSiteRefUpdate.update());
-    verify(validationMetrics).incrementSplitBrain();
-  }
-
-  @Test
-  public void deleteShouldValidateAndSucceed() throws IOException {
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, ObjectId.zeroId());
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulDelete(oldRef);
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThat(multiSiteRefUpdate.delete()).isEqualTo(Result.FORCED);
-    verifyZeroInteractions(validationMetrics);
-  }
-
-  @Test
-  public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() {
-
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
-
-    RefUpdate refUpdate = RefUpdateStub.forSuccessfulDelete(oldRef);
-
-    MultiSiteRefUpdate multiSiteRefUpdate =
-        getMultiSiteRefUpdateWithDefaultPolicyEnforcement(refUpdate);
-
-    assertThrows(IOException.class, () -> multiSiteRefUpdate.delete());
-    verify(validationMetrics).incrementSplitBrainPrevention();
-  }
-
-  private MultiSiteRefUpdate getMultiSiteRefUpdateWithDefaultPolicyEnforcement(
-      RefUpdate refUpdate) {
-    Factory batchRefValidatorFactory =
-        new Factory() {
-          @Override
-          public RefUpdateValidator create(String projectName, RefDatabase refDb) {
-            RefUpdateValidator RefUpdateValidator =
-                new RefUpdateValidator(
-                    sharedRefDb,
-                    validationMetrics,
-                    new DefaultSharedRefEnforcement(),
-                    new DummyLockWrapper(),
-                    projectsFilter,
-                    projectName,
-                    refDb);
-            return RefUpdateValidator;
-          }
-        };
-    return new MultiSiteRefUpdate(batchRefValidatorFactory, A_TEST_PROJECT_NAME, refUpdate, refDb);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java
deleted file mode 100644
index 15c596f..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import java.io.IOException;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class MultiSiteRepositoryTest implements RefFixture {
-
-  @Mock MultiSiteRefDatabase.Factory multiSiteRefDbFactory;
-  @Mock MultiSiteRefDatabase multiSiteRefDb;
-  @Mock RefDatabase genericRefDb;
-
-  @Mock MultiSiteRefUpdate multiSiteRefUpdate;
-
-  @Mock Repository repository;
-
-  private final String PROJECT_NAME = "ProjectName";
-  private final String REFS_HEADS_MASTER = "refs/heads/master";
-
-  @Override
-  public String testBranch() {
-    return null;
-  }
-
-  private void setMockitoCommon() {
-    doReturn(genericRefDb).when(repository).getRefDatabase();
-    doReturn(multiSiteRefDb).when(multiSiteRefDbFactory).create(PROJECT_NAME, genericRefDb);
-  }
-
-  @Test
-  public void shouldInvokeMultiSiteRefDbFactoryCreate() {
-    setMockitoCommon();
-    try (MultiSiteRepository multiSiteRepository =
-        new MultiSiteRepository(multiSiteRefDbFactory, PROJECT_NAME, repository)) {
-
-      multiSiteRepository.getRefDatabase();
-      verify(multiSiteRefDbFactory).create(PROJECT_NAME, genericRefDb);
-    }
-  }
-
-  @Test
-  public void shouldInvokeNewUpdateInMultiSiteRefDatabase() throws IOException {
-    setMockitoCommon();
-    try (MultiSiteRepository multiSiteRepository =
-        new MultiSiteRepository(multiSiteRefDbFactory, PROJECT_NAME, repository)) {
-      multiSiteRepository.getRefDatabase().newUpdate(REFS_HEADS_MASTER, false);
-
-      verify(multiSiteRefDb).newUpdate(REFS_HEADS_MASTER, false);
-    }
-  }
-
-  @Test
-  public void shouldInvokeUpdateInMultiSiteRefUpdate() throws IOException {
-    setMockitoCommon();
-    doReturn(Result.NEW).when(multiSiteRefUpdate).update();
-    doReturn(multiSiteRefUpdate).when(multiSiteRefDb).newUpdate(REFS_HEADS_MASTER, false);
-
-    try (MultiSiteRepository multiSiteRepository =
-        new MultiSiteRepository(multiSiteRefDbFactory, PROJECT_NAME, repository)) {
-
-      Result updateResult =
-          multiSiteRepository.getRefDatabase().newUpdate(REFS_HEADS_MASTER, false).update();
-
-      verify(multiSiteRefUpdate).update();
-      assertThat(updateResult).isEqualTo(Result.NEW);
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java
deleted file mode 100644
index ba381a4..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite.validation;
-
-import com.google.gerrit.extensions.api.changes.NotifyHandling;
-import com.google.gerrit.extensions.events.ProjectDeletedListener;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ProjectDeletedSharedDbCleanupTest implements RefFixture {
-  @Rule public TestName nameRule = new TestName();
-
-  @Mock ValidationMetrics mockValidationMetrics;
-  @Mock SharedRefDatabaseWrapper sharedRefDatabase;
-
-  @Test
-  public void aDeleteProjectEventShouldCleanupProjectFromZk() throws Exception {
-    String projectName = A_TEST_PROJECT_NAME;
-    ProjectDeletedSharedDbCleanup projectDeletedSharedDbCleanup =
-        new ProjectDeletedSharedDbCleanup(sharedRefDatabase, mockValidationMetrics);
-
-    ProjectDeletedListener.Event event =
-        new ProjectDeletedListener.Event() {
-          @Override
-          public String getProjectName() {
-            return projectName;
-          }
-
-          @Override
-          public NotifyHandling getNotify() {
-            return NotifyHandling.NONE;
-          }
-        };
-
-    projectDeletedSharedDbCleanup.onProjectDeleted(event);
-
-    Mockito.verify(sharedRefDatabase, Mockito.times(1)).remove(A_TEST_PROJECT_NAME_KEY);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
index 4706131..88d2270 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -24,6 +24,8 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.events.Event;
@@ -34,14 +36,13 @@
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.util.Optional;
-import org.apache.commons.io.IOUtils;
+import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
@@ -121,8 +122,7 @@
     assertThat(ref).isNotNull();
 
     ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
-    long storedVersion =
-        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    long storedVersion = readLongObject(loader);
     assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
 
     verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
@@ -167,8 +167,7 @@
     assertThat(ref).isNotNull();
 
     ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
-    long storedVersion =
-        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    long storedVersion = readLongObject(loader);
     assertThat(storedVersion).isGreaterThan((long) masterPlusOneCommit.getCommitTime());
 
     verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
@@ -208,8 +207,7 @@
     assertThat(ref).isNotNull();
 
     ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
-    long storedVersion =
-        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    long storedVersion = readLongObject(loader);
     assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
 
     verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
@@ -226,6 +224,12 @@
     producerShouldNotUpdateProjectVersionUponMagicRefUpdatedEvent(RefNames.REFS_STARRED_CHANGES);
   }
 
+  private long readLongObject(ObjectLoader loader)
+      throws LargeObjectException, UnsupportedEncodingException {
+    String boutString = new String(loader.getBytes(), StandardCharsets.UTF_8.name());
+    return Long.parseLong(boutString);
+  }
+
   private void producerShouldNotUpdateProjectVersionUponMagicRefUpdatedEvent(String magicRefPrefix)
       throws Exception {
     String magicRefName = magicRefPrefix + "/foo";
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
deleted file mode 100644
index b9b07bd..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.entities.Project;
-import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterFunction;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class RefUpdateValidatorTest implements RefFixture {
-  private static final DefaultSharedRefEnforcement defaultRefEnforcement =
-      new DefaultSharedRefEnforcement();
-
-  @Mock SharedRefDatabaseWrapper sharedRefDb;
-
-  @Mock SharedRefLogger sharedRefLogger;
-
-  @Mock RefDatabase localRefDb;
-
-  @Mock ValidationMetrics validationMetrics;
-
-  @Mock RefUpdate refUpdate;
-
-  @Mock ProjectsFilter projectsFilter;
-
-  @Mock OneParameterFunction<ObjectId, Result> rollbackFunction;
-
-  String refName;
-  Ref oldUpdateRef;
-  Ref newUpdateRef;
-  Ref localRef;
-
-  RefUpdateValidator refUpdateValidator;
-
-  @Before
-  public void setupMocks() throws Exception {
-    refName = aBranchRef();
-    oldUpdateRef = newRef(refName, AN_OBJECT_ID_1);
-    newUpdateRef = newRef(refName, AN_OBJECT_ID_2);
-    localRef = newRef(refName, AN_OBJECT_ID_3);
-
-    doReturn(localRef).when(localRefDb).findRef(refName);
-    doReturn(localRef).when(localRefDb).exactRef(refName);
-    doReturn(newUpdateRef.getObjectId()).when(refUpdate).getNewObjectId();
-    doReturn(refName).when(refUpdate).getName();
-    lenient().doReturn(oldUpdateRef.getObjectId()).when(refUpdate).getOldObjectId();
-    doReturn(Result.FAST_FORWARD).when(rollbackFunction).invoke(any());
-
-    doReturn(true).when(projectsFilter).matches(anyString());
-
-    refUpdateValidator = newRefUpdateValidator(sharedRefDb);
-  }
-
-  @Test
-  public void validationShouldSucceedWhenSharedRefDbIsNoop() throws Exception {
-    SharedRefDatabaseWrapper noopSharedRefDbWrapper = new SharedRefDatabaseWrapper(sharedRefLogger);
-
-    Result result =
-        newRefUpdateValidator(noopSharedRefDbWrapper)
-            .executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
-    assertThat(result).isEqualTo(RefUpdate.Result.NEW);
-  }
-
-  @Test
-  public void validationShouldSucceedWhenLocalRefDbIsUpToDate() throws Exception {
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, newUpdateRef.getObjectId());
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(
-            refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
-
-    assertThat(result).isEqualTo(RefUpdate.Result.NEW);
-  }
-
-  @Test
-  public void sharedRefDbShouldBeUpdatedWithRefDeleted() throws Exception {
-    doReturn(ObjectId.zeroId()).when(refUpdate).getNewObjectId();
-    doReturn(true).when(sharedRefDb).isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, ObjectId.zeroId());
-    doReturn(localRef).doReturn(null).when(localRefDb).findRef(refName);
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(
-            refUpdate, () -> RefUpdate.Result.FORCED, this::defaultRollback);
-
-    assertThat(result).isEqualTo(RefUpdate.Result.FORCED);
-  }
-
-  @Test
-  public void sharedRefDbShouldBeUpdatedWithNewRefCreated() throws Exception {
-    Ref localNullRef = nullRef(refName);
-
-    doReturn(true).when(sharedRefDb).isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-    doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localNullRef, newUpdateRef.getObjectId());
-    doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).findRef(refName);
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(
-            refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
-
-    assertThat(result).isEqualTo(RefUpdate.Result.NEW);
-  }
-
-  @Test
-  public void validationShouldFailWhenLocalRefDbIsOutOfSync() throws Exception {
-    lenient()
-        .doReturn(true)
-        .when(sharedRefDb)
-        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME_KEY, refName);
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(
-            refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
-
-    assertThat(result).isEqualTo(Result.LOCK_FAILURE);
-  }
-
-  @Test
-  public void shouldRollbackWhenLocalRefDbIsUpToDateButFinalCompareAndPutIsFailing()
-      throws Exception {
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
-    lenient()
-        .doReturn(true)
-        .when(sharedRefDb)
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-    doReturn(false)
-        .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, newUpdateRef.getObjectId());
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(refUpdate, () -> Result.NEW, rollbackFunction);
-
-    verify(rollbackFunction, times(1)).invoke(any());
-    assertThat(result).isEqualTo(Result.LOCK_FAILURE);
-  }
-
-  @Test
-  public void shouldNotUpdateSharedRefDbWhenFinalCompareAndPutIsFailing() throws Exception {
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
-
-    Result result =
-        refUpdateValidator.executeRefUpdate(
-            refUpdate, () -> Result.LOCK_FAILURE, this::defaultRollback);
-
-    verify(sharedRefDb, never())
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-    assertThat(result).isEqualTo(RefUpdate.Result.LOCK_FAILURE);
-  }
-
-  @Test
-  public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception {
-    when(projectsFilter.matches(anyString())).thenReturn(false);
-
-    refUpdateValidator.executeRefUpdate(
-        refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
-
-    verify(sharedRefDb, never())
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-  }
-
-  private RefUpdateValidator newRefUpdateValidator(SharedRefDatabaseWrapper refDbWrapper) {
-    return new RefUpdateValidator(
-        refDbWrapper,
-        validationMetrics,
-        defaultRefEnforcement,
-        new DummyLockWrapper(),
-        projectsFilter,
-        A_TEST_PROJECT_NAME,
-        localRefDb);
-  }
-
-  private Result defaultRollback(ObjectId objectId) {
-    return Result.NO_CHANGE;
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
deleted file mode 100644
index f3008a2..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.Configuration.SharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import java.util.Arrays;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Before;
-import org.junit.Test;
-
-public class CustomSharedRefEnforcementByProjectTest implements RefFixture {
-
-  SharedRefEnforcement refEnforcement;
-
-  @Before
-  public void setUp() {
-    Config sharedRefDbConfig = new Config();
-    sharedRefDbConfig.setStringList(
-        SharedRefDatabase.SECTION,
-        SharedRefDatabase.SUBSECTION_ENFORCEMENT_RULES,
-        EnforcePolicy.IGNORED.name(),
-        Arrays.asList(
-            "ProjectOne",
-            "ProjectTwo:refs/heads/master/test",
-            "ProjectTwo:refs/heads/master/test2"));
-
-    refEnforcement = newCustomRefEnforcement(sharedRefDbConfig);
-  }
-
-  @Test
-  public void projectOneShouldReturnDesiredForAllRefs() {
-    Ref aRef = newRef("refs/heads/master/2", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void projectOneEnforcementShouldAlwaysPrevail() {
-    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aNonListedProjectShouldRequireRefForMasterTest() {
-    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", aRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void projectTwoSpecificRefShouldReturnIgnoredPolicy() {
-    Ref refOne = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    Ref refTwo = newRef("refs/heads/master/test2", AN_OBJECT_ID_1);
-
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refTwo.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aNonListedProjectShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/newChange", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aNonListedRefInProjectShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aNonListedProjectAndRefShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForProjectOneShouldReturnIgnored() {
-    assertThat(refEnforcement.getPolicy("ProjectOne")).isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void getProjectPolicyForProjectTwoShouldReturnRequired() {
-    assertThat(refEnforcement.getPolicy("ProjectTwo")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForNonListedProjectShouldReturnRequired() {
-    assertThat(refEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForNonListedProjectWhenSingleProject() {
-    SharedRefEnforcement customEnforcement =
-        newCustomRefEnforcementWithValue(EnforcePolicy.IGNORED, ":refs/heads/master");
-
-    assertThat(customEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getANonListedProjectWhenOnlyOneProjectIsListedShouldReturnRequired() {
-    SharedRefEnforcement customEnforcement =
-        newCustomRefEnforcementWithValue(EnforcePolicy.IGNORED, "AProject:");
-    assertThat(customEnforcement.getPolicy("NonListedProject", "refs/heads/master"))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  private SharedRefEnforcement newCustomRefEnforcementWithValue(
-      EnforcePolicy policy, String... projectAndRefs) {
-    Config sharedRefDbConfiguration = new Config();
-    sharedRefDbConfiguration.setStringList(
-        SharedRefDatabase.SECTION,
-        SharedRefDatabase.SUBSECTION_ENFORCEMENT_RULES,
-        policy.name(),
-        Arrays.asList(projectAndRefs));
-    return newCustomRefEnforcement(sharedRefDbConfiguration);
-  }
-
-  private SharedRefEnforcement newCustomRefEnforcement(Config sharedRefDbConfig) {
-    return new CustomSharedRefEnforcementByProject(
-        new Configuration(sharedRefDbConfig, new Config()));
-  }
-
-  @Override
-  public String testBranch() {
-    return "fooBranch";
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
deleted file mode 100644
index 2964e15..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Test;
-
-public class DefaultSharedRefEnforcementTest implements RefFixture {
-
-  SharedRefEnforcement refEnforcement = new DefaultSharedRefEnforcement();
-
-  @Test
-  public void anImmutableChangeShouldBeIgnored() {
-    Ref immutableChangeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aChangeMetaShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/changes/01/1/meta", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aCacheAutomergeShouldBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/cache-automerge/01/1/1000000", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aDraftCommentsShouldBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void regularRefHeadsMasterShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/heads/master", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void regularCommitShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/heads/stable-2.16", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void allUsersExternalIdsRefShouldBeRequired() {
-    Ref refOne = newRef("refs/meta/external-ids", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("All-Users", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Override
-  public String testBranch() {
-    return "fooBranch";
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
deleted file mode 100644
index f2b57a1..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.MultisiteReplicationPushFilter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class MultisiteReplicationPushFilterTest extends LocalDiskRepositoryTestCase
-    implements RefFixture {
-
-  @Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment();
-
-  @Mock SharedRefDatabaseWrapper sharedRefDatabaseMock;
-
-  @Inject private InMemoryRepositoryManager gitRepositoryManager;
-
-  String project = A_TEST_PROJECT_NAME;
-  Project.NameKey projectName = A_TEST_PROJECT_NAME_KEY;
-
-  private TestRepository<InMemoryRepository> repo;
-
-  @Before
-  public void setupTestRepo() throws Exception {
-    InMemoryRepository inMemoryRepo =
-        gitRepositoryManager.createRepository(A_TEST_PROJECT_NAME_KEY);
-    repo = new TestRepository<>(inMemoryRepo);
-  }
-
-  @Test
-  public void shouldReturnAllRefUpdatesWhenAllUpToDate() throws Exception {
-    List<RemoteRefUpdate> refUpdates =
-        Arrays.asList(refUpdate("refs/heads/foo"), refUpdate("refs/heads/bar"));
-    doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
-
-    MultisiteReplicationPushFilter pushFilter =
-        new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
-    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
-
-    assertThat(filteredRefUpdates).containsExactlyElementsIn(refUpdates);
-  }
-
-  @Test
-  public void shouldFilterOutOneOutdatedRef() throws Exception {
-    RemoteRefUpdate refUpToDate = refUpdate("refs/heads/uptodate");
-    RemoteRefUpdate outdatedRef = refUpdate("refs/heads/outdated");
-    List<RemoteRefUpdate> refUpdates = Arrays.asList(refUpToDate, outdatedRef);
-    SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(outdatedRef.getSrcRef());
-
-    MultisiteReplicationPushFilter pushFilter =
-        new MultisiteReplicationPushFilter(sharedRefDatabase, gitRepositoryManager);
-    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
-
-    assertThat(filteredRefUpdates).containsExactly(refUpToDate);
-  }
-
-  @Test
-  public void shouldLoadLocalVersionAndNotFilter() throws Exception {
-    RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated");
-    List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated);
-    doReturn(false).doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
-
-    MultisiteReplicationPushFilter pushFilter =
-        new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
-    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
-
-    assertThat(filteredRefUpdates).containsExactly(temporaryOutdated);
-    verify(sharedRefDatabaseMock, times(2)).isUpToDate(any(), any());
-  }
-
-  @Test
-  public void shouldLoadLocalVersionAndFilter() throws Exception {
-    RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated");
-    repo.branch("refs/heads/temporaryOutdated").commit().create();
-    List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated);
-    doReturn(false).doReturn(false).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
-
-    MultisiteReplicationPushFilter pushFilter =
-        new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
-    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
-
-    assertThat(filteredRefUpdates).isEmpty();
-    verify(sharedRefDatabaseMock, times(2)).isUpToDate(any(), any());
-  }
-
-  @Test
-  public void shouldFilterOutAllOutdatedChangesRef() throws Exception {
-    RemoteRefUpdate refUpToDate = refUpdate("refs/heads/uptodate");
-    RemoteRefUpdate refChangeUpToDate = refUpdate("refs/changes/25/1225/2");
-    RemoteRefUpdate changeMetaRef = refUpdate("refs/changes/12/4512/meta");
-    RemoteRefUpdate changeRef = refUpdate("refs/changes/12/4512/1");
-    List<RemoteRefUpdate> refUpdates =
-        Arrays.asList(refUpToDate, refChangeUpToDate, changeMetaRef, changeRef);
-    SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(changeMetaRef.getSrcRef());
-
-    MultisiteReplicationPushFilter pushFilter =
-        new MultisiteReplicationPushFilter(sharedRefDatabase, gitRepositoryManager);
-    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
-
-    assertThat(filteredRefUpdates).containsExactly(refUpToDate, refChangeUpToDate);
-  }
-
-  private SharedRefDatabaseWrapper newSharedRefDatabase(String... rejectedRefs) {
-    Set<String> rejectedSet = new HashSet<>();
-    rejectedSet.addAll(Arrays.asList(rejectedRefs));
-
-    GlobalRefDatabase sharedRefDatabase =
-        new GlobalRefDatabase() {
-
-          @Override
-          public boolean isUpToDate(Project.NameKey project, Ref ref)
-              throws GlobalRefDbLockException {
-            return !rejectedSet.contains(ref.getName());
-          }
-
-          @Override
-          public boolean exists(Project.NameKey project, String refName) {
-            return true;
-          }
-
-          @Override
-          public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
-              throws GlobalRefDbSystemError {
-            return false;
-          }
-
-          @Override
-          public <T> boolean compareAndPut(
-              Project.NameKey project, String refName, T currValue, T newValue)
-              throws GlobalRefDbSystemError {
-            return false;
-          }
-
-          @Override
-          public AutoCloseable lockRef(Project.NameKey project, String refName)
-              throws GlobalRefDbLockException {
-            return null;
-          }
-
-          @Override
-          public void remove(Project.NameKey project) throws GlobalRefDbSystemError {}
-
-          @Override
-          public <T> Optional<T> get(Project.NameKey project, String refName, Class<T> clazz)
-              throws GlobalRefDbSystemError {
-            return Optional.empty();
-          }
-        };
-    return new SharedRefDatabaseWrapper(
-        DynamicItem.itemOf(GlobalRefDatabase.class, sharedRefDatabase),
-        new DisabledSharedRefLogger());
-  }
-
-  private RemoteRefUpdate refUpdate(String refName) throws Exception {
-    ObjectId srcObjId = ObjectId.fromString("0000000000000000000000000000000000000001");
-    Ref srcRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, refName, srcObjId);
-    repo.branch(refName).commit().create();
-    return new RemoteRefUpdate(null, srcRef, "origin", false, "origin", srcObjId);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java
deleted file mode 100644
index c63530a..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Test;
-
-public class NoopSharedRefDatabaseTest implements RefFixture {
-
-  private Ref sampleRef = newRef(A_TEST_REF_NAME, AN_OBJECT_ID_1);
-  private NoopSharedRefDatabase objectUnderTest = new NoopSharedRefDatabase();
-
-  @Test
-  public void isUpToDateShouldAlwaysReturnTrue() {
-    assertThat(objectUnderTest.isUpToDate(A_TEST_PROJECT_NAME_KEY, sampleRef)).isTrue();
-  }
-
-  @Test
-  public void compareAndPutShouldAlwaysReturnTrue() {
-    assertThat(objectUnderTest.compareAndPut(A_TEST_PROJECT_NAME_KEY, sampleRef, AN_OBJECT_ID_2))
-        .isTrue();
-  }
-
-  @Test
-  public void existsShouldAlwaysReturnFalse() {
-    assertThat(objectUnderTest.exists(A_TEST_PROJECT_NAME_KEY, A_TEST_REF_NAME)).isFalse();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java
deleted file mode 100644
index c9eeaa8..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Ref.Storage;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-public class RefSharedDatabaseTest implements RefFixture {
-  @Rule public TestName nameRule = new TestName();
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  @Test
-  public void shouldCreateANewRef() {
-
-    ObjectId objectId = AN_OBJECT_ID_1;
-    String refName = aBranchRef();
-
-    Ref aNewRef = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId);
-
-    assertThat(aNewRef.getName()).isEqualTo(refName);
-    assertThat(aNewRef.getObjectId()).isEqualTo(objectId);
-    assertThat(aNewRef.getStorage()).isEqualTo(Storage.NETWORK);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java
deleted file mode 100644
index 7c1d7b4..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2019 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.multisite.validation.dfsrefdb;
-
-import java.io.IOException;
-import org.apache.commons.lang.NotImplementedException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.Ignore;
-
-@Ignore
-public class RefUpdateStub extends RefUpdate {
-
-  public static RefUpdate forSuccessfulCreate(Ref newRef) {
-    return new RefUpdateStub(Result.NEW, null, newRef, newRef.getObjectId());
-  }
-
-  public static RefUpdate forSuccessfulUpdate(Ref oldRef, ObjectId newObjectId) {
-    return new RefUpdateStub(Result.FAST_FORWARD, null, oldRef, newObjectId);
-  }
-
-  public static RefUpdate forSuccessfulDelete(Ref oldRef) {
-    return new RefUpdateStub(null, Result.FORCED, oldRef, ObjectId.zeroId());
-  }
-
-  private final Result updateResult;
-  private final Result deleteResult;
-
-  public RefUpdateStub(Result updateResult, Result deleteResult, Ref oldRef, ObjectId newObjectId) {
-    super(oldRef);
-    this.setNewObjectId(newObjectId);
-    this.updateResult = updateResult;
-    this.deleteResult = deleteResult;
-  }
-
-  @Override
-  protected RefDatabase getRefDatabase() {
-    throw new NotImplementedException("Method not implemented yet, not assumed you needed it!!");
-  }
-
-  @Override
-  protected Repository getRepository() {
-    throw new NotImplementedException("Method not implemented yet, not assumed you needed it!!");
-  }
-
-  @Override
-  protected boolean tryLock(boolean deref) throws IOException {
-    throw new NotImplementedException("Method not implemented yet, not assumed you needed it!!");
-  }
-
-  @Override
-  protected void unlock() {
-    throw new NotImplementedException("Method not implemented yet, not assumed you needed it!!");
-  }
-
-  @Override
-  protected Result doUpdate(Result desiredResult) throws IOException {
-    throw new NotImplementedException("Method not implemented, shouldn't be called!!");
-  }
-
-  @Override
-  protected Result doDelete(Result desiredResult) throws IOException {
-    throw new NotImplementedException("Method not implemented, shouldn't be called!!");
-  }
-
-  @Override
-  protected Result doLink(String target) throws IOException {
-    throw new NotImplementedException("Method not implemented yet, not assumed you needed it!!");
-  }
-
-  @Override
-  public Result update() throws IOException {
-    if (updateResult != null) return updateResult;
-
-    throw new NotImplementedException("Not assumed you needed to stub this call!!");
-  }
-
-  @Override
-  public Result update(RevWalk walk) throws IOException {
-    if (updateResult != null) return updateResult;
-
-    throw new NotImplementedException("Not assumed you needed to stub this call!!");
-  }
-
-  @Override
-  public Result delete() throws IOException {
-    if (deleteResult != null) return deleteResult;
-
-    throw new NotImplementedException("Not assumed you needed to stub this call!!");
-  }
-
-  @Override
-  public Result delete(RevWalk walk) throws IOException {
-    if (deleteResult != null) return deleteResult;
-
-    throw new NotImplementedException("Not assumed you needed to stub this call!!");
-  }
-}