Use put operation for project version update

Project version update is used to generate metrics
about the project replication state. This information
doesn't have to be milliseconds precise, if two updates
happened within a short period of time we do not have to
guarantee that the latest one will be stored as long as
the project version ref is updated to one of those two.
This means that using `compareAndPut` operation is not
necessary and more lightweight `put` operation can be
used instead.

Bug: Issue 297440086

Change-Id: I6d3413f86c55f5fc9ab6323d30628e58ad74a137
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 14a421e..e07fef0 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
@@ -24,6 +24,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
@@ -143,7 +144,6 @@
             .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
             .map(Long::parseLong);
 
-    String sharedValue = sharedVersion.map(Object::toString).orElse(null);
     try {
       if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
         logger.atWarning().log(
@@ -157,29 +157,41 @@
           String.format(
               "Updating shared project %s value to %d", projectNameKey.get(), newVersion));
 
-      boolean success =
-          sharedRefDb.compareAndPut(
-              projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, sharedValue, newVersion.toString());
-      if (!success) {
-        String message =
-            String.format(
-                "Project value update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), sharedValue, newVersion);
-        logger.atSevere().log(message);
-        throw new SharedProjectVersionUpdateException(message);
-      }
-
+      updateProjectVersionValue(projectNameKey, newVersion, sharedVersion);
       return true;
     } catch (GlobalRefDbSystemError refDbSystemError) {
       String message =
           String.format(
               "Error while updating shared project value for %s. Current value %s, new value: %s. Error: %s",
-              projectNameKey.get(), sharedValue, newVersion, refDbSystemError.getMessage());
+              projectNameKey.get(),
+              sharedVersion.map(Object::toString).orElse(null),
+              newVersion,
+              refDbSystemError.getMessage());
       logger.atSevere().withCause(refDbSystemError).log(message);
       throw new SharedProjectVersionUpdateException(message);
     }
   }
 
+  private void updateProjectVersionValue(
+      NameKey projectNameKey, Long newVersion, Optional<Long> sharedVersion) {
+    try {
+      if (sharedRefDb.isSetOperationSupported()) {
+        sharedRefDb.put(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, newVersion.toString());
+        return;
+      }
+    } catch (NoSuchMethodError e) {
+      logger.atSevere().log(
+          "Global-refdb library is outdated and is not supporting "
+              + "'put' method, update global-refdb to the newest version. Falling back to 'compareAndPut'");
+    }
+
+    sharedRefDb.compareAndPut(
+        projectNameKey,
+        MULTI_SITE_VERSIONING_VALUE_REF,
+        sharedVersion.map(Object::toString).orElse(null),
+        newVersion.toString());
+  }
+
   /* (non-Javadoc)
    * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectLocalVersion(java.lang.String)
    */
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
index b7ad98c..fc2259f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
@@ -36,4 +36,7 @@
 
   @Override
   public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {}
+
+  @Override
+  public <T> void logRefUpdate(String project, String refName, T newRefValue) {}
 }
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 63da1b8..d99fb4b 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,16 +18,21 @@
 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.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 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.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.google.common.base.Suppliers;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -122,6 +127,35 @@
   }
 
   @Test
+  public void producerShouldUsePutInsteadOfCompareAndPutWhenExtendedGlobalRefDb()
+      throws IOException {
+    when(sharedRefDb.isSetOperationSupported()).thenReturn(true);
+    RefUpdatedEvent refUpdatedEvent = new RefUpdatedEvent();
+    RefUpdateAttribute refUpdatedAttribute = new RefUpdateAttribute();
+    refUpdatedAttribute.project = A_TEST_PROJECT_NAME_KEY.get();
+    refUpdatedAttribute.refName = A_TEST_REF_NAME;
+    refUpdatedEvent.refUpdate = Suppliers.memoize(() -> refUpdatedAttribute);
+
+    new ProjectVersionRefUpdateImpl(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+
+    verify(sharedRefDb, never())
+        .compareAndPut(any(Project.NameKey.class), anyString(), anyLong(), anyLong());
+
+    verify(sharedRefDb).put(any(Project.NameKey.class), any(String.class), any(String.class));
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    long storedVersion = readLongObject(loader);
+    assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
+  }
+
+  @Test
   public void producerShouldUpdateProjectVersionUponForcedPushRefUpdatedEvent() throws Exception {
     Context.setForwardedEvent(false);