Merge branch 'stable-3.1'

* stable-3.1:
  Use a global version number instead of the commit timestamp
  Update project version on global ref-db only when more recent
  Add missing prometheus configuration

Change-Id: Iaf3846dc0a8210762c80b19c9bc6b87293ce54b1
diff --git a/setup_local_env/prometheus-config/prometheus.yml b/setup_local_env/prometheus-config/prometheus.yml
new file mode 100644
index 0000000..e6d3ea1
--- /dev/null
+++ b/setup_local_env/prometheus-config/prometheus.yml
@@ -0,0 +1,14 @@
+# my global config
+global:
+  scrape_interval:     10s
+  evaluation_interval: 10s
+
+scrape_configs:
+  - job_name: gerrit
+    static_configs:
+      - targets: ['localhost:18080','localhost:18081']
+    metrics_path: '/plugins/metrics-reporter-prometheus/metrics'
+    params:
+      format: ['prometheus']
+    bearer_token: token
+
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 32b8af3..15b1e09 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
@@ -79,7 +79,7 @@
   @Override
   public Optional<ChangeNotes> getChangeNotes() {
     try (ManualRequestContext ctx = oneOffReqCtx.open()) {
-      this.changeNotes = Optional.ofNullable(changeFinder.findOne(changeId));
+      this.changeNotes = changeFinder.findOne(changeId);
       return changeNotes;
     }
   }
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 bd910ad..73bab49 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
@@ -18,10 +18,8 @@
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
-import com.google.common.primitives.Ints;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.events.Event;
@@ -42,13 +40,9 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 
 @Singleton
 public class ProjectVersionRefUpdate implements EventListener {
@@ -105,23 +99,18 @@
     }
     try {
       Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
-      Ref currentProjectVersionRef = getLocalProjectVersionRef(refUpdatedEvent.getProjectNameKey());
-      Optional<Long> currentProjectVersionValue =
-          getLongFromObjectId(projectNameKey.get(), currentProjectVersionRef.getObjectId());
+      long newVersion = getCurrentGlobalVersionNumber();
 
-      Optional<Long> lastRefUpdatedTimestamp = getLastRefUpdatedTimestamp(projectNameKey, refName);
-      Optional<ObjectId> newProjectVersionObjectId =
-          updateLocalProjectVersion(projectNameKey, lastRefUpdatedTimestamp);
+      Optional<RefUpdate> newProjectVersionRefUpdate =
+          updateLocalProjectVersion(projectNameKey, newVersion);
 
-      if (newProjectVersionObjectId.isPresent()) {
-        verLogger.log(projectNameKey, lastRefUpdatedTimestamp.get(), 0L);
+      if (newProjectVersionRefUpdate.isPresent()) {
+        verLogger.log(projectNameKey, newVersion, 0L);
 
-        updateSharedProjectVersion(
-            projectNameKey,
-            currentProjectVersionRef,
-            newProjectVersionObjectId.get(),
-            currentProjectVersionValue,
-            lastRefUpdatedTimestamp);
+        if (updateSharedProjectVersion(
+            projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
+          gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
+        }
       } else {
         logger.atWarning().log(
             "Ref %s not found on projet %s: skipping project version update",
@@ -149,71 +138,49 @@
     return newId;
   }
 
-  private Ref getLocalProjectVersionRef(Project.NameKey projectNameKey)
-      throws LocalProjectVersionUpdateException {
-    try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
-      Ref ref = repository.findRef(MULTI_SITE_VERSIONING_REF);
-      return ref != null ? ref : NULL_PROJECT_VERSION_REF;
-    } catch (IOException e) {
-      String message =
-          String.format("Error while getting current version ref for %s", projectNameKey.get());
-      logger.atSevere().withCause(e).log(message);
-      throw new LocalProjectVersionUpdateException(message);
-    }
-  }
-
-  private Optional<Long> getLastRefUpdatedTimestamp(Project.NameKey projectNameKey, String refName)
-      throws LocalProjectVersionUpdateException {
-    logger.atFine().log(
-        String.format(
-            "Getting last ref updated time for project %s, ref %s", projectNameKey.get(), refName));
-    try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
-      Ref ref = repository.findRef(refName);
-      if (ref == null) {
-        logger.atWarning().log("Unable to find ref " + refName + " in project " + projectNameKey);
-        return Optional.empty();
-      }
-      try (RevWalk walk = new RevWalk(repository)) {
-        RevCommit commit = walk.parseCommit(ref.getObjectId());
-        return Optional.of(Integer.toUnsignedLong(commit.getCommitTime()));
-      }
-    } catch (IOException ioe) {
-      String message =
-          String.format(
-              "Error while getting last ref updated time for project %s, ref %s",
-              projectNameKey.get(), refName);
-      logger.atSevere().withCause(ioe).log(message);
-      throw new LocalProjectVersionUpdateException(message);
-    }
-  }
-
-  private void updateSharedProjectVersion(
-      Project.NameKey projectNameKey,
-      Ref currentRef,
-      ObjectId newObjectId,
-      Optional<Long> currentVersion,
-      Optional<Long> newVersion)
+  private boolean updateSharedProjectVersion(
+      Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
       throws SharedProjectVersionUpdateException {
 
-    logger.atFine().log(
-        String.format(
-            "Updating shared project version for %s. Current value %s, new value: %s",
-            projectNameKey.get(), currentRef.getObjectId(), newObjectId));
+    Ref sharedRef =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
+            .map(
+                (String objectId) ->
+                    new ObjectIdRef.Unpeeled(
+                        Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
+            .orElse(
+                new ObjectIdRef.Unpeeled(
+                    Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
+    Optional<Long> sharedVersion =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
+            .map(Long::parseLong);
+
     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();
+      if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
+        logger.atWarning().log(
+            String.format(
+                "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
+                projectNameKey.get(),
+                newObjectId,
+                newVersion,
+                sharedRef.getObjectId().getName(),
+                sharedVersion.get()));
+        return false;
       }
 
-      boolean success = sharedRefDb.compareAndPut(projectNameKey, currentRef, newObjectId);
+      logger.atFine().log(
+          String.format(
+              "Updating shared project %s version to %s (value=%d)",
+              projectNameKey.get(), newObjectId, newVersion));
+
+      boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
       if (!success) {
         String message =
             String.format(
                 "Project version blob update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), safeGetObjectId(currentRef), newObjectId);
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
         logger.atSevere().log(message);
         throw new SharedProjectVersionUpdateException(message);
       }
@@ -222,22 +189,24 @@
           sharedRefDb.compareAndPut(
               projectNameKey,
               MULTI_SITE_VERSIONING_VALUE_REF,
-              currentVersion.map(Object::toString).orElse(null),
-              newVersion.map(Object::toString).orElse(null));
+              sharedVersion.map(Object::toString).orElse(null),
+              newVersion.toString());
       if (!success) {
         String message =
             String.format(
                 "Project version update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), safeGetObjectId(currentRef), newObjectId);
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
         logger.atSevere().log(message);
         throw new SharedProjectVersionUpdateException(message);
       }
+
+      return true;
     } catch (GlobalRefDbSystemError refDbSystemError) {
       String message =
           String.format(
               "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
               projectNameKey.get(),
-              currentRef.getObjectId(),
+              sharedRef.getObjectId(),
               newObjectId,
               refDbSystemError.getMessage());
       logger.atSevere().withCause(refDbSystemError).log(message);
@@ -283,54 +252,25 @@
     }
   }
 
-  private Optional<Long> getLongFromObjectId(String projectName, ObjectId objectId) {
-    if (objectId.equals(ObjectId.zeroId())) {
-      return Optional.empty();
-    }
-    try (Repository repository =
-        gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
-      ObjectReader or = repository.newObjectReader();
-      ObjectLoader ol = or.open(objectId, OBJ_BLOB);
-      if (ol.getType() != OBJ_BLOB) {
-        // In theory this should be thrown by open but not all implementations may do it properly
-        // (certainly InMemoryRepository doesn't).
-        logger.atSevere().log("Incorrect object type loaded for objectId %s", objectId.toString());
-        return Optional.empty();
-      }
-      String str = CharMatcher.whitespace().trimFrom(new String(ol.getCachedBytes(), UTF_8));
-      Integer value = Ints.tryParse(str);
-      logger.atInfo().log(
-          "Found remote version for project %s, value: %s - %d",
-          projectName, objectId.toString(), value);
-      return Optional.of(Integer.toUnsignedLong(value));
-    } catch (IOException e) {
-      logger.atSevere().withCause(e).log("Cannot parse objectId %s", objectId.toString());
-      return Optional.empty();
-    }
-  }
-
-  private Optional<ObjectId> updateLocalProjectVersion(
-      Project.NameKey projectNameKey, Optional<Long> lastRefUpdatedTimestamp)
+  private Optional<RefUpdate> updateLocalProjectVersion(
+      Project.NameKey projectNameKey, long newVersionNumber)
       throws LocalProjectVersionUpdateException {
-    if (!lastRefUpdatedTimestamp.isPresent()) {
-      return Optional.empty();
-    }
-
-    logger.atFine().log("Updating local version for project " + projectNameKey.get());
+    logger.atFine().log(
+        "Updating local version for project %s with version %d",
+        projectNameKey.get(), newVersionNumber);
     try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
-      RefUpdate refUpdate = getProjectVersionRefUpdate(repository, lastRefUpdatedTimestamp.get());
+      RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
       RefUpdate.Result result = refUpdate.update();
       if (!isSuccessful(result)) {
         String message =
             String.format(
                 "RefUpdate failed with result %s for: project=%s, version=%d",
-                result.name(), projectNameKey.get(), lastRefUpdatedTimestamp.get());
+                result.name(), projectNameKey.get(), newVersionNumber);
         logger.atSevere().log(message);
         throw new LocalProjectVersionUpdateException(message);
       }
 
-      gitReferenceUpdated.fire(projectNameKey, refUpdate, null);
-      return Optional.of(refUpdate.getNewObjectId());
+      return Optional.of(refUpdate);
     } catch (IOException e) {
       String message = "Cannot create versioning command for " + projectNameKey.get();
       logger.atSevere().withCause(e).log(message);
@@ -338,6 +278,10 @@
     }
   }
 
+  private long getCurrentGlobalVersionNumber() {
+    return System.currentTimeMillis() / 1000;
+  }
+
   private Boolean isSuccessful(RefUpdate.Result result) {
     return SUCCESSFUL_RESULTS.contains(result);
   }
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 20b49c1..2eefb5a 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
@@ -88,12 +88,16 @@
   @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.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.of("26f7ee61bf0e470e8393c884526eec8a9b943a63"));
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.of("" + (masterCommit.getCommitTime() - 1)));
     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()))
@@ -112,22 +116,72 @@
     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());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
 
-    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0);
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
+  }
+
+  @Test
+  public void producerShouldUpdateProjectVersionUponForcedPushRefUpdatedEvent() throws Exception {
+    Context.setForwardedEvent(false);
+
+    Thread.sleep(1000L);
+    RevCommit masterPlusOneCommit = repo.branch("master").commit().create();
+
+    Thread.sleep(1000L);
+    repo.branch("master").update(masterCommit);
+
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.of("26f7ee61bf0e470e8393c884526eec8a9b943a63"));
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.of("" + (masterCommit.getCommitTime() - 1)));
+    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), any(Ref.class), any(ObjectId.class));
+
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterPlusOneCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 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.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.empty());
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.empty());
 
     when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
         .thenReturn(true);
@@ -147,10 +201,11 @@
     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());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
 
-    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0);
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
   }
 
   @Test