Merge branch 'stable-3.1'

* stable-3.1:
  Fix mocks in ProjectVersionRefUpdateTest
  Re-create global-refdb project versions when missing
  Add unit-test for SubscriberMetrics
  Silence exception when repository is not found
  Generate project version logging for analyzing replication lag
  Rely on replication for version ref propagation

Change-Id: I5f62e452339da023dba5794490c9555792440b86
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java
new file mode 100644
index 0000000..c2c4b46
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java
@@ -0,0 +1,48 @@
+// 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.gerrit.entities.Project;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.log4j.PatternLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class Log4jProjectVersionLogger extends LibModuleLogFile implements ProjectVersionLogger {
+  private static final String LOG_NAME = "project_version_log";
+  private final Logger verLog;
+
+  @Inject
+  public Log4jProjectVersionLogger(SystemLog systemLog) {
+    super(systemLog, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
+    this.verLog = LoggerFactory.getLogger(LOG_NAME);
+  }
+
+  @Override
+  public void log(Project.NameKey projectName, long currentVersion, long replicationLag) {
+    if (replicationLag > 0) {
+      verLog.warn(
+          "{ \"project\":\"{}\", \"version\":{}, \"lag\":{} }",
+          projectName,
+          currentVersion,
+          replicationLag);
+    } else {
+      verLog.info("{ \"project\":\"{}\", \"version\":{} }", projectName, currentVersion);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java
new file mode 100644
index 0000000..6ababb6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java
@@ -0,0 +1,22 @@
+// 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.gerrit.entities.Project;
+
+public interface ProjectVersionLogger {
+
+  public void log(Project.NameKey projectName, long currentVersion, long replicationLag);
+}
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 b4e5c94..4459859 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
@@ -25,6 +25,7 @@
 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;
@@ -46,14 +47,18 @@
 
   private final Counter1<String> subscriberSuccessCounter;
   private final Counter1<String> subscriberFailureCounter;
+  private final ProjectVersionLogger verLogger;
 
-  public Map<String, Long> replicationStatusPerProject = new HashMap<>();
+  private final Map<String, Long> replicationStatusPerProject = new HashMap<>();
+  private final Map<String, Long> localVersionPerProject = new HashMap<>();
 
   private ProjectVersionRefUpdate projectVersionRefUpdate;
 
   @Inject
   public SubscriberMetrics(
-      MetricMaker metricMaker, ProjectVersionRefUpdate projectVersionRefUpdate) {
+      MetricMaker metricMaker,
+      ProjectVersionRefUpdate projectVersionRefUpdate,
+      ProjectVersionLogger verLogger) {
 
     this.projectVersionRefUpdate = projectVersionRefUpdate;
     this.subscriberSuccessCounter =
@@ -81,6 +86,8 @@
           }
           return Collections.max(lags);
         });
+
+    this.verLogger = verLogger;
   }
 
   public void incrementSubscriberConsumedMessage() {
@@ -115,10 +122,16 @@
     Optional<Long> localVersion = projectVersionRefUpdate.getProjectLocalVersion(projectName.get());
     if (remoteVersion.isPresent() && localVersion.isPresent()) {
       long lag = remoteVersion.get() - localVersion.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);
+
+      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",
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 45d0806..bd910ad 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
@@ -27,16 +27,18 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.IntBlob;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import com.googlesource.gerrit.plugins.replication.RefReplicationDoneEvent;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.Set;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -60,14 +62,21 @@
       new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
 
   private final GitRepositoryManager gitRepositoryManager;
+  private final GitReferenceUpdated gitReferenceUpdated;
+  private final ProjectVersionLogger verLogger;
 
   protected final SharedRefDatabaseWrapper sharedRefDb;
 
   @Inject
   public ProjectVersionRefUpdate(
-      GitRepositoryManager gitRepositoryManager, SharedRefDatabaseWrapper sharedRefDb) {
+      GitRepositoryManager gitRepositoryManager,
+      SharedRefDatabaseWrapper sharedRefDb,
+      GitReferenceUpdated gitReferenceUpdated,
+      ProjectVersionLogger verLogger) {
     this.gitRepositoryManager = gitRepositoryManager;
     this.sharedRefDb = sharedRefDb;
+    this.gitReferenceUpdated = gitReferenceUpdated;
+    this.verLogger = verLogger;
   }
 
   @Override
@@ -77,34 +86,12 @@
     if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
       updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
     }
-
-    // Consumers of the Event use RefReplicationDoneEvent to trigger the version update
-    if (Context.isForwardedEvent() && event instanceof RefReplicationDoneEvent) {
-      updateConsumerProjectVersion((RefReplicationDoneEvent) event);
-    }
-  }
-
-  private void updateConsumerProjectVersion(RefReplicationDoneEvent refReplicationDoneEvent) {
-    Project.NameKey projectNameKey = refReplicationDoneEvent.getProjectNameKey();
-    String refName = refReplicationDoneEvent.getRefName();
-
-    if (isSpecialRefName(refName)) {
-      logger.atFine().log(
-          "Found a special ref name %s, skipping update for %s", refName, projectNameKey.get());
-      return;
-    }
-    try {
-      updateLocalProjectVersion(
-          projectNameKey, getLastRefUpdatedTimestamp(projectNameKey, refName));
-    } catch (LocalProjectVersionUpdateException e) {
-      logger.atSevere().withCause(e).log(
-          "Issue encountered when updating version for project " + projectNameKey);
-    }
   }
 
   private boolean isSpecialRefName(String refName) {
     return refName.startsWith(RefNames.REFS_SEQUENCES)
-        || refName.startsWith(RefNames.REFS_STARRED_CHANGES);
+        || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
+        || refName.equals(MULTI_SITE_VERSIONING_REF);
   }
 
   private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
@@ -127,6 +114,8 @@
           updateLocalProjectVersion(projectNameKey, lastRefUpdatedTimestamp);
 
       if (newProjectVersionObjectId.isPresent()) {
+        verLogger.log(projectNameKey, lastRefUpdatedTimestamp.get(), 0L);
+
         updateSharedProjectVersion(
             projectNameKey,
             currentProjectVersionRef,
@@ -211,15 +200,24 @@
             "Updating shared project version for %s. Current value %s, new value: %s",
             projectNameKey.get(), currentRef.getObjectId(), newObjectId));
     try {
+      if (!sharedRefDb.exists(projectNameKey, MULTI_SITE_VERSIONING_REF)) {
+        currentRef =
+            new ObjectIdRef.Unpeeled(Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
+      }
+      if (!sharedRefDb.exists(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF)) {
+        currentVersion = Optional.empty();
+      }
+
       boolean success = sharedRefDb.compareAndPut(projectNameKey, currentRef, newObjectId);
       if (!success) {
         String message =
             String.format(
                 "Project version blob update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), currentRef.getObjectId(), newObjectId);
+                projectNameKey.get(), safeGetObjectId(currentRef), newObjectId);
         logger.atSevere().log(message);
         throw new SharedProjectVersionUpdateException(message);
       }
+
       success =
           sharedRefDb.compareAndPut(
               projectNameKey,
@@ -230,7 +228,7 @@
         String message =
             String.format(
                 "Project version update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), currentRef.getObjectId(), newObjectId);
+                projectNameKey.get(), safeGetObjectId(currentRef), newObjectId);
         logger.atSevere().log(message);
         throw new SharedProjectVersionUpdateException(message);
       }
@@ -256,6 +254,8 @@
         logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
         return Optional.of(repoVersion);
       }
+    } catch (RepositoryNotFoundException re) {
+      logger.atFine().log("Project '%s' not found", projectName);
     } catch (IOException e) {
       logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
     }
@@ -269,6 +269,10 @@
     return globalVersion.flatMap(longString -> getLongValueOf(longString));
   }
 
+  private Object safeGetObjectId(Ref currentRef) {
+    return currentRef == null ? "null" : currentRef.getObjectId();
+  }
+
   private Optional<Long> getLongValueOf(String longString) {
     try {
       return Optional.ofNullable(Long.parseLong(longString));
@@ -324,6 +328,8 @@
         logger.atSevere().log(message);
         throw new LocalProjectVersionUpdateException(message);
       }
+
+      gitReferenceUpdated.fire(projectNameKey, refUpdate, null);
       return Optional.of(refUpdate.getNewObjectId());
     } catch (IOException e) {
       String message = "Cannot create versioning command for " + projectNameKey.get();
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 1719f38..481d288 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
@@ -21,7 +21,9 @@
 import com.google.inject.Scopes;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
@@ -45,6 +47,7 @@
 
     bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
     bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+    bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
     factory(LockWrapper.Factory.class);
 
     factory(MultiSiteRepository.Factory.class);
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
new file mode 100644
index 0000000..99148fa
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
@@ -0,0 +1,98 @@
+// 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.consumer;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.google.common.base.Suppliers;
+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.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;
+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 SubscriberMetricsTest {
+  private static final String A_TEST_PROJECT_NAME = "test-project";
+  private static final Project.NameKey A_TEST_PROJECT_NAME_KEY =
+      Project.nameKey(A_TEST_PROJECT_NAME);
+
+  @Mock private SharedRefDatabaseWrapper sharedRefDb;
+  @Mock private GitReferenceUpdated gitReferenceUpdated;
+  @Mock private MetricMaker metricMaker;
+  @Mock private ProjectVersionLogger verLogger;
+  @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
+  private SubscriberMetrics metrics;
+  private EventMessage.Header msgHeader;
+
+  @Before
+  public void setup() throws Exception {
+    msgHeader = new EventMessage.Header(UUID.randomUUID(), UUID.randomUUID());
+    metrics = new SubscriberMetrics(metricMaker, projectVersionRefUpdate, verLogger);
+  }
+
+  @Test
+  public void shouldLogProjectVersionWhenReceivingRefUpdatedEventWithoutLag() {
+    Optional<Long> globalRefDbVersion = Optional.of(System.currentTimeMillis() / 1000);
+    when(projectVersionRefUpdate.getProjectRemoteVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+    when(projectVersionRefUpdate.getProjectLocalVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+
+    EventMessage eventMessage = new EventMessage(msgHeader, newRefUpdateEvent());
+
+    metrics.updateReplicationStatusMetrics(eventMessage);
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, globalRefDbVersion.get(), 0);
+  }
+
+  @Test
+  public void shouldLogProjectVersionWhenReceivingRefUpdatedEventWithALag() {
+    Optional<Long> globalRefDbVersion = Optional.of(System.currentTimeMillis() / 1000);
+    long replicationLag = 60;
+    when(projectVersionRefUpdate.getProjectRemoteVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion.map(ts -> ts + replicationLag));
+    when(projectVersionRefUpdate.getProjectLocalVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+
+    EventMessage eventMessage = new EventMessage(msgHeader, newRefUpdateEvent());
+
+    metrics.updateReplicationStatusMetrics(eventMessage);
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, globalRefDbVersion.get(), replicationLag);
+  }
+
+  private RefUpdatedEvent newRefUpdateEvent() {
+    RefUpdateAttribute refUpdate = new RefUpdateAttribute();
+    refUpdate.project = A_TEST_PROJECT_NAME;
+    refUpdate.refName = "refs/heads/foo";
+    refUpdate.newRev = "591727cfec5174368a7829f79741c41683d84c89";
+    RefUpdatedEvent refUpdateEvent = new RefUpdatedEvent();
+    refUpdateEvent.refUpdate = Suppliers.ofInstance(refUpdate);
+    return refUpdateEvent;
+  }
+}
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 9a4020d..20b49c1 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
@@ -18,22 +18,24 @@
 import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF;
 import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atMost;
-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.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 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 com.googlesource.gerrit.plugins.replication.RefReplicationDoneEvent;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Optional;
@@ -59,6 +61,8 @@
 
   @Mock RefUpdatedEvent refUpdatedEvent;
   @Mock SharedRefDatabaseWrapper sharedRefDb;
+  @Mock GitReferenceUpdated gitReferenceUpdated;
+  @Mock ProjectVersionLogger verLogger;
 
   @Inject private ProjectConfig.Factory projectConfigFactory;
   @Inject private InMemoryRepositoryManager repoManager;
@@ -84,12 +88,21 @@
   @Test
   public void producerShouldUpdateProjectVersionUponRefUpdatedEvent() throws IOException {
     Context.setForwardedEvent(false);
+    when(sharedRefDb.exists(
+            A_TEST_PROJECT_NAME_KEY, ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF))
+        .thenReturn(true);
+    when(sharedRefDb.exists(
+            A_TEST_PROJECT_NAME_KEY, ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF))
+        .thenReturn(true);
     when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
         .thenReturn(true);
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any()))
+        .thenReturn(true);
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent);
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
 
@@ -101,6 +114,43 @@
     ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
     String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name());
     assertThat(Long.parseLong(storedVersion)).isEqualTo(masterCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0);
+  }
+
+  @Test
+  public void producerShouldCreateNewProjectVersionWhenMissingUponRefUpdatedEvent()
+      throws IOException {
+    Context.setForwardedEvent(false);
+    when(sharedRefDb.exists(
+            A_TEST_PROJECT_NAME_KEY, ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF))
+        .thenReturn(false);
+    when(sharedRefDb.exists(
+            A_TEST_PROJECT_NAME_KEY, ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF))
+        .thenReturn(false);
+
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
+        .thenReturn(true);
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any()))
+        .thenReturn(true);
+    when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
+    when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+
+    verify(sharedRefDb, atMost(1))
+        .compareAndPut(any(Project.NameKey.class), isNull(), any(ObjectId.class));
+
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name());
+    assertThat(Long.parseLong(storedVersion)).isEqualTo(masterCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0);
   }
 
   @Test
@@ -122,10 +172,13 @@
     when(refUpdatedEvent.getRefName()).thenReturn(magicRefName);
     repo.branch(magicRefName).commit().create();
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent);
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
     assertThat(ref).isNull();
+
+    verifyZeroInteractions(verLogger);
   }
 
   @Test
@@ -134,57 +187,13 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(Project.nameKey("aNonExistentProject"));
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent);
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
     assertThat(ref).isNull();
-  }
 
-  @Test
-  public void consumerShouldUpdateProjectVersionUponRefReplicationDoneEvent() throws IOException {
-    Context.setForwardedEvent(true);
-    RefReplicationDoneEvent refReplicatedEvent =
-        new RefReplicationDoneEvent(A_TEST_PROJECT_NAME, A_TEST_REF_NAME, 1);
-
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent);
-
-    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
-    assertThat(ref).isNotNull();
-
-    verify(sharedRefDb, never())
-        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
-
-    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
-    String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name());
-    assertThat(Long.parseLong(storedVersion))
-        .isEqualTo(Integer.toUnsignedLong(masterCommit.getCommitTime()));
-  }
-
-  @Test
-  public void consumerShouldNotUpdateProjectVersionUponSequenceRefReplicationDoneEvent()
-      throws Exception {
-    consumerShouldNotUpdateProjectVersionUponMagicRefReplicationDoneEvent(RefNames.REFS_SEQUENCES);
-  }
-
-  @Test
-  public void consumerShouldNotUpdateProjectVersionUponStarredChangesRefReplicationDoneEvent()
-      throws Exception {
-    consumerShouldNotUpdateProjectVersionUponMagicRefReplicationDoneEvent(
-        RefNames.REFS_STARRED_CHANGES);
-  }
-
-  private void consumerShouldNotUpdateProjectVersionUponMagicRefReplicationDoneEvent(
-      String magicRefPrefix) throws Exception {
-    String magicRef = magicRefPrefix + "/foo";
-    Context.setForwardedEvent(true);
-    RefReplicationDoneEvent refReplicationDoneEvent =
-        new RefReplicationDoneEvent(A_TEST_PROJECT_NAME, magicRef, 1);
-    repo.branch(magicRef).commit().create();
-
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicationDoneEvent);
-
-    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
-    assertThat(ref).isNull();
+    verifyZeroInteractions(verLogger);
   }
 
   @Test
@@ -193,31 +202,10 @@
         .thenReturn(Optional.of("123"));
 
     Optional<Long> version =
-        new ProjectVersionRefUpdate(repoManager, sharedRefDb)
+        new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
             .getProjectRemoteVersion(A_TEST_PROJECT_NAME);
 
     assertThat(version.isPresent()).isTrue();
     assertThat(version.get()).isEqualTo(123L);
   }
-
-  @Test
-  public void getLocalProjectVersionShouldReturnCorrectValue() throws IOException {
-    updateLocalVersion();
-    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
-    assertThat(ref).isNotNull();
-
-    Optional<Long> version =
-        new ProjectVersionRefUpdate(repoManager, sharedRefDb)
-            .getProjectLocalVersion(A_TEST_PROJECT_NAME);
-
-    assertThat(version.isPresent()).isTrue();
-    assertThat(version.get()).isEqualTo(masterCommit.getCommitTime());
-  }
-
-  private void updateLocalVersion() {
-    Context.setForwardedEvent(true);
-    RefReplicationDoneEvent refReplicatedEvent =
-        new RefReplicationDoneEvent(A_TEST_PROJECT_NAME, A_TEST_REF_NAME, 1);
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent);
-  }
 }