Fix changes re-indexing during Gerrit start

When changes are fetched from the repo, the changes list is not sorted
by lastUpdatedDate.
When Gerrit re-indexed the change, it updated lastIndexTimeStamp with
change lastUpdatedDate value, as lastIndexTimeStamp < lastUpdatedDate.
If the most recent change was fetched first, all next changes, that
were created after lastIndexTimeStamp, were not re-indexed.
The change introduces intermediate variable, that holds latest
timestamp until all changes are re-indexed. Only when re-indexing
is completed, lastIndexTimeStamp is updated with most recent timestamp.

Bug: Issue 15632
Change-Id: I14ee82915cba1b2ca528cf8ffe7a7305ab8eaf54
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
index 3cb0ed0..92ec127 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
@@ -53,18 +53,20 @@
         int count = 0;
         int errors = 0;
         Stopwatch stopwatch = Stopwatch.createStarted();
+        Timestamp maxFetchedItemTs = Timestamp.valueOf(newLastIndexTs.toLocalDateTime());
         for (T c : fetchItems()) {
           try {
             Optional<Timestamp> itemTs = indexIfNeeded(c, newLastIndexTs);
             if (itemTs.isPresent()) {
               count++;
-              newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get());
+              maxFetchedItemTs = maxTimestamp(maxFetchedItemTs, itemTs.get());
             }
           } catch (Exception e) {
             log.atSevere().withCause(e).log("Unable to reindex %s %s", itemNameString, c);
             errors++;
           }
         }
+        newLastIndexTs = maxTimestamp(newLastIndexTs, maxFetchedItemTs);
         long elapsedNanos = stopwatch.stop().elapsed(TimeUnit.NANOSECONDS);
         if (count > 0) {
           log.atInfo().log(
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnableTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnableTest.java
index 28dc451..e338dcb 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnableTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnableTest.java
@@ -59,8 +59,10 @@
   @Mock private AllUsersName allUsers;
   @Mock private ChangeNotes.Factory changeNotesFactory;
   @Mock private Repository repo;
-  @Mock private ChangeNotesResult changeNotesRes;
-  @Mock private ChangeNotes changeNotes;
+  @Mock private ChangeNotesResult changeNotesResFirst;
+  @Mock private ChangeNotesResult changeNotesResSecond;
+  @Mock private ChangeNotes changeNotesFirst;
+  @Mock private ChangeNotes changeNotesSecond;
 
   private ChangeReindexRunnable changeReindexRunnable;
 
@@ -75,7 +77,7 @@
   public void changeIsIndexedWhenItIsCreatedAfterLastChangeReindex() throws Exception {
     Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
     Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
-    Change change = newChange(afterCurrentTime);
+    Change change = newChange(123, afterCurrentTime);
 
     Optional<Timestamp> changeLastTs = changeReindexRunnable.indexIfNeeded(change, currentTime);
     assertThat(changeLastTs.isPresent()).isTrue();
@@ -88,16 +90,17 @@
     LocalDateTime currentTime = LocalDateTime.now(ZoneOffset.UTC);
     Timestamp afterCurrentTime =
         new Timestamp(currentTime.toEpochSecond(ZoneOffset.UTC) * 1000 + 1000L);
-    Change change = newChange(afterCurrentTime);
+    Change change = newChange(123, afterCurrentTime);
 
     when(indexTs.getUpdateTs(AbstractIndexRestApiServlet.IndexName.CHANGE))
         .thenReturn(Optional.of(currentTime));
     when(projectCache.all()).thenReturn(ImmutableSortedSet.of(change.getProject()));
     when(repoManager.openRepository(change.getProject())).thenReturn(repo);
-    when(changeNotesFactory.scan(repo, change.getProject())).thenReturn(Stream.of(changeNotesRes));
-    lenient().when(changeNotesRes.error()).thenReturn(Optional.empty());
-    when(changeNotesRes.notes()).thenReturn(changeNotes);
-    when(changeNotes.getChange()).thenReturn(change);
+    when(changeNotesFactory.scan(repo, change.getProject()))
+        .thenReturn(Stream.of(changeNotesResFirst));
+    lenient().when(changeNotesResFirst.error()).thenReturn(Optional.empty());
+    when(changeNotesResFirst.notes()).thenReturn(changeNotesFirst);
+    when(changeNotesFirst.getChange()).thenReturn(change);
 
     changeReindexRunnable.run();
 
@@ -109,7 +112,7 @@
     LocalDateTime currentTime = LocalDateTime.now(ZoneOffset.UTC);
     Timestamp afterCurrentTime =
         new Timestamp(currentTime.toEpochSecond(ZoneOffset.UTC) * 1000 + 1000L);
-    Change change = newChange(afterCurrentTime);
+    Change change = newChange(123, afterCurrentTime);
 
     when(indexTs.getUpdateTs(AbstractIndexRestApiServlet.IndexName.CHANGE))
         .thenReturn(Optional.of(currentTime));
@@ -119,10 +122,10 @@
     lenient().when(invalidChangeRes.notes()).thenThrow(IllegalStateException.class);
     when(invalidChangeRes.error()).thenReturn(Optional.of(new IllegalStateException()));
     when(changeNotesFactory.scan(repo, change.getProject()))
-        .thenReturn(Stream.of(invalidChangeRes, changeNotesRes));
-    when(changeNotesRes.error()).thenReturn(Optional.empty());
-    when(changeNotesRes.notes()).thenReturn(changeNotes);
-    when(changeNotes.getChange()).thenReturn(change);
+        .thenReturn(Stream.of(invalidChangeRes, changeNotesResFirst));
+    when(changeNotesResFirst.error()).thenReturn(Optional.empty());
+    when(changeNotesResFirst.notes()).thenReturn(changeNotesFirst);
+    when(changeNotesFirst.getChange()).thenReturn(change);
 
     changeReindexRunnable.run();
 
@@ -133,7 +136,7 @@
   public void groupIsNotIndexedWhenItIsCreatedBeforeLastGroupReindex() throws Exception {
     Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
     Timestamp beforeCurrentTime = new Timestamp(currentTime.getTime() - 1000L);
-    Change change = newChange(beforeCurrentTime);
+    Change change = newChange(123, beforeCurrentTime);
 
     Optional<Timestamp> changeLastTs = changeReindexRunnable.indexIfNeeded(change, currentTime);
     assertThat(changeLastTs.isPresent()).isFalse();
@@ -141,14 +144,44 @@
         .index(changeProjectIndexKey(change), Operation.INDEX, Optional.empty());
   }
 
+  @Test
+  public void twoChangesAreIndexedDuringTheRunWhenItIsCreatedAfterLastChangeReindex()
+      throws Exception {
+    LocalDateTime currentTime = LocalDateTime.now(ZoneOffset.UTC);
+    Timestamp afterCurrentTime =
+        new Timestamp(currentTime.toEpochSecond(ZoneOffset.UTC) * 1000 + 1000L);
+    Timestamp secondAfterCurrentTime =
+        new Timestamp(currentTime.toEpochSecond(ZoneOffset.UTC) * 1000 + 2000L);
+    Change firstChange = newChange(123, secondAfterCurrentTime);
+    Change secondChange = newChange(456, afterCurrentTime);
+
+    when(indexTs.getUpdateTs(AbstractIndexRestApiServlet.IndexName.CHANGE))
+        .thenReturn(Optional.of(currentTime));
+    when(projectCache.all()).thenReturn(ImmutableSortedSet.of(firstChange.getProject()));
+    when(repoManager.openRepository(firstChange.getProject())).thenReturn(repo);
+    when(changeNotesFactory.scan(repo, firstChange.getProject()))
+        .thenReturn(Stream.of(changeNotesResFirst, changeNotesResSecond));
+    lenient().when(changeNotesResFirst.error()).thenReturn(Optional.empty());
+    lenient().when(changeNotesResSecond.error()).thenReturn(Optional.empty());
+    when(changeNotesResFirst.notes()).thenReturn(changeNotesFirst);
+    when(changeNotesResSecond.notes()).thenReturn(changeNotesSecond);
+    when(changeNotesFirst.getChange()).thenReturn(firstChange);
+    when(changeNotesSecond.getChange()).thenReturn(secondChange);
+
+    changeReindexRunnable.run();
+
+    verify(indexer).index(changeProjectIndexKey(firstChange), Operation.INDEX, Optional.empty());
+    verify(indexer).index(changeProjectIndexKey(secondChange), Operation.INDEX, Optional.empty());
+  }
+
   private String changeProjectIndexKey(Change change) {
     return change.getProject() + "~" + change.getChangeId();
   }
 
-  private Change newChange(Timestamp changeTs) {
+  private Change newChange(int id, Timestamp changeTs) {
     return new Change(
         Change.key("changekey"),
-        Change.id(123),
+        Change.id(id),
         Account.id(1000000),
         BranchNameKey.create("projectname", "main"),
         changeTs.toInstant());